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 Exception('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(keystone,
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['project_id'] = project_id
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 SubnetSettings:
249 Class representing a subnet configuration
252 def __init__(self, **kwargs):
254 Constructor - all parameters are optional except cidr (subnet mask)
255 :param cidr: The CIDR. REQUIRED if config parameter is None
256 :param ip_version: The IP version, which is 4 or 6.
257 :param name: The subnet name.
258 :param project_name: The name of the project who owns the network.
259 Only administrative users can specify a project ID
260 other than their own. You cannot change this value
261 through authorization policies.
262 :param start: The start address for the allocation pools.
263 :param end: The end address for the allocation pools.
264 :param gateway_ip: The gateway IP address.
265 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
267 :param dns_nameservers: A list of DNS name servers for the subnet.
268 Specify each name server as an IP address
269 and separate multiple entries with a space.
270 For example [8.8.8.7 8.8.8.8].
271 :param host_routes: A list of host route dictionaries for the subnet.
275 "destination":"0.0.0.0/0",
276 "nexthop":"123.456.78.9"
279 "destination":"192.168.0.0/24",
280 "nexthop":"192.168.0.1"
283 :param destination: The destination for static route
284 :param nexthop: The next hop for the destination.
285 :param ipv6_ra_mode: A valid value is dhcpv6-stateful,
286 dhcpv6-stateless, or slaac.
287 :param ipv6_address_mode: A valid value is dhcpv6-stateful,
288 dhcpv6-stateless, or slaac.
289 :raise: Exception when config does not have or cidr values are None
291 self.cidr = kwargs['cidr']
292 if kwargs.get('ip_version'):
293 self.ip_version = kwargs['ip_version']
297 # Optional attributes that can be set after instantiation
298 self.name = kwargs.get('name')
299 self.project_name = kwargs.get('project_name')
300 self.start = kwargs.get('start')
301 self.end = kwargs.get('end')
302 self.gateway_ip = kwargs.get('gateway_ip')
303 self.enable_dhcp = kwargs.get('enable_dhcp')
305 if kwargs.get('dns_nameservers'):
306 self.dns_nameservers = kwargs.get('dns_nameservers')
308 self.dns_nameservers = ['8.8.8.8']
310 self.host_routes = kwargs.get('host_routes')
311 self.destination = kwargs.get('destination')
312 self.nexthop = kwargs.get('nexthop')
313 self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode')
314 self.ipv6_address_mode = kwargs.get('ipv6_address_mode')
316 if not self.name or not self.cidr:
317 raise Exception('Name and cidr required for subnets')
319 def dict_for_neutron(self, os_creds, network=None):
321 Returns a dictionary object representing this object.
322 This is meant to be converted into JSON designed for use by the Neutron
324 :param os_creds: the OpenStack credentials
325 :param network: The network object on which the subnet will be created
327 :return: the dictionary object
331 'ip_version': self.ip_version,
335 out['network_id'] = network.id
337 out['name'] = self.name
338 if self.project_name:
339 keystone = keystone_utils.keystone_client(os_creds)
340 project = keystone_utils.get_project(keystone, self.project_name)
343 project_id = project.id
345 out['project_id'] = project_id
348 'Could not find project ID for project named - ' +
350 if self.start and self.end:
351 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
353 out['gateway_ip'] = self.gateway_ip
354 if self.enable_dhcp is not None:
355 out['enable_dhcp'] = self.enable_dhcp
356 if self.dns_nameservers and len(self.dns_nameservers) > 0:
357 out['dns_nameservers'] = self.dns_nameservers
358 if self.host_routes and len(self.host_routes) > 0:
359 out['host_routes'] = self.host_routes
361 out['destination'] = self.destination
363 out['nexthop'] = self.nexthop
364 if self.ipv6_ra_mode:
365 out['ipv6_ra_mode'] = self.ipv6_ra_mode
366 if self.ipv6_address_mode:
367 out['ipv6_address_mode'] = self.ipv6_address_mode
373 Class representing a port configuration
376 def __init__(self, **kwargs):
378 Constructor - all parameters are optional
379 :param name: A symbolic name for the port.
380 :param network_name: The name of the network on which to create the
382 :param admin_state_up: A boolean value denoting the administrative
383 status of the port. True = up / False = down
384 :param project_name: The name of the project who owns the network.
385 Only administrative users can specify a project ID
386 other than their own. You cannot change this value
387 through authorization policies.
388 :param mac_address: The MAC address. If you specify an address that is
389 not valid, a Bad Request (400) status code is
390 returned. If you do not specify a MAC address,
391 OpenStack Networking tries to allocate one. If a
392 failure occurs, a Service Unavailable (503) status
394 :param ip_addrs: A list of dict objects where each contains two keys
395 'subnet_name' and 'ip' values which will get mapped to
396 self.fixed_ips. These values will be directly
397 translated into the fixed_ips dict
398 :param fixed_ips: A dict where the key is the subnet IDs and value is
399 the IP address to assign to the port
400 :param security_groups: One or more security group IDs.
401 :param allowed_address_pairs: A dictionary containing a set of zero or
402 more allowed address pairs. An address
403 pair contains an IP address and MAC
405 :param opt_value: The extra DHCP option value.
406 :param opt_name: The extra DHCP option name.
407 :param device_owner: The ID of the entity that uses this port.
408 For example, a DHCP agent.
409 :param device_id: The ID of the device that uses this port.
410 For example, a virtual server.
414 kwargs = kwargs['port']
418 self.name = kwargs.get('name')
419 self.network_name = kwargs.get('network_name')
421 if kwargs.get('admin_state_up') is not None:
422 self.admin_state_up = bool(kwargs['admin_state_up'])
424 self.admin_state_up = True
426 self.project_name = kwargs.get('project_name')
427 self.mac_address = kwargs.get('mac_address')
428 self.ip_addrs = kwargs.get('ip_addrs')
429 self.fixed_ips = kwargs.get('fixed_ips')
430 self.security_groups = kwargs.get('security_groups')
431 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
432 self.opt_value = kwargs.get('opt_value')
433 self.opt_name = kwargs.get('opt_name')
434 self.device_owner = kwargs.get('device_owner')
435 self.device_id = kwargs.get('device_id')
437 if not self.name or not self.network_name:
439 'The attributes neutron, name, and network_name are required '
442 def __set_fixed_ips(self, neutron):
444 Sets the self.fixed_ips value
445 :param neutron: the Neutron client
448 if not self.fixed_ips and self.ip_addrs:
449 self.fixed_ips = list()
451 for ip_addr_dict in self.ip_addrs:
452 subnet = neutron_utils.get_subnet_by_name(neutron,
456 self.fixed_ips.append({'ip_address': ip_addr_dict['ip'],
457 'subnet_id': subnet.id})
460 'Invalid port configuration, subnet does not exist '
461 'with name - ' + ip_addr_dict['subnet_name'])
463 def dict_for_neutron(self, neutron, os_creds):
465 Returns a dictionary object representing this object.
466 This is meant to be converted into JSON designed for use by the Neutron
469 TODO - expand automated testing to exercise all parameters
470 :param neutron: the Neutron client
471 :param os_creds: the OpenStack credentials
472 :return: the dictionary object
474 self.__set_fixed_ips(neutron)
479 if self.project_name:
480 keystone = keystone_utils.keystone_client(os_creds)
481 project = keystone_utils.get_project(keystone, self.project_name)
483 project_id = project.id
486 self.network = neutron_utils.get_network(neutron,
491 'Cannot locate network with name - ' + self.network_name)
493 out['network_id'] = self.network.id
495 if self.admin_state_up is not None:
496 out['admin_state_up'] = self.admin_state_up
498 out['name'] = self.name
499 if self.project_name:
501 out['project_id'] = project_id
504 'Could not find project ID for project named - ' +
507 out['mac_address'] = self.mac_address
508 if self.fixed_ips and len(self.fixed_ips) > 0:
509 out['fixed_ips'] = self.fixed_ips
510 if self.security_groups:
511 out['security_groups'] = self.security_groups
512 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
513 out['allowed_address_pairs'] = self.allowed_address_pairs
515 out['opt_value'] = self.opt_value
517 out['opt_name'] = self.opt_name
518 if self.device_owner:
519 out['device_owner'] = self.device_owner
521 out['device_id'] = self.device_id
524 def __eq__(self, other):
525 return (self.name == other.name and
526 self.network_name == other.network_name and
527 self.admin_state_up == other.admin_state_up and
528 self.project_name == other.project_name and
529 self.mac_address == other.mac_address and
530 self.ip_addrs == other.ip_addrs and
531 self.fixed_ips == other.fixed_ips and
532 self.security_groups == other.security_groups and
533 self.allowed_address_pairs == other.allowed_address_pairs and
534 self.opt_value == other.opt_value and
535 self.opt_name == other.opt_name and
536 self.device_owner == other.device_owner and
537 self.device_id == other.device_id)