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.
18 from neutronclient.common.exceptions import NetworkNotFoundClient
20 from snaps.openstack.openstack_creator import OpenStackNetworkObject
21 from snaps.openstack.utils import keystone_utils, neutron_utils
23 __author__ = 'spisarski'
25 logger = logging.getLogger('OpenStackNetwork')
28 class OpenStackNetwork(OpenStackNetworkObject):
30 Class responsible for managing a network in OpenStack
33 def __init__(self, os_creds, network_settings):
35 Constructor - all parameters are required
36 :param os_creds: The credentials to connect with OpenStack
37 :param network_settings: The settings used to create a network
39 super(self.__class__, self).__init__(os_creds)
41 self.network_settings = network_settings
43 # Attributes instantiated on create()
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))
61 Responsible for creating not only the network but then a private
62 subnet, router, and an interface to the router.
63 :return: the Network domain object
67 if not self.__network:
68 self.__network = neutron_utils.create_network(
69 self._neutron, self._os_creds, self.network_settings)
71 'Network [%s] created successfully' % self.__network.id)
77 Removes and deletes all items created in reverse order.
81 neutron_utils.delete_network(self._neutron, self.__network)
82 except NetworkNotFoundClient:
86 def get_network(self):
88 Returns the created OpenStack network object
89 :return: the OpenStack network object
94 class NetworkSettings:
96 Class representing a network configuration
99 def __init__(self, **kwargs):
101 Constructor - all parameters are optional
102 :param name: The network name.
103 :param admin_state_up: The administrative status of the network.
104 True = up / False = down (default True)
105 :param shared: Boolean value indicating whether this network is shared
106 across all projects/tenants. By default, only
107 administrative users can change this value.
108 :param project_name: Admin-only. The name of the project that will own
109 the network. This project can be different from
110 the project that makes the create network request.
111 However, only administrative users can specify a
112 project ID other than their own. You cannot change
113 this value through authorization policies.
114 :param external: when true, will setup an external network
116 :param network_type: the type of network (i.e. vlan|flat).
117 :param physical_network: the name of the physical network
118 (required when network_type is 'flat')
119 :param segmentation_id: the id of the segmentation
120 (this is required when network_type is 'vlan')
121 :param subnets or subnet_settings: List of SubnetSettings objects.
125 self.project_id = None
127 self.name = kwargs.get('name')
128 if kwargs.get('admin_state_up') is not None:
129 self.admin_state_up = bool(kwargs['admin_state_up'])
131 self.admin_state_up = True
133 if kwargs.get('shared') is not None:
134 self.shared = bool(kwargs['shared'])
138 self.project_name = kwargs.get('project_name')
140 if kwargs.get('external') is not None:
141 self.external = bool(kwargs.get('external'))
143 self.external = False
145 self.network_type = kwargs.get('network_type')
146 self.physical_network = kwargs.get('physical_network')
147 self.segmentation_id = kwargs.get('segmentation_id')
149 self.subnet_settings = list()
150 subnet_settings = kwargs.get('subnets')
151 if not subnet_settings:
152 subnet_settings = kwargs.get('subnet_settings', list())
154 for subnet_config in subnet_settings:
155 if isinstance(subnet_config, SubnetSettings):
156 self.subnet_settings.append(subnet_config)
158 self.subnet_settings.append(
159 SubnetSettings(**subnet_config['subnet']))
161 if not self.name or len(self.name) < 1:
162 raise NetworkSettingsError('Name required for networks')
164 def get_project_id(self, os_creds):
166 Returns the project ID for a given project_name or None
167 :param os_creds: the credentials required for keystone client retrieval
168 :return: the ID or None
171 return self.project_id
173 if self.project_name:
174 keystone = keystone_utils.keystone_client(os_creds)
175 project = keystone_utils.get_project(
176 keystone=keystone, project_name=self.project_name)
182 def dict_for_neutron(self, os_creds):
184 Returns a dictionary object representing this object.
185 This is meant to be converted into JSON designed for use by the Neutron
187 TODO - expand automated testing to exercise all parameters
189 :param os_creds: the OpenStack credentials
190 :return: the dictionary object
195 out['name'] = self.name
196 if self.admin_state_up is not None:
197 out['admin_state_up'] = self.admin_state_up
199 out['shared'] = self.shared
200 if self.project_name:
201 project_id = self.get_project_id(os_creds)
203 out['tenant_id'] = project_id
205 raise NetworkSettingsError(
206 'Could not find project ID for project named - ' +
208 if self.network_type:
209 out['provider:network_type'] = self.network_type
210 if self.physical_network:
211 out['provider:physical_network'] = self.physical_network
212 if self.segmentation_id:
213 out['provider:segmentation_id'] = self.segmentation_id
215 out['router:external'] = self.external
216 return {'network': out}
219 class NetworkSettingsError(Exception):
221 Exception to be thrown when networks settings attributes are incorrect
225 class IPv6Mode(enum.Enum):
230 stateful = 'dhcpv6-stateful'
231 stateless = 'dhcpv6-stateless'
234 class SubnetSettings:
236 Class representing a subnet configuration
239 def __init__(self, **kwargs):
241 Constructor - all parameters are optional except cidr (subnet mask)
242 :param name: The subnet name (required)
243 :param cidr: The CIDR (required)
244 :param ip_version: The IP version, which is 4 or 6 (required)
245 :param project_name: The name of the project who owns the network.
246 Only administrative users can specify a project ID
247 other than their own. You cannot change this value
248 through authorization policies (optional)
249 :param start: The start address for the allocation pools (optional)
250 :param end: The end address for the allocation pools (optional)
251 :param gateway_ip: The gateway IP address (optional)
252 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
254 :param dns_nameservers: A list of DNS name servers for the subnet.
255 Specify each name server as an IP address
256 and separate multiple entries with a space.
257 For example [8.8.8.7 8.8.8.8]
259 :param host_routes: A list of host route dictionaries for the subnet.
263 "destination":"0.0.0.0/0",
264 "nexthop":"123.456.78.9"
267 "destination":"192.168.0.0/24",
268 "nexthop":"192.168.0.1"
271 :param destination: The destination for static route (optional)
272 :param nexthop: The next hop for the destination (optional)
273 :param ipv6_ra_mode: an instance of the IPv6Mode enum
274 (optional when enable_dhcp is True)
275 :param ipv6_address_mode: an instance of the IPv6Mode enum
276 (optional when enable_dhcp is True)
277 :raise: SubnetSettingsError when config does not have or cidr values
280 self.cidr = kwargs.get('cidr')
281 if kwargs.get('ip_version'):
282 self.ip_version = kwargs['ip_version']
286 # Optional attributes that can be set after instantiation
287 self.name = kwargs.get('name')
288 self.project_name = kwargs.get('project_name')
289 self.start = kwargs.get('start')
290 self.end = kwargs.get('end')
291 self.gateway_ip = kwargs.get('gateway_ip')
292 self.enable_dhcp = kwargs.get('enable_dhcp')
294 if 'dns_nameservers' in kwargs:
295 self.dns_nameservers = kwargs.get('dns_nameservers')
297 if self.ip_version == 4:
298 self.dns_nameservers = ['8.8.8.8']
300 self.dns_nameservers = list()
302 self.host_routes = kwargs.get('host_routes')
303 self.destination = kwargs.get('destination')
304 self.nexthop = kwargs.get('nexthop')
305 self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
306 self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
308 if not self.name or not self.cidr:
309 raise SubnetSettingsError('Name and cidr required for subnets')
311 def dict_for_neutron(self, os_creds, network=None):
313 Returns a dictionary object representing this object.
314 This is meant to be converted into JSON designed for use by the Neutron
316 :param os_creds: the OpenStack credentials
317 :param network: The network object on which the subnet will be created
319 :return: the dictionary object
323 'ip_version': self.ip_version,
327 out['network_id'] = network.id
329 out['name'] = self.name
330 if self.project_name:
331 keystone = keystone_utils.keystone_client(os_creds)
332 project = keystone_utils.get_project(
333 keystone=keystone, project_name=self.project_name)
336 project_id = project.id
338 out['tenant_id'] = project_id
340 raise SubnetSettingsError(
341 'Could not find project ID for project named - ' +
343 if self.start and self.end:
344 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
346 out['gateway_ip'] = self.gateway_ip
347 if self.enable_dhcp is not None:
348 out['enable_dhcp'] = self.enable_dhcp
349 if self.dns_nameservers and len(self.dns_nameservers) > 0:
350 out['dns_nameservers'] = self.dns_nameservers
351 if self.host_routes and len(self.host_routes) > 0:
352 out['host_routes'] = self.host_routes
354 out['destination'] = self.destination
356 out['nexthop'] = self.nexthop
357 if self.ipv6_ra_mode:
358 out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
359 if self.ipv6_address_mode:
360 out['ipv6_address_mode'] = self.ipv6_address_mode.value
366 Takes a the direction value maps it to the Direction enum. When None return
368 :param mode: the mode value
369 :return: the IPv6Mode enum object
370 :raise: SubnetSettingsError if value is invalid
374 if isinstance(mode, IPv6Mode):
378 if mode_str == 'slaac':
379 return IPv6Mode.slaac
380 elif mode_str == 'dhcpv6-stateful':
381 return IPv6Mode.stateful
382 elif mode_str == 'stateful':
383 return IPv6Mode.stateful
384 elif mode_str == 'dhcpv6-stateless':
385 return IPv6Mode.stateless
386 elif mode_str == 'stateless':
387 return IPv6Mode.stateless
389 raise SubnetSettingsError('Invalid mode - ' + mode_str)
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 security_groups: One or more security group IDs.
426 :param allowed_address_pairs: A dictionary containing a set of zero or
427 more allowed address pairs. An address
428 pair contains an IP address and MAC
430 :param opt_value: The extra DHCP option value (optional)
431 :param opt_name: The extra DHCP option name (optional)
432 :param device_owner: The ID of the entity that uses this port.
433 For example, a DHCP agent (optional)
434 :param device_id: The ID of the device that uses this port.
435 For example, a virtual server (optional)
439 kwargs = kwargs['port']
441 self.name = kwargs.get('name')
442 self.network_name = kwargs.get('network_name')
444 if kwargs.get('admin_state_up') is not None:
445 self.admin_state_up = bool(kwargs['admin_state_up'])
447 self.admin_state_up = True
449 self.project_name = kwargs.get('project_name')
450 self.mac_address = kwargs.get('mac_address')
451 self.ip_addrs = kwargs.get('ip_addrs')
452 self.security_groups = kwargs.get('security_groups')
453 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
454 self.opt_value = kwargs.get('opt_value')
455 self.opt_name = kwargs.get('opt_name')
456 self.device_owner = kwargs.get('device_owner')
457 self.device_id = kwargs.get('device_id')
459 if not self.network_name:
460 raise PortSettingsError(
461 'The attribute network_name is required')
463 def __get_fixed_ips(self, neutron):
465 Sets the self.fixed_ips value
466 :param neutron: the Neutron client
473 for ip_addr_dict in self.ip_addrs:
474 subnet = neutron_utils.get_subnet(
475 neutron, subnet_name=ip_addr_dict['subnet_name'])
476 if subnet and 'ip' in ip_addr_dict:
477 fixed_ips.append({'ip_address': ip_addr_dict['ip'],
478 'subnet_id': subnet.id})
480 raise PortSettingsError(
481 'Invalid port configuration, subnet does not exist '
482 'with name - ' + ip_addr_dict['subnet_name'])
486 def dict_for_neutron(self, neutron, os_creds):
488 Returns a dictionary object representing this object.
489 This is meant to be converted into JSON designed for use by the Neutron
492 TODO - expand automated testing to exercise all parameters
493 :param neutron: the Neutron client
494 :param os_creds: the OpenStack credentials
495 :return: the dictionary object
501 if self.project_name:
502 keystone = keystone_utils.keystone_client(os_creds)
503 project = keystone_utils.get_project(
504 keystone=keystone, project_name=self.project_name)
506 project_id = project.id
508 network = neutron_utils.get_network(
509 neutron, network_name=self.network_name, project_id=project_id)
511 raise PortSettingsError(
512 'Cannot locate network with name - ' + self.network_name)
514 out['network_id'] = network.id
516 if self.admin_state_up is not None:
517 out['admin_state_up'] = self.admin_state_up
519 out['name'] = self.name
520 if self.project_name:
522 out['tenant_id'] = project_id
524 raise PortSettingsError(
525 'Could not find project ID for project named - ' +
528 out['mac_address'] = self.mac_address
530 fixed_ips = self.__get_fixed_ips(neutron)
531 if fixed_ips and len(fixed_ips) > 0:
532 out['fixed_ips'] = fixed_ips
534 if self.security_groups:
535 out['security_groups'] = self.security_groups
536 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
537 out['allowed_address_pairs'] = self.allowed_address_pairs
539 out['opt_value'] = self.opt_value
541 out['opt_name'] = self.opt_name
542 if self.device_owner:
543 out['device_owner'] = self.device_owner
545 out['device_id'] = self.device_id
548 def __eq__(self, other):
549 return (self.name == other.name and
550 self.network_name == other.network_name and
551 self.admin_state_up == other.admin_state_up and
552 self.project_name == other.project_name and
553 self.mac_address == other.mac_address and
554 self.ip_addrs == other.ip_addrs and
555 # self.fixed_ips == other.fixed_ips and
556 self.security_groups == other.security_groups and
557 self.allowed_address_pairs == other.allowed_address_pairs and
558 self.opt_value == other.opt_value and
559 self.opt_name == other.opt_name and
560 self.device_owner == other.device_owner and
561 self.device_id == other.device_id)
564 class PortSettingsError(Exception):
566 Exception to be thrown when port settings attributes are incorrect