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, network_settings=self.network_settings,
57 project_id=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.id)
72 logger.debug('Creating Subnets....')
73 for subnet_setting in self.network_settings.subnet_settings:
74 sub_inst = neutron_utils.get_subnet(
75 self.__neutron, subnet_settings=subnet_setting)
77 self.__subnets.append(sub_inst)
79 "Subnet '%s' created successfully" % sub_inst.id)
82 self.__subnets.append(
83 neutron_utils.create_subnet(
84 self.__neutron, subnet_setting, self.__os_creds,
91 Removes and deletes all items created in reverse order.
93 for subnet in self.__subnets:
96 'Deleting subnet with name ' + subnet.name)
97 neutron_utils.delete_subnet(self.__neutron, subnet)
100 'Error deleting subnet with message - ' + str(e))
102 self.__subnets = list()
106 neutron_utils.delete_network(self.__neutron, self.__network)
110 self.__network = None
112 def get_network(self):
114 Returns the created OpenStack network object
115 :return: the OpenStack network object
117 return self.__network
119 def get_subnets(self):
121 Returns the OpenStack subnet objects
124 return self.__subnets
127 class NetworkSettings:
129 Class representing a network configuration
132 def __init__(self, **kwargs):
134 Constructor - all parameters are optional
135 :param name: The network name.
136 :param admin_state_up: The administrative status of the network.
137 True = up / False = down (default True)
138 :param shared: Boolean value indicating whether this network is shared
139 across all projects/tenants. By default, only
140 administrative users can change this value.
141 :param project_name: Admin-only. The name of the project that will own
142 the network. This project can be different from
143 the project that makes the create network request.
144 However, only administrative users can specify a
145 project ID other than their own. You cannot change
146 this value through authorization policies.
147 :param external: when true, will setup an external network
149 :param network_type: the type of network (i.e. vlan|flat).
150 :param physical_network: the name of the physical network
151 (this is required when network_type is 'flat')
152 :param segmentation_id: the id of the segmentation
153 (this is required when network_type is 'vlan')
154 :param subnets or subnet_settings: List of SubnetSettings objects.
158 self.project_id = None
160 self.name = kwargs.get('name')
161 if kwargs.get('admin_state_up') is not None:
162 self.admin_state_up = bool(kwargs['admin_state_up'])
164 self.admin_state_up = True
166 if kwargs.get('shared') is not None:
167 self.shared = bool(kwargs['shared'])
171 self.project_name = kwargs.get('project_name')
173 if kwargs.get('external') is not None:
174 self.external = bool(kwargs.get('external'))
176 self.external = False
178 self.network_type = kwargs.get('network_type')
179 self.physical_network = kwargs.get('physical_network')
180 self.segmentation_id = kwargs.get('segmentation_id')
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 NetworkSettingsError('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(
209 keystone=keystone, project_name=self.project_name)
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['tenant_id'] = project_id
238 raise NetworkSettingsError(
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
245 if self.segmentation_id:
246 out['provider:segmentation_id'] = self.segmentation_id
248 out['router:external'] = self.external
249 return {'network': out}
252 class NetworkSettingsError(Exception):
254 Exception to be thrown when networks settings attributes are incorrect
258 class SubnetSettings:
260 Class representing a subnet configuration
263 def __init__(self, **kwargs):
265 Constructor - all parameters are optional except cidr (subnet mask)
266 :param cidr: The CIDR. REQUIRED if config parameter is None
267 :param ip_version: The IP version, which is 4 or 6.
268 :param name: The subnet name.
269 :param project_name: The name of the project who owns the network.
270 Only administrative users can specify a project ID
271 other than their own. You cannot change this value
272 through authorization policies.
273 :param start: The start address for the allocation pools.
274 :param end: The end address for the allocation pools.
275 :param gateway_ip: The gateway IP address.
276 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
278 :param dns_nameservers: A list of DNS name servers for the subnet.
279 Specify each name server as an IP address
280 and separate multiple entries with a space.
281 For example [8.8.8.7 8.8.8.8].
282 :param host_routes: A list of host route dictionaries for the subnet.
286 "destination":"0.0.0.0/0",
287 "nexthop":"123.456.78.9"
290 "destination":"192.168.0.0/24",
291 "nexthop":"192.168.0.1"
294 :param destination: The destination for static route
295 :param nexthop: The next hop for the destination.
296 :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
297 dhcpv6-stateless, or slaac.
298 :param ipv6_address_mode: A valid value is dhcpv6-stateful,
299 dhcpv6-stateless, or slaac.
300 :raise: SubnetSettingsError when config does not have or cidr values
303 self.cidr = kwargs.get('cidr')
304 if kwargs.get('ip_version'):
305 self.ip_version = kwargs['ip_version']
309 # Optional attributes that can be set after instantiation
310 self.name = kwargs.get('name')
311 self.project_name = kwargs.get('project_name')
312 self.start = kwargs.get('start')
313 self.end = kwargs.get('end')
314 self.gateway_ip = kwargs.get('gateway_ip')
315 self.enable_dhcp = kwargs.get('enable_dhcp')
317 if kwargs.get('dns_nameservers'):
318 self.dns_nameservers = kwargs.get('dns_nameservers')
320 self.dns_nameservers = ['8.8.8.8']
322 self.host_routes = kwargs.get('host_routes')
323 self.destination = kwargs.get('destination')
324 self.nexthop = kwargs.get('nexthop')
325 self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
326 self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
328 if not self.name or not self.cidr:
329 raise SubnetSettingsError('Name and cidr required for subnets')
331 def dict_for_neutron(self, os_creds, network=None):
333 Returns a dictionary object representing this object.
334 This is meant to be converted into JSON designed for use by the Neutron
336 :param os_creds: the OpenStack credentials
337 :param network: The network object on which the subnet will be created
339 :return: the dictionary object
343 'ip_version': self.ip_version,
347 out['network_id'] = network.id
349 out['name'] = self.name
350 if self.project_name:
351 keystone = keystone_utils.keystone_client(os_creds)
352 project = keystone_utils.get_project(
353 keystone=keystone, project_name=self.project_name)
356 project_id = project.id
358 out['tenant_id'] = project_id
360 raise SubnetSettingsError(
361 'Could not find project ID for project named - ' +
363 if self.start and self.end:
364 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
366 out['gateway_ip'] = self.gateway_ip
367 if self.enable_dhcp is not None:
368 out['enable_dhcp'] = self.enable_dhcp
369 if self.dns_nameservers and len(self.dns_nameservers) > 0:
370 out['dns_nameservers'] = self.dns_nameservers
371 if self.host_routes and len(self.host_routes) > 0:
372 out['host_routes'] = self.host_routes
374 out['destination'] = self.destination
376 out['nexthop'] = self.nexthop
377 if self.ipv6_ra_mode:
378 out['ipv6_ra_mode'] = self.ipv6_ra_mode
379 if self.ipv6_address_mode:
380 out['ipv6_address_mode'] = self.ipv6_address_mode
384 class SubnetSettingsError(Exception):
386 Exception to be thrown when subnet settings attributes are incorrect
392 Class representing a port configuration
395 def __init__(self, **kwargs):
397 Constructor - all parameters are optional
398 :param name: A symbolic name for the port.
399 :param network_name: The name of the network on which to create the
401 :param admin_state_up: A boolean value denoting the administrative
402 status of the port. True = up / False = down
403 :param project_name: The name of the project who owns the network.
404 Only administrative users can specify a project ID
405 other than their own. You cannot change this value
406 through authorization policies.
407 :param mac_address: The MAC address. If you specify an address that is
408 not valid, a Bad Request (400) status code is
409 returned. If you do not specify a MAC address,
410 OpenStack Networking tries to allocate one. If a
411 failure occurs, a Service Unavailable (503) status
413 :param ip_addrs: A list of dict objects where each contains two keys
414 'subnet_name' and 'ip' values which will get mapped to
415 self.fixed_ips. These values will be directly
416 translated into the fixed_ips dict
417 :param fixed_ips: A dict where the key is the subnet IDs and value is
418 the IP address to assign to the port
419 :param security_groups: One or more security group IDs.
420 :param allowed_address_pairs: A dictionary containing a set of zero or
421 more allowed address pairs. An address
422 pair contains an IP address and MAC
424 :param opt_value: The extra DHCP option value.
425 :param opt_name: The extra DHCP option name.
426 :param device_owner: The ID of the entity that uses this port.
427 For example, a DHCP agent.
428 :param device_id: The ID of the device that uses this port.
429 For example, a virtual server.
433 kwargs = kwargs['port']
437 self.name = kwargs.get('name')
438 self.network_name = kwargs.get('network_name')
440 if kwargs.get('admin_state_up') is not None:
441 self.admin_state_up = bool(kwargs['admin_state_up'])
443 self.admin_state_up = True
445 self.project_name = kwargs.get('project_name')
446 self.mac_address = kwargs.get('mac_address')
447 self.ip_addrs = kwargs.get('ip_addrs')
448 self.fixed_ips = kwargs.get('fixed_ips')
449 self.security_groups = kwargs.get('security_groups')
450 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
451 self.opt_value = kwargs.get('opt_value')
452 self.opt_name = kwargs.get('opt_name')
453 self.device_owner = kwargs.get('device_owner')
454 self.device_id = kwargs.get('device_id')
456 if not self.name or not self.network_name:
457 raise PortSettingsError(
458 'The attributes neutron, name, and network_name are required '
461 def __set_fixed_ips(self, neutron):
463 Sets the self.fixed_ips value
464 :param neutron: the Neutron client
467 if not self.fixed_ips and self.ip_addrs:
468 self.fixed_ips = list()
470 for ip_addr_dict in self.ip_addrs:
471 subnet = neutron_utils.get_subnet(
472 neutron, subnet_name=ip_addr_dict['subnet_name'])
473 if subnet and 'ip' in ip_addr_dict:
474 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
475 'subnet_id': subnet.id})
477 raise PortSettingsError(
478 'Invalid port configuration, subnet does not exist '
479 'with name - ' + ip_addr_dict['subnet_name'])
481 def dict_for_neutron(self, neutron, os_creds):
483 Returns a dictionary object representing this object.
484 This is meant to be converted into JSON designed for use by the Neutron
487 TODO - expand automated testing to exercise all parameters
488 :param neutron: the Neutron client
489 :param os_creds: the OpenStack credentials
490 :return: the dictionary object
492 self.__set_fixed_ips(neutron)
497 if self.project_name:
498 keystone = keystone_utils.keystone_client(os_creds)
499 project = keystone_utils.get_project(
500 keystone=keystone, project_name=self.project_name)
502 project_id = project.id
505 self.network = neutron_utils.get_network(
506 neutron, network_name=self.network_name, project_id=project_id)
508 raise PortSettingsError(
509 'Cannot locate network with name - ' + self.network_name)
511 out['network_id'] = self.network.id
513 if self.admin_state_up is not None:
514 out['admin_state_up'] = self.admin_state_up
516 out['name'] = self.name
517 if self.project_name:
519 out['tenant_id'] = project_id
521 raise PortSettingsError(
522 'Could not find project ID for project named - ' +
525 out['mac_address'] = self.mac_address
526 if self.fixed_ips and len(self.fixed_ips) > 0:
527 out['fixed_ips'] = self.fixed_ips
528 if self.security_groups:
529 out['security_groups'] = self.security_groups
530 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
531 out['allowed_address_pairs'] = self.allowed_address_pairs
533 out['opt_value'] = self.opt_value
535 out['opt_name'] = self.opt_name
536 if self.device_owner:
537 out['device_owner'] = self.device_owner
539 out['device_id'] = self.device_id
542 def __eq__(self, other):
543 return (self.name == other.name and
544 self.network_name == other.network_name and
545 self.admin_state_up == other.admin_state_up and
546 self.project_name == other.project_name and
547 self.mac_address == other.mac_address and
548 self.ip_addrs == other.ip_addrs and
549 self.fixed_ips == other.fixed_ips and
550 self.security_groups == other.security_groups and
551 self.allowed_address_pairs == other.allowed_address_pairs and
552 self.opt_value == other.opt_value and
553 self.opt_name == other.opt_name and
554 self.device_owner == other.device_owner and
555 self.device_id == other.device_id)
558 class PortSettingsError(Exception):
560 Exception to be thrown when port settings attributes are incorrect