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
19 from snaps.openstack.openstack_creator import OpenStackNetworkObject
20 from snaps.openstack.utils import keystone_utils, neutron_utils
22 __author__ = 'spisarski'
24 logger = logging.getLogger('OpenStackNetwork')
27 class OpenStackNetwork(OpenStackNetworkObject):
29 Class responsible for managing a network in OpenStack
32 def __init__(self, os_creds, network_settings):
34 Constructor - all parameters are required
35 :param os_creds: The credentials to connect with OpenStack
36 :param network_settings: The settings used to create a network
38 super(self.__class__, self).__init__(os_creds)
40 self.network_settings = network_settings
42 # Attributes instantiated on create()
44 self.__subnets = list()
48 Loads the existing OpenStack network/subnet
49 :return: The Network domain object or None
51 super(self.__class__, self).initialize()
53 self.__network = neutron_utils.get_network(
54 self._neutron, network_settings=self.network_settings,
55 project_id=self.network_settings.get_project_id(self._os_creds))
58 for subnet_setting in self.network_settings.subnet_settings:
59 sub_inst = neutron_utils.get_subnet(
60 self._neutron, subnet_settings=subnet_setting)
62 self.__subnets.append(sub_inst)
64 "Subnet '%s' created successfully" % sub_inst.id)
70 Responsible for creating not only the network but then a private
71 subnet, router, and an interface to the router.
72 :return: the Network domain object
76 if not self.__network:
77 self.__network = neutron_utils.create_network(
78 self._neutron, self._os_creds, self.network_settings)
80 "Network '%s' created successfully" % self.__network.id)
82 for subnet_setting in self.network_settings.subnet_settings:
83 sub_inst = neutron_utils.get_subnet(
84 self._neutron, subnet_settings=subnet_setting)
86 sub_inst = neutron_utils.create_subnet(
87 self._neutron, subnet_setting, self._os_creds,
90 self.__subnets.append(sub_inst)
92 "Subnet '%s' created successfully" % sub_inst.id)
98 Removes and deletes all items created in reverse order.
100 for subnet in self.__subnets:
103 'Deleting subnet with name ' + subnet.name)
104 neutron_utils.delete_subnet(self._neutron, subnet)
105 except NotFound as e:
107 'Error deleting subnet with message - ' + str(e))
109 self.__subnets = list()
113 neutron_utils.delete_network(self._neutron, self.__network)
117 self.__network = None
119 def get_network(self):
121 Returns the created OpenStack network object
122 :return: the OpenStack network object
124 return self.__network
126 def get_subnets(self):
128 Returns the OpenStack subnet objects
131 return self.__subnets
134 class NetworkSettings:
136 Class representing a network configuration
139 def __init__(self, **kwargs):
141 Constructor - all parameters are optional
142 :param name: The network name.
143 :param admin_state_up: The administrative status of the network.
144 True = up / False = down (default True)
145 :param shared: Boolean value indicating whether this network is shared
146 across all projects/tenants. By default, only
147 administrative users can change this value.
148 :param project_name: Admin-only. The name of the project that will own
149 the network. This project can be different from
150 the project that makes the create network request.
151 However, only administrative users can specify a
152 project ID other than their own. You cannot change
153 this value through authorization policies.
154 :param external: when true, will setup an external network
156 :param network_type: the type of network (i.e. vlan|flat).
157 :param physical_network: the name of the physical network
158 (required when network_type is 'flat')
159 :param segmentation_id: the id of the segmentation
160 (this is required when network_type is 'vlan')
161 :param subnets or subnet_settings: List of SubnetSettings objects.
165 self.project_id = None
167 self.name = kwargs.get('name')
168 if kwargs.get('admin_state_up') is not None:
169 self.admin_state_up = bool(kwargs['admin_state_up'])
171 self.admin_state_up = True
173 if kwargs.get('shared') is not None:
174 self.shared = bool(kwargs['shared'])
178 self.project_name = kwargs.get('project_name')
180 if kwargs.get('external') is not None:
181 self.external = bool(kwargs.get('external'))
183 self.external = False
185 self.network_type = kwargs.get('network_type')
186 self.physical_network = kwargs.get('physical_network')
187 self.segmentation_id = kwargs.get('segmentation_id')
189 self.subnet_settings = list()
190 subnet_settings = kwargs.get('subnets')
191 if not subnet_settings:
192 subnet_settings = kwargs.get('subnet_settings')
194 for subnet_config in subnet_settings:
195 if isinstance(subnet_config, SubnetSettings):
196 self.subnet_settings.append(subnet_config)
198 self.subnet_settings.append(
199 SubnetSettings(**subnet_config['subnet']))
201 if not self.name or len(self.name) < 1:
202 raise NetworkSettingsError('Name required for networks')
204 def get_project_id(self, os_creds):
206 Returns the project ID for a given project_name or None
207 :param os_creds: the credentials required for keystone client retrieval
208 :return: the ID or None
211 return self.project_id
213 if self.project_name:
214 keystone = keystone_utils.keystone_client(os_creds)
215 project = keystone_utils.get_project(
216 keystone=keystone, project_name=self.project_name)
222 def dict_for_neutron(self, os_creds):
224 Returns a dictionary object representing this object.
225 This is meant to be converted into JSON designed for use by the Neutron
227 TODO - expand automated testing to exercise all parameters
229 :param os_creds: the OpenStack credentials
230 :return: the dictionary object
235 out['name'] = self.name
236 if self.admin_state_up is not None:
237 out['admin_state_up'] = self.admin_state_up
239 out['shared'] = self.shared
240 if self.project_name:
241 project_id = self.get_project_id(os_creds)
243 out['tenant_id'] = project_id
245 raise NetworkSettingsError(
246 'Could not find project ID for project named - ' +
248 if self.network_type:
249 out['provider:network_type'] = self.network_type
250 if self.physical_network:
251 out['provider:physical_network'] = self.physical_network
252 if self.segmentation_id:
253 out['provider:segmentation_id'] = self.segmentation_id
255 out['router:external'] = self.external
256 return {'network': out}
259 class NetworkSettingsError(Exception):
261 Exception to be thrown when networks settings attributes are incorrect
265 class SubnetSettings:
267 Class representing a subnet configuration
270 def __init__(self, **kwargs):
272 Constructor - all parameters are optional except cidr (subnet mask)
273 :param name: The subnet name (required)
274 :param cidr: The CIDR (required)
275 :param ip_version: The IP version, which is 4 or 6 (required)
276 :param project_name: The name of the project who owns the network.
277 Only administrative users can specify a project ID
278 other than their own. You cannot change this value
279 through authorization policies (optional)
280 :param start: The start address for the allocation pools (optional)
281 :param end: The end address for the allocation pools (optional)
282 :param gateway_ip: The gateway IP address (optional)
283 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
285 :param dns_nameservers: A list of DNS name servers for the subnet.
286 Specify each name server as an IP address
287 and separate multiple entries with a space.
288 For example [8.8.8.7 8.8.8.8]
290 :param host_routes: A list of host route dictionaries for the subnet.
294 "destination":"0.0.0.0/0",
295 "nexthop":"123.456.78.9"
298 "destination":"192.168.0.0/24",
299 "nexthop":"192.168.0.1"
302 :param destination: The destination for static route (optional)
303 :param nexthop: The next hop for the destination (optional)
304 :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
305 dhcpv6-stateless, or slaac (optional)
306 :param ipv6_address_mode: A valid value is dhcpv6-stateful,
307 dhcpv6-stateless, or slaac (optional)
308 :raise: SubnetSettingsError when config does not have or cidr values
311 self.cidr = kwargs.get('cidr')
312 if kwargs.get('ip_version'):
313 self.ip_version = kwargs['ip_version']
317 # Optional attributes that can be set after instantiation
318 self.name = kwargs.get('name')
319 self.project_name = kwargs.get('project_name')
320 self.start = kwargs.get('start')
321 self.end = kwargs.get('end')
322 self.gateway_ip = kwargs.get('gateway_ip')
323 self.enable_dhcp = kwargs.get('enable_dhcp')
325 if kwargs.get('dns_nameservers'):
326 self.dns_nameservers = kwargs.get('dns_nameservers')
328 self.dns_nameservers = ['8.8.8.8']
330 self.host_routes = kwargs.get('host_routes')
331 self.destination = kwargs.get('destination')
332 self.nexthop = kwargs.get('nexthop')
333 self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
334 self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
336 if not self.name or not self.cidr:
337 raise SubnetSettingsError('Name and cidr required for subnets')
339 def dict_for_neutron(self, os_creds, network=None):
341 Returns a dictionary object representing this object.
342 This is meant to be converted into JSON designed for use by the Neutron
344 :param os_creds: the OpenStack credentials
345 :param network: The network object on which the subnet will be created
347 :return: the dictionary object
351 'ip_version': self.ip_version,
355 out['network_id'] = network.id
357 out['name'] = self.name
358 if self.project_name:
359 keystone = keystone_utils.keystone_client(os_creds)
360 project = keystone_utils.get_project(
361 keystone=keystone, project_name=self.project_name)
364 project_id = project.id
366 out['tenant_id'] = project_id
368 raise SubnetSettingsError(
369 'Could not find project ID for project named - ' +
371 if self.start and self.end:
372 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
374 out['gateway_ip'] = self.gateway_ip
375 if self.enable_dhcp is not None:
376 out['enable_dhcp'] = self.enable_dhcp
377 if self.dns_nameservers and len(self.dns_nameservers) > 0:
378 out['dns_nameservers'] = self.dns_nameservers
379 if self.host_routes and len(self.host_routes) > 0:
380 out['host_routes'] = self.host_routes
382 out['destination'] = self.destination
384 out['nexthop'] = self.nexthop
385 if self.ipv6_ra_mode:
386 out['ipv6_ra_mode'] = self.ipv6_ra_mode
387 if self.ipv6_address_mode:
388 out['ipv6_address_mode'] = self.ipv6_address_mode
392 class SubnetSettingsError(Exception):
394 Exception to be thrown when subnet settings attributes are incorrect
400 Class representing a port configuration
403 def __init__(self, **kwargs):
406 :param name: A symbolic name for the port (optional).
407 :param network_name: The name of the network on which to create the
409 :param admin_state_up: A boolean value denoting the administrative
410 status of the port (default = True)
411 :param project_name: The name of the project who owns the network.
412 Only administrative users can specify a project ID
413 other than their own. You cannot change this value
414 through authorization policies (optional)
415 :param mac_address: The MAC address. If you specify an address that is
416 not valid, a Bad Request (400) status code is
417 returned. If you do not specify a MAC address,
418 OpenStack Networking tries to allocate one. If a
419 failure occurs, a Service Unavailable (503) status
420 code is returned (optional)
421 :param ip_addrs: A list of dict objects where each contains two keys
422 'subnet_name' and 'ip' values which will get mapped to
423 self.fixed_ips. These values will be directly
424 translated into the fixed_ips dict (optional)
425 :param fixed_ips: A dict where the key is the subnet IDs and value is
426 the IP address to assign to the port (optional and
427 recommended to configure via ip_addrs instead)
428 :param security_groups: One or more security group IDs.
429 :param allowed_address_pairs: A dictionary containing a set of zero or
430 more allowed address pairs. An address
431 pair contains an IP address and MAC
433 :param opt_value: The extra DHCP option value (optional)
434 :param opt_name: The extra DHCP option name (optional)
435 :param device_owner: The ID of the entity that uses this port.
436 For example, a DHCP agent (optional)
437 :param device_id: The ID of the device that uses this port.
438 For example, a virtual server (optional)
442 kwargs = kwargs['port']
446 self.name = kwargs.get('name')
447 self.network_name = kwargs.get('network_name')
449 if kwargs.get('admin_state_up') is not None:
450 self.admin_state_up = bool(kwargs['admin_state_up'])
452 self.admin_state_up = True
454 self.project_name = kwargs.get('project_name')
455 self.mac_address = kwargs.get('mac_address')
456 self.ip_addrs = kwargs.get('ip_addrs')
457 self.fixed_ips = kwargs.get('fixed_ips')
458 self.security_groups = kwargs.get('security_groups')
459 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
460 self.opt_value = kwargs.get('opt_value')
461 self.opt_name = kwargs.get('opt_name')
462 self.device_owner = kwargs.get('device_owner')
463 self.device_id = kwargs.get('device_id')
465 if not self.network_name:
466 raise PortSettingsError(
467 'The attribute network_name is required')
469 def __set_fixed_ips(self, neutron):
471 Sets the self.fixed_ips value
472 :param neutron: the Neutron client
475 if not self.fixed_ips and self.ip_addrs:
476 self.fixed_ips = list()
478 for ip_addr_dict in self.ip_addrs:
479 subnet = neutron_utils.get_subnet(
480 neutron, subnet_name=ip_addr_dict['subnet_name'])
481 if subnet and 'ip' in ip_addr_dict:
482 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
483 'subnet_id': subnet.id})
485 raise PortSettingsError(
486 'Invalid port configuration, subnet does not exist '
487 'with name - ' + ip_addr_dict['subnet_name'])
489 def dict_for_neutron(self, neutron, os_creds):
491 Returns a dictionary object representing this object.
492 This is meant to be converted into JSON designed for use by the Neutron
495 TODO - expand automated testing to exercise all parameters
496 :param neutron: the Neutron client
497 :param os_creds: the OpenStack credentials
498 :return: the dictionary object
500 self.__set_fixed_ips(neutron)
505 if self.project_name:
506 keystone = keystone_utils.keystone_client(os_creds)
507 project = keystone_utils.get_project(
508 keystone=keystone, project_name=self.project_name)
510 project_id = project.id
513 self.network = neutron_utils.get_network(
514 neutron, network_name=self.network_name, project_id=project_id)
516 raise PortSettingsError(
517 'Cannot locate network with name - ' + self.network_name)
519 out['network_id'] = self.network.id
521 if self.admin_state_up is not None:
522 out['admin_state_up'] = self.admin_state_up
524 out['name'] = self.name
525 if self.project_name:
527 out['tenant_id'] = project_id
529 raise PortSettingsError(
530 'Could not find project ID for project named - ' +
533 out['mac_address'] = self.mac_address
534 if self.fixed_ips and len(self.fixed_ips) > 0:
535 out['fixed_ips'] = self.fixed_ips
536 if self.security_groups:
537 out['security_groups'] = self.security_groups
538 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
539 out['allowed_address_pairs'] = self.allowed_address_pairs
541 out['opt_value'] = self.opt_value
543 out['opt_name'] = self.opt_name
544 if self.device_owner:
545 out['device_owner'] = self.device_owner
547 out['device_id'] = self.device_id
550 def __eq__(self, other):
551 return (self.name == other.name and
552 self.network_name == other.network_name and
553 self.admin_state_up == other.admin_state_up and
554 self.project_name == other.project_name and
555 self.mac_address == other.mac_address and
556 self.ip_addrs == other.ip_addrs and
557 self.fixed_ips == other.fixed_ips and
558 self.security_groups == other.security_groups and
559 self.allowed_address_pairs == other.allowed_address_pairs and
560 self.opt_value == other.opt_value and
561 self.opt_name == other.opt_name and
562 self.device_owner == other.device_owner and
563 self.device_id == other.device_id)
566 class PortSettingsError(Exception):
568 Exception to be thrown when port settings attributes are incorrect