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.id)
72 logger.debug('Creating Subnets....')
73 for subnet_setting in self.network_settings.subnet_settings:
74 sub_inst = neutron_utils.get_subnet_by_name(
75 self.__neutron, subnet_setting.name)
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 subnets or subnet_settings: List of SubnetSettings objects.
156 self.project_id = None
158 self.name = kwargs.get('name')
159 if kwargs.get('admin_state_up') is not None:
160 self.admin_state_up = bool(kwargs['admin_state_up'])
162 self.admin_state_up = True
164 if kwargs.get('shared') is not None:
165 self.shared = bool(kwargs['shared'])
169 self.project_name = kwargs.get('project_name')
171 if kwargs.get('external') is not None:
172 self.external = bool(kwargs.get('external'))
174 self.external = False
176 self.network_type = kwargs.get('network_type')
177 self.physical_network = kwargs.get('physical_network')
179 self.subnet_settings = list()
180 subnet_settings = kwargs.get('subnets')
181 if not subnet_settings:
182 subnet_settings = kwargs.get('subnet_settings')
184 for subnet_config in subnet_settings:
185 if isinstance(subnet_config, SubnetSettings):
186 self.subnet_settings.append(subnet_config)
188 self.subnet_settings.append(
189 SubnetSettings(**subnet_config['subnet']))
191 if not self.name or len(self.name) < 1:
192 raise NetworkSettingsError('Name required for networks')
194 def get_project_id(self, os_creds):
196 Returns the project ID for a given project_name or None
197 :param os_creds: the credentials required for keystone client retrieval
198 :return: the ID or None
201 return self.project_id
203 if self.project_name:
204 keystone = keystone_utils.keystone_client(os_creds)
205 project = keystone_utils.get_project(
206 keystone=keystone, project_name=self.project_name)
212 def dict_for_neutron(self, os_creds):
214 Returns a dictionary object representing this object.
215 This is meant to be converted into JSON designed for use by the Neutron
217 TODO - expand automated testing to exercise all parameters
219 :param os_creds: the OpenStack credentials
220 :return: the dictionary object
225 out['name'] = self.name
226 if self.admin_state_up is not None:
227 out['admin_state_up'] = self.admin_state_up
229 out['shared'] = self.shared
230 if self.project_name:
231 project_id = self.get_project_id(os_creds)
233 out['tenant_id'] = project_id
235 raise NetworkSettingsError(
236 'Could not find project ID for project named - ' +
238 if self.network_type:
239 out['provider:network_type'] = self.network_type
240 if self.physical_network:
241 out['provider:physical_network'] = self.physical_network
243 out['router:external'] = self.external
244 return {'network': out}
247 class NetworkSettingsError(Exception):
249 Exception to be thrown when networks settings attributes are incorrect
253 class SubnetSettings:
255 Class representing a subnet configuration
258 def __init__(self, **kwargs):
260 Constructor - all parameters are optional except cidr (subnet mask)
261 :param cidr: The CIDR. REQUIRED if config parameter is None
262 :param ip_version: The IP version, which is 4 or 6.
263 :param name: The subnet name.
264 :param project_name: The name of the project who owns the network.
265 Only administrative users can specify a project ID
266 other than their own. You cannot change this value
267 through authorization policies.
268 :param start: The start address for the allocation pools.
269 :param end: The end address for the allocation pools.
270 :param gateway_ip: The gateway IP address.
271 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
273 :param dns_nameservers: A list of DNS name servers for the subnet.
274 Specify each name server as an IP address
275 and separate multiple entries with a space.
276 For example [8.8.8.7 8.8.8.8].
277 :param host_routes: A list of host route dictionaries for the subnet.
281 "destination":"0.0.0.0/0",
282 "nexthop":"123.456.78.9"
285 "destination":"192.168.0.0/24",
286 "nexthop":"192.168.0.1"
289 :param destination: The destination for static route
290 :param nexthop: The next hop for the destination.
291 :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
292 dhcpv6-stateless, or slaac.
293 :param ipv6_address_mode: A valid value is dhcpv6-stateful,
294 dhcpv6-stateless, or slaac.
295 :raise: SubnetSettingsError when config does not have or cidr values
298 self.cidr = kwargs.get('cidr')
299 if kwargs.get('ip_version'):
300 self.ip_version = kwargs['ip_version']
304 # Optional attributes that can be set after instantiation
305 self.name = kwargs.get('name')
306 self.project_name = kwargs.get('project_name')
307 self.start = kwargs.get('start')
308 self.end = kwargs.get('end')
309 self.gateway_ip = kwargs.get('gateway_ip')
310 self.enable_dhcp = kwargs.get('enable_dhcp')
312 if kwargs.get('dns_nameservers'):
313 self.dns_nameservers = kwargs.get('dns_nameservers')
315 self.dns_nameservers = ['8.8.8.8']
317 self.host_routes = kwargs.get('host_routes')
318 self.destination = kwargs.get('destination')
319 self.nexthop = kwargs.get('nexthop')
320 self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
321 self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
323 if not self.name or not self.cidr:
324 raise SubnetSettingsError('Name and cidr required for subnets')
326 def dict_for_neutron(self, os_creds, network=None):
328 Returns a dictionary object representing this object.
329 This is meant to be converted into JSON designed for use by the Neutron
331 :param os_creds: the OpenStack credentials
332 :param network: The network object on which the subnet will be created
334 :return: the dictionary object
338 'ip_version': self.ip_version,
342 out['network_id'] = network.id
344 out['name'] = self.name
345 if self.project_name:
346 keystone = keystone_utils.keystone_client(os_creds)
347 project = keystone_utils.get_project(
348 keystone=keystone, project_name=self.project_name)
351 project_id = project.id
353 out['tenant_id'] = project_id
355 raise SubnetSettingsError(
356 'Could not find project ID for project named - ' +
358 if self.start and self.end:
359 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
361 out['gateway_ip'] = self.gateway_ip
362 if self.enable_dhcp is not None:
363 out['enable_dhcp'] = self.enable_dhcp
364 if self.dns_nameservers and len(self.dns_nameservers) > 0:
365 out['dns_nameservers'] = self.dns_nameservers
366 if self.host_routes and len(self.host_routes) > 0:
367 out['host_routes'] = self.host_routes
369 out['destination'] = self.destination
371 out['nexthop'] = self.nexthop
372 if self.ipv6_ra_mode:
373 out['ipv6_ra_mode'] = self.ipv6_ra_mode
374 if self.ipv6_address_mode:
375 out['ipv6_address_mode'] = self.ipv6_address_mode
379 class SubnetSettingsError(Exception):
381 Exception to be thrown when subnet settings attributes are incorrect
387 Class representing a port configuration
390 def __init__(self, **kwargs):
392 Constructor - all parameters are optional
393 :param name: A symbolic name for the port.
394 :param network_name: The name of the network on which to create the
396 :param admin_state_up: A boolean value denoting the administrative
397 status of the port. True = up / False = down
398 :param project_name: The name of the project who owns the network.
399 Only administrative users can specify a project ID
400 other than their own. You cannot change this value
401 through authorization policies.
402 :param mac_address: The MAC address. If you specify an address that is
403 not valid, a Bad Request (400) status code is
404 returned. If you do not specify a MAC address,
405 OpenStack Networking tries to allocate one. If a
406 failure occurs, a Service Unavailable (503) status
408 :param ip_addrs: A list of dict objects where each contains two keys
409 'subnet_name' and 'ip' values which will get mapped to
410 self.fixed_ips. These values will be directly
411 translated into the fixed_ips dict
412 :param fixed_ips: A dict where the key is the subnet IDs and value is
413 the IP address to assign to the port
414 :param security_groups: One or more security group IDs.
415 :param allowed_address_pairs: A dictionary containing a set of zero or
416 more allowed address pairs. An address
417 pair contains an IP address and MAC
419 :param opt_value: The extra DHCP option value.
420 :param opt_name: The extra DHCP option name.
421 :param device_owner: The ID of the entity that uses this port.
422 For example, a DHCP agent.
423 :param device_id: The ID of the device that uses this port.
424 For example, a virtual server.
428 kwargs = kwargs['port']
432 self.name = kwargs.get('name')
433 self.network_name = kwargs.get('network_name')
435 if kwargs.get('admin_state_up') is not None:
436 self.admin_state_up = bool(kwargs['admin_state_up'])
438 self.admin_state_up = True
440 self.project_name = kwargs.get('project_name')
441 self.mac_address = kwargs.get('mac_address')
442 self.ip_addrs = kwargs.get('ip_addrs')
443 self.fixed_ips = kwargs.get('fixed_ips')
444 self.security_groups = kwargs.get('security_groups')
445 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
446 self.opt_value = kwargs.get('opt_value')
447 self.opt_name = kwargs.get('opt_name')
448 self.device_owner = kwargs.get('device_owner')
449 self.device_id = kwargs.get('device_id')
451 if not self.name or not self.network_name:
452 raise PortSettingsError(
453 'The attributes neutron, name, and network_name are required '
456 def __set_fixed_ips(self, neutron):
458 Sets the self.fixed_ips value
459 :param neutron: the Neutron client
462 if not self.fixed_ips and self.ip_addrs:
463 self.fixed_ips = list()
465 for ip_addr_dict in self.ip_addrs:
466 subnet = neutron_utils.get_subnet_by_name(neutron,
470 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
471 'subnet_id': subnet.id})
473 raise PortSettingsError(
474 'Invalid port configuration, subnet does not exist '
475 'with name - ' + ip_addr_dict['subnet_name'])
477 def dict_for_neutron(self, neutron, os_creds):
479 Returns a dictionary object representing this object.
480 This is meant to be converted into JSON designed for use by the Neutron
483 TODO - expand automated testing to exercise all parameters
484 :param neutron: the Neutron client
485 :param os_creds: the OpenStack credentials
486 :return: the dictionary object
488 self.__set_fixed_ips(neutron)
493 if self.project_name:
494 keystone = keystone_utils.keystone_client(os_creds)
495 project = keystone_utils.get_project(
496 keystone=keystone, project_name=self.project_name)
498 project_id = project.id
501 self.network = neutron_utils.get_network(neutron,
505 raise PortSettingsError(
506 'Cannot locate network with name - ' + self.network_name)
508 out['network_id'] = self.network.id
510 if self.admin_state_up is not None:
511 out['admin_state_up'] = self.admin_state_up
513 out['name'] = self.name
514 if self.project_name:
516 out['tenant_id'] = project_id
518 raise PortSettingsError(
519 'Could not find project ID for project named - ' +
522 out['mac_address'] = self.mac_address
523 if self.fixed_ips and len(self.fixed_ips) > 0:
524 out['fixed_ips'] = self.fixed_ips
525 if self.security_groups:
526 out['security_groups'] = self.security_groups
527 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
528 out['allowed_address_pairs'] = self.allowed_address_pairs
530 out['opt_value'] = self.opt_value
532 out['opt_name'] = self.opt_name
533 if self.device_owner:
534 out['device_owner'] = self.device_owner
536 out['device_id'] = self.device_id
539 def __eq__(self, other):
540 return (self.name == other.name and
541 self.network_name == other.network_name and
542 self.admin_state_up == other.admin_state_up and
543 self.project_name == other.project_name and
544 self.mac_address == other.mac_address and
545 self.ip_addrs == other.ip_addrs and
546 self.fixed_ips == other.fixed_ips and
547 self.security_groups == other.security_groups and
548 self.allowed_address_pairs == other.allowed_address_pairs and
549 self.opt_value == other.opt_value and
550 self.opt_name == other.opt_name and
551 self.device_owner == other.device_owner and
552 self.device_id == other.device_id)
555 class PortSettingsError(Exception):
557 Exception to be thrown when port settings attributes are incorrect