1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 from neutronclient.common.exceptions import NotFound
18 from snaps.openstack.utils import keystone_utils, neutron_utils
20 __author__ = 'spisarski'
22 logger = logging.getLogger('OpenStackNetwork')
25 class OpenStackNetwork:
27 Class responsible for creating a network in OpenStack
30 def __init__(self, os_creds, network_settings):
32 Constructor - all parameters are required
33 :param os_creds: The credentials to connect with OpenStack
34 :param network_settings: The settings used to create a network
36 self.__os_creds = os_creds
37 self.network_settings = network_settings
40 # Attributes instantiated on create()
42 self.__subnets = list()
44 def create(self, cleanup=False):
46 Responsible for creating not only the network but then a private
47 subnet, router, and an interface to the router.
48 :param cleanup: When true, only perform lookups for OpenStack objects.
49 :return: the created network object or None
51 self.__neutron = neutron_utils.neutron_client(self.__os_creds)
54 'Creating neutron network %s...' % self.network_settings.name)
55 net_inst = neutron_utils.get_network(
56 self.__neutron, self.network_settings.name,
57 self.network_settings.get_project_id(self.__os_creds))
59 self.__network = net_inst
62 self.__network = neutron_utils.create_network(
63 self.__neutron, self.__os_creds, self.network_settings)
66 'Network does not exist and will not create as in cleanup'
70 "Network '%s' created successfully" % self.__network['network'][
73 logger.debug('Creating Subnets....')
74 for subnet_setting in self.network_settings.subnet_settings:
75 sub_inst = neutron_utils.get_subnet_by_name(self.__neutron,
78 self.__subnets.append(sub_inst)
80 "Subnet '%s' created successfully" % sub_inst['subnet'][
84 self.__subnets.append(
85 neutron_utils.create_subnet(self.__neutron,
94 Removes and deletes all items created in reverse order.
96 for subnet in self.__subnets:
99 'Deleting subnet with name ' + subnet['subnet']['name'])
100 neutron_utils.delete_subnet(self.__neutron, subnet)
101 except NotFound as e:
103 'Error deleting subnet with message - ' + str(e))
105 self.__subnets = list()
109 neutron_utils.delete_network(self.__neutron, self.__network)
113 self.__network = None
115 def get_network(self):
117 Returns the created OpenStack network object
118 :return: the OpenStack network object
120 return self.__network
122 def get_subnets(self):
124 Returns the OpenStack subnet objects
127 return self.__subnets
130 class NetworkSettings:
132 Class representing a network configuration
135 def __init__(self, **kwargs):
137 Constructor - all parameters are optional
138 :param name: The network name.
139 :param admin_state_up: The administrative status of the network.
140 True = up / False = down (default True)
141 :param shared: Boolean value indicating whether this network is shared
142 across all projects/tenants. By default, only
143 administrative users can change this value.
144 :param project_name: Admin-only. The name of the project that will own
145 the network. This project can be different from
146 the project that makes the create network request.
147 However, only administrative users can specify a
148 project ID other than their own. You cannot change
149 this value through authorization policies.
150 :param external: when true, will setup an external network
152 :param network_type: the type of network (i.e. vlan|flat).
153 :param physical_network: the name of the physical network
154 (this is required when network_type is 'flat')
155 :param subnets or subnet_settings: List of SubnetSettings objects.
159 self.project_id = None
161 self.name = kwargs.get('name')
162 if kwargs.get('admin_state_up') is not None:
163 self.admin_state_up = bool(kwargs['admin_state_up'])
165 self.admin_state_up = True
167 if kwargs.get('shared') is not None:
168 self.shared = bool(kwargs['shared'])
172 self.project_name = kwargs.get('project_name')
174 if kwargs.get('external') is not None:
175 self.external = bool(kwargs.get('external'))
177 self.external = False
179 self.network_type = kwargs.get('network_type')
180 self.physical_network = kwargs.get('physical_network')
182 self.subnet_settings = list()
183 subnet_settings = kwargs.get('subnets')
184 if not subnet_settings:
185 subnet_settings = kwargs.get('subnet_settings')
187 for subnet_config in subnet_settings:
188 if isinstance(subnet_config, SubnetSettings):
189 self.subnet_settings.append(subnet_config)
191 self.subnet_settings.append(
192 SubnetSettings(**subnet_config['subnet']))
194 if not self.name or len(self.name) < 1:
195 raise Exception('Name required for networks')
197 def get_project_id(self, os_creds):
199 Returns the project ID for a given project_name or None
200 :param os_creds: the credentials required for keystone client retrieval
201 :return: the ID or None
204 return self.project_id
206 if self.project_name:
207 keystone = keystone_utils.keystone_client(os_creds)
208 project = keystone_utils.get_project(keystone,
215 def dict_for_neutron(self, os_creds):
217 Returns a dictionary object representing this object.
218 This is meant to be converted into JSON designed for use by the Neutron
220 TODO - expand automated testing to exercise all parameters
222 :param os_creds: the OpenStack credentials
223 :return: the dictionary object
228 out['name'] = self.name
229 if self.admin_state_up is not None:
230 out['admin_state_up'] = self.admin_state_up
232 out['shared'] = self.shared
233 if self.project_name:
234 project_id = self.get_project_id(os_creds)
236 out['project_id'] = project_id
239 'Could not find project ID for project named - ' +
241 if self.network_type:
242 out['provider:network_type'] = self.network_type
243 if self.physical_network:
244 out['provider:physical_network'] = self.physical_network
246 out['router:external'] = self.external
247 return {'network': out}
250 class SubnetSettings:
252 Class representing a subnet configuration
255 def __init__(self, **kwargs):
257 Constructor - all parameters are optional except cidr (subnet mask)
258 :param cidr: The CIDR. REQUIRED if config parameter is None
259 :param ip_version: The IP version, which is 4 or 6.
260 :param name: The subnet name.
261 :param project_name: The name of the project who owns the network.
262 Only administrative users can specify a project ID
263 other than their own. You cannot change this value
264 through authorization policies.
265 :param start: The start address for the allocation pools.
266 :param end: The end address for the allocation pools.
267 :param gateway_ip: The gateway IP address.
268 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
270 :param dns_nameservers: A list of DNS name servers for the subnet.
271 Specify each name server as an IP address
272 and separate multiple entries with a space.
273 For example [8.8.8.7 8.8.8.8].
274 :param host_routes: A list of host route dictionaries for the subnet.
278 "destination":"0.0.0.0/0",
279 "nexthop":"123.456.78.9"
282 "destination":"192.168.0.0/24",
283 "nexthop":"192.168.0.1"
286 :param destination: The destination for static route
287 :param nexthop: The next hop for the destination.
288 :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
289 dhcpv6-stateless, or slaac.
290 :param ipv6_address_mode: A valid value is dhcpv6-stateful,
291 dhcpv6-stateless, or slaac.
292 :raise: Exception when config does not have or cidr values are None
294 self.cidr = kwargs['cidr']
295 if kwargs.get('ip_version'):
296 self.ip_version = kwargs['ip_version']
300 # Optional attributes that can be set after instantiation
301 self.name = kwargs.get('name')
302 self.project_name = kwargs.get('project_name')
303 self.start = kwargs.get('start')
304 self.end = kwargs.get('end')
305 self.gateway_ip = kwargs.get('gateway_ip')
306 self.enable_dhcp = kwargs.get('enable_dhcp')
308 if kwargs.get('dns_nameservers'):
309 self.dns_nameservers = kwargs.get('dns_nameservers')
311 self.dns_nameservers = ['8.8.8.8']
313 self.host_routes = kwargs.get('host_routes')
314 self.destination = kwargs.get('destination')
315 self.nexthop = kwargs.get('nexthop')
316 self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
317 self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
319 if not self.name or not self.cidr:
320 raise Exception('Name and cidr required for subnets')
322 def dict_for_neutron(self, os_creds, network=None):
324 Returns a dictionary object representing this object.
325 This is meant to be converted into JSON designed for use by the Neutron
327 :param os_creds: the OpenStack credentials
328 :param network: The network object on which the subnet will be created
330 :return: the dictionary object
334 'ip_version': self.ip_version,
338 out['network_id'] = network['network']['id']
340 out['name'] = self.name
341 if self.project_name:
342 keystone = keystone_utils.keystone_client(os_creds)
343 project = keystone_utils.get_project(keystone, self.project_name)
346 project_id = project.id
348 out['project_id'] = project_id
351 'Could not find project ID for project named - ' +
353 if self.start and self.end:
354 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
356 out['gateway_ip'] = self.gateway_ip
357 if self.enable_dhcp is not None:
358 out['enable_dhcp'] = self.enable_dhcp
359 if self.dns_nameservers and len(self.dns_nameservers) > 0:
360 out['dns_nameservers'] = self.dns_nameservers
361 if self.host_routes and len(self.host_routes) > 0:
362 out['host_routes'] = self.host_routes
364 out['destination'] = self.destination
366 out['nexthop'] = self.nexthop
367 if self.ipv6_ra_mode:
368 out['ipv6_ra_mode'] = self.ipv6_ra_mode
369 if self.ipv6_address_mode:
370 out['ipv6_address_mode'] = self.ipv6_address_mode
376 Class representing a port configuration
379 def __init__(self, **kwargs):
381 Constructor - all parameters are optional
382 :param name: A symbolic name for the port.
383 :param network_name: The name of the network on which to create the
385 :param admin_state_up: A boolean value denoting the administrative
386 status of the port. True = up / False = down
387 :param project_name: The name of the project who owns the network.
388 Only administrative users can specify a project ID
389 other than their own. You cannot change this value
390 through authorization policies.
391 :param mac_address: The MAC address. If you specify an address that is
392 not valid, a Bad Request (400) status code is
393 returned. If you do not specify a MAC address,
394 OpenStack Networking tries to allocate one. If a
395 failure occurs, a Service Unavailable (503) status
397 :param ip_addrs: A list of dict objects where each contains two keys
398 'subnet_name' and 'ip' values which will get mapped to
399 self.fixed_ips. These values will be directly
400 translated into the fixed_ips dict
401 :param fixed_ips: A dict where the key is the subnet IDs and value is
402 the IP address to assign to the port
403 :param security_groups: One or more security group IDs.
404 :param allowed_address_pairs: A dictionary containing a set of zero or
405 more allowed address pairs. An address
406 pair contains an IP address and MAC
408 :param opt_value: The extra DHCP option value.
409 :param opt_name: The extra DHCP option name.
410 :param device_owner: The ID of the entity that uses this port.
411 For example, a DHCP agent.
412 :param device_id: The ID of the device that uses this port.
413 For example, a virtual server.
417 kwargs = kwargs['port']
421 self.name = kwargs.get('name')
422 self.network_name = kwargs.get('network_name')
424 if kwargs.get('admin_state_up') is not None:
425 self.admin_state_up = bool(kwargs['admin_state_up'])
427 self.admin_state_up = True
429 self.project_name = kwargs.get('project_name')
430 self.mac_address = kwargs.get('mac_address')
431 self.ip_addrs = kwargs.get('ip_addrs')
432 self.fixed_ips = kwargs.get('fixed_ips')
433 self.security_groups = kwargs.get('security_groups')
434 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
435 self.opt_value = kwargs.get('opt_value')
436 self.opt_name = kwargs.get('opt_name')
437 self.device_owner = kwargs.get('device_owner')
438 self.device_id = kwargs.get('device_id')
440 if not self.name or not self.network_name:
442 'The attributes neutron, name, and network_name are required '
445 def __set_fixed_ips(self, neutron):
447 Sets the self.fixed_ips value
448 :param neutron: the Neutron client
451 if not self.fixed_ips and self.ip_addrs:
452 self.fixed_ips = list()
454 for ip_addr_dict in self.ip_addrs:
455 subnet = neutron_utils.get_subnet_by_name(neutron,
459 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
460 'subnet_id': subnet['subnet'][
464 'Invalid port configuration, subnet does not exist '
465 'with name - ' + ip_addr_dict['subnet_name'])
467 def dict_for_neutron(self, neutron, os_creds):
469 Returns a dictionary object representing this object.
470 This is meant to be converted into JSON designed for use by the Neutron
473 TODO - expand automated testing to exercise all parameters
474 :param neutron: the Neutron client
475 :param os_creds: the OpenStack credentials
476 :return: the dictionary object
478 self.__set_fixed_ips(neutron)
483 if self.project_name:
484 keystone = keystone_utils.keystone_client(os_creds)
485 project = keystone_utils.get_project(keystone, self.project_name)
487 project_id = project.id
490 self.network = neutron_utils.get_network(neutron,
495 'Cannot locate network with name - ' + self.network_name)
497 out['network_id'] = self.network['network']['id']
499 if self.admin_state_up is not None:
500 out['admin_state_up'] = self.admin_state_up
502 out['name'] = self.name
503 if self.project_name:
505 out['project_id'] = project_id
508 'Could not find project ID for project named - ' +
511 out['mac_address'] = self.mac_address
512 if self.fixed_ips and len(self.fixed_ips) > 0:
513 out['fixed_ips'] = self.fixed_ips
514 if self.security_groups:
515 out['security_groups'] = self.security_groups
516 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
517 out['allowed_address_pairs'] = self.allowed_address_pairs
519 out['opt_value'] = self.opt_value
521 out['opt_name'] = self.opt_name
522 if self.device_owner:
523 out['device_owner'] = self.device_owner
525 out['device_id'] = self.device_id