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.
16 from neutronclient.common.utils import str2bool
18 from snaps.openstack.utils import keystone_utils, neutron_utils
21 class NetworkConfig(object):
23 Class representing a network configuration
26 def __init__(self, **kwargs):
28 Constructor - all parameters are optional
29 :param name: The network name.
30 :param admin_state_up: The administrative status of the network.
31 True = up / False = down (default True)
32 :param shared: Boolean value indicating whether this network is shared
33 across all projects/tenants. By default, only
34 administrative users can change this value.
35 :param project_name: Admin-only. The name of the project that will own
36 the network. This project can be different from
37 the project that makes the create network request.
38 However, only administrative users can specify a
39 project ID other than their own. You cannot change
40 this value through authorization policies.
41 :param external: when true, will setup an external network
43 :param network_type: the type of network (i.e. vlan|flat).
44 :param physical_network: the name of the physical network
45 (required when network_type is 'flat')
46 :param segmentation_id: the id of the segmentation
47 (this is required when network_type is 'vlan')
48 :param subnets or subnet_settings: List of SubnetConfig objects.
52 self.project_id = None
54 self.name = kwargs.get('name')
55 if kwargs.get('admin_state_up') is not None:
56 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
58 self.admin_state_up = True
60 if kwargs.get('shared') is not None:
61 self.shared = str2bool(str(kwargs['shared']))
65 self.project_name = kwargs.get('project_name')
67 if kwargs.get('external') is not None:
68 self.external = str2bool(str(kwargs.get('external')))
72 self.network_type = kwargs.get('network_type')
73 self.physical_network = kwargs.get('physical_network')
74 self.segmentation_id = kwargs.get('segmentation_id')
76 self.subnet_settings = list()
77 subnet_settings = kwargs.get('subnets')
78 if not subnet_settings:
79 subnet_settings = kwargs.get('subnet_settings', list())
81 for subnet_config in subnet_settings:
82 if isinstance(subnet_config, SubnetConfig):
83 self.subnet_settings.append(subnet_config)
85 self.subnet_settings.append(
86 SubnetConfig(**subnet_config['subnet']))
88 if not self.name or len(self.name) < 1:
89 raise NetworkConfigError('Name required for networks')
91 def get_project_id(self, os_creds):
93 Returns the project ID for a given project_name or None
94 :param os_creds: the credentials required for keystone client retrieval
95 :return: the ID or None
98 return self.project_id
100 if self.project_name:
101 keystone = keystone_utils.keystone_client(os_creds)
102 project = keystone_utils.get_project(
103 keystone=keystone, project_name=self.project_name)
109 def dict_for_neutron(self, os_creds):
111 Returns a dictionary object representing this object.
112 This is meant to be converted into JSON designed for use by the Neutron
114 TODO - expand automated testing to exercise all parameters
116 :param os_creds: the OpenStack credentials
117 :return: the dictionary object
122 out['name'] = self.name
123 if self.admin_state_up is not None:
124 out['admin_state_up'] = self.admin_state_up
126 out['shared'] = self.shared
127 if self.project_name:
128 project_id = self.get_project_id(os_creds)
130 out['tenant_id'] = project_id
132 raise NetworkConfigError(
133 'Could not find project ID for project named - ' +
135 if self.network_type:
136 out['provider:network_type'] = self.network_type
137 if self.physical_network:
138 out['provider:physical_network'] = self.physical_network
139 if self.segmentation_id:
140 out['provider:segmentation_id'] = self.segmentation_id
142 out['router:external'] = self.external
143 return {'network': out}
146 class NetworkConfigError(Exception):
148 Exception to be thrown when networks settings attributes are incorrect
152 class IPv6Mode(enum.Enum):
157 stateful = 'dhcpv6-stateful'
158 stateless = 'dhcpv6-stateless'
161 class SubnetConfig(object):
163 Class representing a subnet configuration
166 def __init__(self, **kwargs):
168 Constructor - all parameters are optional except cidr (subnet mask)
169 :param name: The subnet name (required)
170 :param cidr: The CIDR (required)
171 :param ip_version: The IP version, which is 4 or 6 (required)
172 :param project_name: The name of the project who owns the network.
173 Only administrative users can specify a project ID
174 other than their own. You cannot change this value
175 through authorization policies (optional)
176 :param start: The start address for the allocation pools (optional)
177 :param end: The end address for the allocation pools (optional)
178 :param gateway_ip: The gateway IP address (optional)
179 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
181 :param dns_nameservers: A list of DNS name servers for the subnet.
182 Specify each name server as an IP address
183 and separate multiple entries with a space.
184 For example [8.8.8.7 8.8.8.8]
186 :param host_routes: A list of host route dictionaries for the subnet.
190 "destination":"0.0.0.0/0",
191 "nexthop":"123.456.78.9"
194 "destination":"192.168.0.0/24",
195 "nexthop":"192.168.0.1"
198 :param destination: The destination for static route (optional)
199 :param nexthop: The next hop for the destination (optional)
200 :param ipv6_ra_mode: an instance of the IPv6Mode enum
201 (optional when enable_dhcp is True)
202 :param ipv6_address_mode: an instance of the IPv6Mode enum
203 (optional when enable_dhcp is True)
204 :raise: SubnetConfigError when config does not have or cidr values
207 self.cidr = kwargs.get('cidr')
208 if kwargs.get('ip_version'):
209 self.ip_version = kwargs['ip_version']
213 # Optional attributes that can be set after instantiation
214 self.name = kwargs.get('name')
215 self.project_name = kwargs.get('project_name')
216 self.start = kwargs.get('start')
217 self.end = kwargs.get('end')
218 self.gateway_ip = kwargs.get('gateway_ip')
219 self.enable_dhcp = kwargs.get('enable_dhcp')
221 if 'dns_nameservers' in kwargs:
222 self.dns_nameservers = kwargs.get('dns_nameservers')
224 self.dns_nameservers = list()
226 self.host_routes = kwargs.get('host_routes')
227 self.destination = kwargs.get('destination')
228 self.nexthop = kwargs.get('nexthop')
229 self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
230 self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
232 if not self.name or not self.cidr:
233 raise SubnetConfigError('Name and cidr required for subnets')
235 def dict_for_neutron(self, os_creds, network=None):
237 Returns a dictionary object representing this object.
238 This is meant to be converted into JSON designed for use by the Neutron
240 :param os_creds: the OpenStack credentials
241 :param network: The network object on which the subnet will be created
243 :return: the dictionary object
247 'ip_version': self.ip_version,
251 out['network_id'] = network.id
253 out['name'] = self.name
254 if self.project_name:
255 keystone = keystone_utils.keystone_client(os_creds)
256 project = keystone_utils.get_project(
257 keystone=keystone, project_name=self.project_name)
260 project_id = project.id
262 out['tenant_id'] = project_id
264 raise SubnetConfigError(
265 'Could not find project ID for project named - ' +
267 if self.start and self.end:
268 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
270 out['gateway_ip'] = self.gateway_ip
271 if self.enable_dhcp is not None:
272 out['enable_dhcp'] = self.enable_dhcp
273 if self.dns_nameservers and len(self.dns_nameservers) > 0:
274 out['dns_nameservers'] = self.dns_nameservers
275 if self.host_routes and len(self.host_routes) > 0:
276 out['host_routes'] = self.host_routes
278 out['destination'] = self.destination
280 out['nexthop'] = self.nexthop
281 if self.ipv6_ra_mode:
282 out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
283 if self.ipv6_address_mode:
284 out['ipv6_address_mode'] = self.ipv6_address_mode.value
290 Takes a the direction value maps it to the Direction enum. When None return
292 :param mode: the mode value
293 :return: the IPv6Mode enum object
294 :raise: SubnetConfigError if value is invalid
298 if isinstance(mode, IPv6Mode):
300 elif isinstance(mode, str):
302 if mode_str == 'slaac':
303 return IPv6Mode.slaac
304 elif mode_str == 'dhcpv6-stateful':
305 return IPv6Mode.stateful
306 elif mode_str == 'stateful':
307 return IPv6Mode.stateful
308 elif mode_str == 'dhcpv6-stateless':
309 return IPv6Mode.stateless
310 elif mode_str == 'stateless':
311 return IPv6Mode.stateless
313 raise SubnetConfigError('Invalid mode - ' + mode_str)
315 return map_mode(mode.value)
318 class SubnetConfigError(Exception):
320 Exception to be thrown when subnet settings attributes are incorrect
324 class PortConfig(object):
326 Class representing a port configuration
329 def __init__(self, **kwargs):
332 :param name: A symbolic name for the port (optional).
333 :param network_name: The name of the network on which to create the
335 :param admin_state_up: A boolean value denoting the administrative
336 status of the port (default = True)
337 :param project_name: The name of the project who owns the network.
338 Only administrative users can specify a project ID
339 other than their own. You cannot change this value
340 through authorization policies (optional)
341 :param mac_address: The MAC address. If you specify an address that is
342 not valid, a Bad Request (400) status code is
343 returned. If you do not specify a MAC address,
344 OpenStack Networking tries to allocate one. If a
345 failure occurs, a Service Unavailable (503) status
346 code is returned (optional)
347 :param ip_addrs: A list of dict objects where each contains two keys
348 'subnet_name' and 'ip' values which will get mapped to
349 self.fixed_ips. These values will be directly
350 translated into the fixed_ips dict (optional)
351 :param security_groups: One or more security group IDs.
352 :param port_security_enabled: When True, security groups will be
353 applied to the port else not
355 :param allowed_address_pairs: A dictionary containing a set of zero or
356 more allowed address pairs. An address
357 pair contains an IP address and MAC
359 :param opt_value: The extra DHCP option value (optional)
360 :param opt_name: The extra DHCP option name (optional)
361 :param device_owner: The ID of the entity that uses this port.
362 For example, a DHCP agent (optional)
363 :param device_id: The ID of the device that uses this port.
364 For example, a virtual server (optional)
365 :param extra_dhcp_opts: k/v of options to use with your DHCP (optional)
369 kwargs = kwargs['port']
371 self.name = kwargs.get('name')
372 self.network_name = kwargs.get('network_name')
374 if kwargs.get('admin_state_up') is not None:
375 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
377 self.admin_state_up = True
379 self.project_name = kwargs.get('project_name')
380 self.mac_address = kwargs.get('mac_address')
381 self.ip_addrs = kwargs.get('ip_addrs')
382 self.security_groups = kwargs.get('security_groups')
384 if kwargs.get('port_security_enabled') is not None:
385 self.port_security_enabled = str2bool(
386 str(kwargs['port_security_enabled']))
388 self.port_security_enabled = None
390 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
391 self.opt_value = kwargs.get('opt_value')
392 self.opt_name = kwargs.get('opt_name')
393 self.device_owner = kwargs.get('device_owner')
394 self.device_id = kwargs.get('device_id')
395 self.extra_dhcp_opts = kwargs.get('extra_dhcp_opts')
397 if not self.network_name:
398 raise PortConfigError(
399 'The attribute network_name is required')
401 def __get_fixed_ips(self, neutron):
403 Sets the self.fixed_ips value
404 :param neutron: the Neutron client
411 for ip_addr_dict in self.ip_addrs:
412 subnet = neutron_utils.get_subnet(
413 neutron, subnet_name=ip_addr_dict['subnet_name'])
414 if subnet and 'ip' in ip_addr_dict:
415 fixed_ips.append({'ip_address': ip_addr_dict['ip'],
416 'subnet_id': subnet.id})
418 raise PortConfigError(
419 'Invalid port configuration, subnet does not exist '
420 'with name - ' + ip_addr_dict['subnet_name'])
424 def dict_for_neutron(self, neutron, os_creds):
426 Returns a dictionary object representing this object.
427 This is meant to be converted into JSON designed for use by the Neutron
430 TODO - expand automated testing to exercise all parameters
431 :param neutron: the Neutron client
432 :param os_creds: the OpenStack credentials
433 :return: the dictionary object
439 if self.project_name:
440 keystone = keystone_utils.keystone_client(os_creds)
441 project = keystone_utils.get_project(
442 keystone=keystone, project_name=self.project_name)
444 project_id = project.id
446 network = neutron_utils.get_network(
447 neutron, network_name=self.network_name, project_id=project_id)
449 raise PortConfigError(
450 'Cannot locate network with name - ' + self.network_name)
452 out['network_id'] = network.id
454 if self.admin_state_up is not None:
455 out['admin_state_up'] = self.admin_state_up
457 out['name'] = self.name
458 if self.project_name:
460 out['tenant_id'] = project_id
462 raise PortConfigError(
463 'Could not find project ID for project named - ' +
466 out['mac_address'] = self.mac_address
468 fixed_ips = self.__get_fixed_ips(neutron)
469 if fixed_ips and len(fixed_ips) > 0:
470 out['fixed_ips'] = fixed_ips
472 if self.security_groups:
474 for sec_grp_name in self.security_groups:
475 sec_grp = neutron_utils.get_security_group(
476 neutron, sec_grp_name=sec_grp_name, project_id=project_id)
478 sec_grp_ids.append(sec_grp.id)
479 out['security_groups'] = sec_grp_ids
480 if self.port_security_enabled is not None:
481 out['port_security_enabled'] = self.port_security_enabled
482 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
483 out['allowed_address_pairs'] = self.allowed_address_pairs
485 out['opt_value'] = self.opt_value
487 out['opt_name'] = self.opt_name
488 if self.device_owner:
489 out['device_owner'] = self.device_owner
491 out['device_id'] = self.device_id
492 if self.extra_dhcp_opts:
493 out['extra_dhcp_opts'] = self.extra_dhcp_opts
496 def __eq__(self, other):
497 return (self.name == other.name and
498 self.network_name == other.network_name and
499 self.admin_state_up == other.admin_state_up and
500 self.project_name == other.project_name and
501 self.mac_address == other.mac_address and
502 self.ip_addrs == other.ip_addrs and
503 # self.fixed_ips == other.fixed_ips and
504 self.security_groups == other.security_groups and
505 self.allowed_address_pairs == other.allowed_address_pairs and
506 self.opt_value == other.opt_value and
507 self.opt_name == other.opt_name and
508 self.device_owner == other.device_owner and
509 self.device_id == other.device_id)
512 class PortConfigError(Exception):
514 Exception to be thrown when port settings attributes are incorrect