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 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(
467 neutron, subnet_name=ip_addr_dict['subnet_name'])
469 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
470 'subnet_id': subnet.id})
472 raise PortSettingsError(
473 'Invalid port configuration, subnet does not exist '
474 'with name - ' + ip_addr_dict['subnet_name'])
476 def dict_for_neutron(self, neutron, os_creds):
478 Returns a dictionary object representing this object.
479 This is meant to be converted into JSON designed for use by the Neutron
482 TODO - expand automated testing to exercise all parameters
483 :param neutron: the Neutron client
484 :param os_creds: the OpenStack credentials
485 :return: the dictionary object
487 self.__set_fixed_ips(neutron)
492 if self.project_name:
493 keystone = keystone_utils.keystone_client(os_creds)
494 project = keystone_utils.get_project(
495 keystone=keystone, project_name=self.project_name)
497 project_id = project.id
500 self.network = neutron_utils.get_network(
501 neutron, network_name=self.network_name, project_id=project_id)
503 raise PortSettingsError(
504 'Cannot locate network with name - ' + self.network_name)
506 out['network_id'] = self.network.id
508 if self.admin_state_up is not None:
509 out['admin_state_up'] = self.admin_state_up
511 out['name'] = self.name
512 if self.project_name:
514 out['tenant_id'] = project_id
516 raise PortSettingsError(
517 'Could not find project ID for project named - ' +
520 out['mac_address'] = self.mac_address
521 if self.fixed_ips and len(self.fixed_ips) > 0:
522 out['fixed_ips'] = self.fixed_ips
523 if self.security_groups:
524 out['security_groups'] = self.security_groups
525 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
526 out['allowed_address_pairs'] = self.allowed_address_pairs
528 out['opt_value'] = self.opt_value
530 out['opt_name'] = self.opt_name
531 if self.device_owner:
532 out['device_owner'] = self.device_owner
534 out['device_id'] = self.device_id
537 def __eq__(self, other):
538 return (self.name == other.name and
539 self.network_name == other.network_name and
540 self.admin_state_up == other.admin_state_up and
541 self.project_name == other.project_name and
542 self.mac_address == other.mac_address and
543 self.ip_addrs == other.ip_addrs and
544 self.fixed_ips == other.fixed_ips and
545 self.security_groups == other.security_groups and
546 self.allowed_address_pairs == other.allowed_address_pairs and
547 self.opt_value == other.opt_value and
548 self.opt_name == other.opt_name and
549 self.device_owner == other.device_owner and
550 self.device_id == other.device_id)
553 class PortSettingsError(Exception):
555 Exception to be thrown when port settings attributes are incorrect