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):
398 :param name: A symbolic name for the port (optional).
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.network_name:
457 raise PortSettingsError(
458 'The attribute network_name is required')
460 def __set_fixed_ips(self, neutron):
462 Sets the self.fixed_ips value
463 :param neutron: the Neutron client
466 if not self.fixed_ips and self.ip_addrs:
467 self.fixed_ips = list()
469 for ip_addr_dict in self.ip_addrs:
470 subnet = neutron_utils.get_subnet(
471 neutron, subnet_name=ip_addr_dict['subnet_name'])
472 if subnet and 'ip' in ip_addr_dict:
473 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
474 'subnet_id': subnet.id})
476 raise PortSettingsError(
477 'Invalid port configuration, subnet does not exist '
478 'with name - ' + ip_addr_dict['subnet_name'])
480 def dict_for_neutron(self, neutron, os_creds):
482 Returns a dictionary object representing this object.
483 This is meant to be converted into JSON designed for use by the Neutron
486 TODO - expand automated testing to exercise all parameters
487 :param neutron: the Neutron client
488 :param os_creds: the OpenStack credentials
489 :return: the dictionary object
491 self.__set_fixed_ips(neutron)
496 if self.project_name:
497 keystone = keystone_utils.keystone_client(os_creds)
498 project = keystone_utils.get_project(
499 keystone=keystone, project_name=self.project_name)
501 project_id = project.id
504 self.network = neutron_utils.get_network(
505 neutron, network_name=self.network_name, project_id=project_id)
507 raise PortSettingsError(
508 'Cannot locate network with name - ' + self.network_name)
510 out['network_id'] = self.network.id
512 if self.admin_state_up is not None:
513 out['admin_state_up'] = self.admin_state_up
515 out['name'] = self.name
516 if self.project_name:
518 out['tenant_id'] = project_id
520 raise PortSettingsError(
521 'Could not find project ID for project named - ' +
524 out['mac_address'] = self.mac_address
525 if self.fixed_ips and len(self.fixed_ips) > 0:
526 out['fixed_ips'] = self.fixed_ips
527 if self.security_groups:
528 out['security_groups'] = self.security_groups
529 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
530 out['allowed_address_pairs'] = self.allowed_address_pairs
532 out['opt_value'] = self.opt_value
534 out['opt_name'] = self.opt_name
535 if self.device_owner:
536 out['device_owner'] = self.device_owner
538 out['device_id'] = self.device_id
541 def __eq__(self, other):
542 return (self.name == other.name and
543 self.network_name == other.network_name and
544 self.admin_state_up == other.admin_state_up and
545 self.project_name == other.project_name and
546 self.mac_address == other.mac_address and
547 self.ip_addrs == other.ip_addrs and
548 self.fixed_ips == other.fixed_ips and
549 self.security_groups == other.security_groups and
550 self.allowed_address_pairs == other.allowed_address_pairs and
551 self.opt_value == other.opt_value and
552 self.opt_name == other.opt_name and
553 self.device_owner == other.device_owner and
554 self.device_id == other.device_id)
557 class PortSettingsError(Exception):
559 Exception to be thrown when port settings attributes are incorrect