1 # Copyright (c) 2016 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.utils import keystone_utils, neutron_utils
21 __author__ = 'spisarski'
23 logger = logging.getLogger('OpenStackNetwork')
26 class OpenStackNetwork:
28 Class responsible for creating a network in OpenStack
31 def __init__(self, os_creds, network_settings):
33 Constructor - all parameters are required
34 :param os_creds: The credentials to connect with OpenStack
35 :param network_settings: The settings used to create a network
37 self.__os_creds = os_creds
38 self.network_settings = network_settings
39 self.__neutron = neutron_utils.neutron_client(self.__os_creds)
41 # Attributes instantiated on create()
43 self.__subnets = list()
45 def create(self, cleanup=False):
47 Responsible for creating not only the network but then a private 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
52 logger.info('Creating neutron network %s...' % self.network_settings.name)
53 net_inst = neutron_utils.get_network(self.__neutron, self.network_settings.name,
54 self.network_settings.get_project_id(self.__os_creds))
56 self.__network = net_inst
59 self.__network = neutron_utils.create_network(self.__neutron, self.__os_creds,
60 self.network_settings)
62 logger.info('Network does not exist and will not create as in cleanup mode')
64 logger.debug("Network '%s' created successfully" % self.__network['network']['id'])
66 logger.debug('Creating Subnets....')
67 for subnet_setting in self.network_settings.subnet_settings:
68 sub_inst = neutron_utils.get_subnet_by_name(self.__neutron, subnet_setting.name)
70 self.__subnets.append(sub_inst)
71 logger.debug("Subnet '%s' created successfully" % sub_inst['subnet']['id'])
74 self.__subnets.append(neutron_utils.create_subnet(self.__neutron, subnet_setting,
75 self.__os_creds, self.__network))
78 except Exception as e:
79 logger.error('Unexpected exception thrown while creating network - ' + str(e))
85 Removes and deletes all items created in reverse order.
87 for subnet in self.__subnets:
89 logger.info('Deleting subnet with name ' + subnet['subnet']['name'])
90 neutron_utils.delete_subnet(self.__neutron, subnet)
92 logger.warn('Error deleting subnet with message - ' + e.message)
94 self.__subnets = list()
98 neutron_utils.delete_network(self.__neutron, self.__network)
102 self.__network = None
104 def get_network(self):
106 Returns the created OpenStack network object
107 :return: the OpenStack network object
109 return self.__network
111 def get_subnets(self):
113 Returns the OpenStack subnet objects
116 return self.__subnets
119 class NetworkSettings:
121 Class representing a network configuration
124 def __init__(self, config=None, name=None, admin_state_up=True, shared=None, project_name=None,
125 external=False, network_type=None, physical_network=None, subnet_settings=list()):
127 Constructor - all parameters are optional
128 :param config: Should be a dict object containing the configuration settings using the attribute names below
129 as each member's the key and overrides any of the other parameters.
130 :param name: The network name.
131 :param admin_state_up: The administrative status of the network. True = up / False = down (default True)
132 :param shared: Boolean value indicating whether this network is shared across all projects/tenants. By default,
133 only administrative users can change this value.
134 :param project_name: Admin-only. The name of the project that will own the network. This project can be
135 different from the project that makes the create network request. However, only
136 administrative users can specify a project ID other than their own. You cannot change this
137 value through authorization policies.
138 :param external: when true, will setup an external network (default False).
139 :param network_type: the type of network (i.e. vlan|flat).
140 :param physical_network: the name of the physical network (this is required when network_type is 'flat')
141 :param subnet_settings: List of SubnetSettings objects.
145 self.project_id = None
148 self.name = config.get('name')
149 if config.get('admin_state_up') is not None:
150 self.admin_state_up = bool(config['admin_state_up'])
152 self.admin_state_up = admin_state_up
154 if config.get('shared') is not None:
155 self.shared = bool(config['shared'])
159 self.project_name = config.get('project_name')
161 if config.get('external') is not None:
162 self.external = bool(config.get('external'))
164 self.external = external
166 self.network_type = config.get('network_type')
167 self.physical_network = config.get('physical_network')
169 self.subnet_settings = list()
170 if config.get('subnets'):
171 for subnet_config in config['subnets']:
172 self.subnet_settings.append(SubnetSettings(config=subnet_config['subnet']))
176 self.admin_state_up = admin_state_up
178 self.project_name = project_name
179 self.external = external
180 self.network_type = network_type
181 self.physical_network = physical_network
182 self.subnet_settings = subnet_settings
184 if not self.name or len(self.name) < 1:
185 raise Exception('Name required for networks')
187 def get_project_id(self, os_creds):
189 Returns the project ID for a given project_name or None
190 :param os_creds: the credentials required for keystone client retrieval
191 :return: the ID or None
194 return self.project_id
196 if self.project_name:
197 keystone = keystone_utils.keystone_client(os_creds)
198 project = keystone_utils.get_project(keystone, self.project_name)
204 def dict_for_neutron(self, os_creds):
206 Returns a dictionary object representing this object.
207 This is meant to be converted into JSON designed for use by the Neutron API
209 TODO - expand automated testing to exercise all parameters
211 :param os_creds: the OpenStack credentials
212 :return: the dictionary object
217 out['name'] = self.name
218 if self.admin_state_up is not None:
219 out['admin_state_up'] = self.admin_state_up
221 out['shared'] = self.shared
222 if self.project_name:
223 project_id = self.get_project_id(os_creds)
225 out['project_id'] = project_id
227 raise Exception('Could not find project ID for project named - ' + self.project_name)
228 if self.network_type:
229 out['provider:network_type'] = self.network_type
230 if self.physical_network:
231 out['provider:physical_network'] = self.physical_network
233 out['router:external'] = self.external
234 return {'network': out}
237 class SubnetSettings:
239 Class representing a subnet configuration
242 def __init__(self, config=None, cidr=None, ip_version=4, name=None, project_name=None, start=None,
243 end=None, gateway_ip=None, enable_dhcp=None, dns_nameservers=None, host_routes=None, destination=None,
244 nexthop=None, ipv6_ra_mode=None, ipv6_address_mode=None):
246 Constructor - all parameters are optional except cidr (subnet mask)
247 :param config: Should be a dict object containing the configuration settings using the attribute names below
248 as each member's the key and overrides any of the other parameters.
249 :param cidr: The CIDR. REQUIRED if config parameter is None
250 :param ip_version: The IP version, which is 4 or 6.
251 :param name: The subnet name.
252 :param project_name: The name of the project who owns the network. Only administrative users can specify a
253 project ID other than their own. You cannot change this value through authorization
255 :param start: The start address for the allocation pools.
256 :param end: The end address for the allocation pools.
257 :param gateway_ip: The gateway IP address.
258 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is disabled.
259 :param dns_nameservers: A list of DNS name servers for the subnet. Specify each name server as an IP address
260 and separate multiple entries with a space. For example [8.8.8.7 8.8.8.8].
261 :param host_routes: A list of host route dictionaries for the subnet. For example:
264 "destination":"0.0.0.0/0",
265 "nexthop":"123.456.78.9"
268 "destination":"192.168.0.0/24",
269 "nexthop":"192.168.0.1"
272 :param destination: The destination for static route
273 :param nexthop: The next hop for the destination.
274 :param ipv6_ra_mode: A valid value is dhcpv6-stateful, dhcpv6-stateless, or slaac.
275 :param ipv6_address_mode: A valid value is dhcpv6-stateful, dhcpv6-stateless, or slaac.
276 :raise: Exception when config does not have or cidr values are None
278 if not dns_nameservers:
279 dns_nameservers = ['8.8.8.8']
282 self.cidr = config['cidr']
283 if config.get('ip_version'):
284 self.ip_version = config['ip_version']
286 self.ip_version = ip_version
288 # Optional attributes that can be set after instantiation
289 self.name = config.get('name')
290 self.project_name = config.get('project_name')
291 self.start = config.get('start')
292 self.end = config.get('end')
293 self.gateway_ip = config.get('gateway_ip')
294 self.enable_dhcp = config.get('enable_dhcp')
296 if config.get('dns_nameservers'):
297 self.dns_nameservers = config.get('dns_nameservers')
299 self.dns_nameservers = dns_nameservers
301 self.host_routes = config.get('host_routes')
302 self.destination = config.get('destination')
303 self.nexthop = config.get('nexthop')
304 self.ipv6_ra_mode = config.get('ipv6_ra_mode')
305 self.ipv6_address_mode = config.get('ipv6_address_mode')
307 # Required attributes
309 self.ip_version = ip_version
311 # Optional attributes that can be set after instantiation
313 self.project_name = project_name
316 self.gateway_ip = gateway_ip
317 self.enable_dhcp = enable_dhcp
318 self.dns_nameservers = dns_nameservers
319 self.host_routes = host_routes
320 self.destination = destination
321 self.nexthop = nexthop
322 self.ipv6_ra_mode = ipv6_ra_mode
323 self.ipv6_address_mode = ipv6_address_mode
325 if not self.name or not self.cidr:
326 raise Exception('Name and cidr required for subnets')
328 def dict_for_neutron(self, os_creds, network=None):
330 Returns a dictionary object representing this object.
331 This is meant to be converted into JSON designed for use by the Neutron API
332 :param os_creds: the OpenStack credentials
333 :param network: (Optional) 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['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(keystone, self.project_name)
350 project_id = project.id
352 out['project_id'] = project_id
354 raise Exception('Could not find project ID for project named - ' + self.project_name)
355 if self.start and self.end:
356 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
358 out['gateway_ip'] = self.gateway_ip
359 if self.enable_dhcp is not None:
360 out['enable_dhcp'] = self.enable_dhcp
361 if self.dns_nameservers and len(self.dns_nameservers) > 0:
362 out['dns_nameservers'] = self.dns_nameservers
363 if self.host_routes and len(self.host_routes) > 0:
364 out['host_routes'] = self.host_routes
366 out['destination'] = self.destination
368 out['nexthop'] = self.nexthop
369 if self.ipv6_ra_mode:
370 out['ipv6_ra_mode'] = self.ipv6_ra_mode
371 if self.ipv6_address_mode:
372 out['ipv6_address_mode'] = self.ipv6_address_mode
378 Class representing a port configuration
381 def __init__(self, config=None, name=None, network_name=None, admin_state_up=True, project_name=None,
382 mac_address=None, ip_addrs=None, fixed_ips=None, security_groups=None, allowed_address_pairs=None,
383 opt_value=None, opt_name=None, device_owner=None, device_id=None):
385 Constructor - all parameters are optional
386 :param config: Should be a dict object containing the configuration settings using the attribute names below
387 as each member's the key and overrides any of the other parameters.
388 :param name: A symbolic name for the port.
389 :param network_name: The name of the network on which to create the port.
390 :param admin_state_up: A boolean value denoting the administrative status of the port. True = up / False = down
391 :param project_name: The name of the project who owns the network. Only administrative users can specify a
392 project ID other than their own. You cannot change this value through authorization
394 :param mac_address: The MAC address. If you specify an address that is not valid, a Bad Request (400) status
395 code is returned. If you do not specify a MAC address, OpenStack Networking tries to
396 allocate one. If a failure occurs, a Service Unavailable (503) status code is returned.
397 :param ip_addrs: A list of dict objects where each contains two keys 'subnet_name' and 'ip' values which will
398 get mapped to self.fixed_ips.
399 These values will be directly translated into the fixed_ips dict
400 :param fixed_ips: A dict where the key is the subnet IDs and value is the IP address to assign to the port
401 :param security_groups: One or more security group IDs.
402 :param allowed_address_pairs: A dictionary containing a set of zero or more allowed address pairs. An address
403 pair contains an IP address and MAC address.
404 :param opt_value: The extra DHCP option value.
405 :param opt_name: The extra DHCP option name.
406 :param device_owner: The ID of the entity that uses this port. For example, a DHCP agent.
407 :param device_id: The ID of the device that uses this port. For example, a virtual server.
413 self.name = config.get('name')
414 self.network_name = config.get('network_name')
416 if config.get('admin_state_up') is not None:
417 self.admin_state_up = bool(config['admin_state_up'])
419 self.admin_state_up = admin_state_up
421 self.project_name = config.get('project_name')
422 self.mac_address = config.get('mac_address')
423 self.ip_addrs = config.get('ip_addrs')
424 self.fixed_ips = config.get('fixed_ips')
425 self.security_groups = config.get('security_groups')
426 self.allowed_address_pairs = config.get('allowed_address_pairs')
427 self.opt_value = config.get('opt_value')
428 self.opt_name = config.get('opt_name')
429 self.device_owner = config.get('device_owner')
430 self.device_id = config.get('device_id')
433 self.network_name = network_name
434 self.admin_state_up = admin_state_up
435 self.project_name = project_name
436 self.mac_address = mac_address
437 self.ip_addrs = ip_addrs
438 self.fixed_ips = fixed_ips
439 self.security_groups = security_groups
440 self.allowed_address_pairs = allowed_address_pairs
441 self.opt_value = opt_value
442 self.opt_name = opt_name
443 self.device_owner = device_owner
444 self.device_id = device_id
446 if not self.name or not self.network_name:
447 raise Exception('The attributes neutron, name, and network_name are required for PortSettings')
449 def __set_fixed_ips(self, neutron):
451 Sets the self.fixed_ips value
452 :param neutron: the Neutron client
455 if not self.fixed_ips and self.ip_addrs:
456 self.fixed_ips = list()
458 for ip_addr_dict in self.ip_addrs:
459 subnet = neutron_utils.get_subnet_by_name(neutron, ip_addr_dict['subnet_name'])
461 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'], 'subnet_id': subnet['subnet']['id']})
463 raise Exception('Invalid port configuration, subnet does not exist with name - ' +
464 ip_addr_dict['subnet_name'])
466 def dict_for_neutron(self, neutron, os_creds):
468 Returns a dictionary object representing this object.
469 This is meant to be converted into JSON designed for use by the Neutron API
471 TODO - expand automated testing to exercise all parameters
472 :param neutron: the Neutron client
473 :param os_creds: the OpenStack credentials
474 :return: the dictionary object
476 self.__set_fixed_ips(neutron)
481 if self.project_name:
482 keystone = keystone_utils.keystone_client(os_creds)
483 project = keystone_utils.get_project(keystone, self.project_name)
485 project_id = project.id
488 self.network = neutron_utils.get_network(neutron, self.network_name, project_id)
490 raise Exception('Cannot locate network with name - ' + self.network_name)
492 out['network_id'] = self.network['network']['id']
494 if self.admin_state_up is not None:
495 out['admin_state_up'] = self.admin_state_up
497 out['name'] = self.name
498 if self.project_name:
500 out['project_id'] = project_id
502 raise Exception('Could not find project ID for project named - ' + self.project_name)
504 out['mac_address'] = self.mac_address
505 if self.fixed_ips and len(self.fixed_ips) > 0:
506 out['fixed_ips'] = self.fixed_ips
507 if self.security_groups:
508 out['security_groups'] = self.security_groups
509 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
510 out['allowed_address_pairs'] = self.allowed_address_pairs
512 out['opt_value'] = self.opt_value
514 out['opt_name'] = self.opt_name
515 if self.device_owner:
516 out['device_owner'] = self.device_owner
518 out['device_id'] = self.device_id