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 session = keystone_utils.keystone_session(os_creds)
102 keystone = keystone_utils.keystone_client(os_creds, session)
104 project = keystone_utils.get_project(
105 keystone=keystone, project_name=self.project_name)
109 keystone_utils.close_session(session)
113 def dict_for_neutron(self, os_creds):
115 Returns a dictionary object representing this object.
116 This is meant to be converted into JSON designed for use by the Neutron
118 TODO - expand automated testing to exercise all parameters
120 :param os_creds: the OpenStack credentials
121 :return: the dictionary object
126 out['name'] = self.name
127 if self.admin_state_up is not None:
128 out['admin_state_up'] = self.admin_state_up
130 out['shared'] = self.shared
131 if self.project_name:
132 project_id = self.get_project_id(os_creds)
134 out['tenant_id'] = project_id
136 raise NetworkConfigError(
137 'Could not find project ID for project named - ' +
139 if self.network_type:
140 out['provider:network_type'] = self.network_type
141 if self.physical_network:
142 out['provider:physical_network'] = self.physical_network
143 if self.segmentation_id:
144 out['provider:segmentation_id'] = self.segmentation_id
146 out['router:external'] = self.external
147 return {'network': out}
150 class NetworkConfigError(Exception):
152 Exception to be thrown when networks settings attributes are incorrect
156 class IPv6Mode(enum.Enum):
161 stateful = 'dhcpv6-stateful'
162 stateless = 'dhcpv6-stateless'
165 class SubnetConfig(object):
167 Class representing a subnet configuration
170 def __init__(self, **kwargs):
172 Constructor - all parameters are optional except cidr (subnet mask)
173 :param name: The subnet name (required)
174 :param cidr: The CIDR (required)
175 :param ip_version: The IP version, which is 4 or 6 (required)
176 :param project_name: The name of the project who owns the network.
177 Only administrative users can specify a project ID
178 other than their own. You cannot change this value
179 through authorization policies (optional)
180 :param start: The start address for the allocation pools (optional)
181 :param end: The end address for the allocation pools (optional)
182 :param gateway_ip: The gateway IP address (optional). When not
183 configured, the IP address will be automatically
184 assigned; when 'none', no gateway address will be
185 assigned, else the value must be valid
186 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
188 :param dns_nameservers: A list of DNS name servers for the subnet.
189 Specify each name server as an IP address
190 and separate multiple entries with a space.
191 For example [8.8.8.7 8.8.8.8]
193 :param host_routes: A list of host route dictionaries for the subnet.
197 "destination":"0.0.0.0/0",
198 "nexthop":"123.456.78.9"
201 "destination":"192.168.0.0/24",
202 "nexthop":"192.168.0.1"
205 :param destination: The destination for static route (optional)
206 :param nexthop: The next hop for the destination (optional)
207 :param ipv6_ra_mode: an instance of the IPv6Mode enum
208 (optional when enable_dhcp is True)
209 :param ipv6_address_mode: an instance of the IPv6Mode enum
210 (optional when enable_dhcp is True)
211 :raise: SubnetConfigError when config does not have or cidr values
214 self.cidr = kwargs.get('cidr')
215 if kwargs.get('ip_version'):
216 self.ip_version = kwargs['ip_version']
220 # Optional attributes that can be set after instantiation
221 self.name = kwargs.get('name')
222 self.project_name = kwargs.get('project_name')
223 self.start = kwargs.get('start')
224 self.end = kwargs.get('end')
225 self.gateway_ip = kwargs.get('gateway_ip')
226 self.enable_dhcp = kwargs.get('enable_dhcp')
228 if 'dns_nameservers' in kwargs:
229 self.dns_nameservers = kwargs.get('dns_nameservers')
231 self.dns_nameservers = list()
233 self.host_routes = kwargs.get('host_routes')
234 self.destination = kwargs.get('destination')
235 self.nexthop = kwargs.get('nexthop')
236 self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
237 self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
239 if not self.name or not self.cidr:
240 raise SubnetConfigError('Name and cidr required for subnets')
242 def dict_for_neutron(self, os_creds, network=None):
244 Returns a dictionary object representing this object.
245 This is meant to be converted into JSON designed for use by the Neutron
247 :param os_creds: the OpenStack credentials
248 :param network: The network object on which the subnet will be created
250 :return: the dictionary object
254 'ip_version': self.ip_version,
258 out['network_id'] = network.id
260 out['name'] = self.name
261 if self.project_name:
262 session = keystone_utils.keystone_session(os_creds)
263 keystone = keystone_utils.keystone_client(os_creds, session)
265 project = keystone_utils.get_project(
266 keystone=keystone, project_name=self.project_name)
268 keystone_utils.close_session(session)
271 project_id = project.id
273 out['tenant_id'] = project_id
275 raise SubnetConfigError(
276 'Could not find project ID for project named - ' +
278 if self.start and self.end:
279 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
281 if self.gateway_ip == 'none':
282 out['gateway_ip'] = None
284 out['gateway_ip'] = self.gateway_ip
285 if self.enable_dhcp is not None:
286 out['enable_dhcp'] = self.enable_dhcp
287 if self.dns_nameservers and len(self.dns_nameservers) > 0:
288 out['dns_nameservers'] = self.dns_nameservers
289 if self.host_routes and len(self.host_routes) > 0:
290 out['host_routes'] = self.host_routes
292 out['destination'] = self.destination
294 out['nexthop'] = self.nexthop
295 if self.ipv6_ra_mode:
296 out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
297 if self.ipv6_address_mode:
298 out['ipv6_address_mode'] = self.ipv6_address_mode.value
304 Takes a the direction value maps it to the Direction enum. When None return
306 :param mode: the mode value
307 :return: the IPv6Mode enum object
308 :raise: SubnetConfigError if value is invalid
312 if isinstance(mode, IPv6Mode):
314 elif isinstance(mode, str):
316 if mode_str == 'slaac':
317 return IPv6Mode.slaac
318 elif mode_str == 'dhcpv6-stateful':
319 return IPv6Mode.stateful
320 elif mode_str == 'stateful':
321 return IPv6Mode.stateful
322 elif mode_str == 'dhcpv6-stateless':
323 return IPv6Mode.stateless
324 elif mode_str == 'stateless':
325 return IPv6Mode.stateless
327 raise SubnetConfigError('Invalid mode - ' + mode_str)
329 return map_mode(mode.value)
332 class SubnetConfigError(Exception):
334 Exception to be thrown when subnet settings attributes are incorrect
338 class PortConfig(object):
340 Class representing a port configuration
343 def __init__(self, **kwargs):
346 :param name: A symbolic name for the port (optional).
347 :param network_name: The name of the network on which to create the
349 :param admin_state_up: A boolean value denoting the administrative
350 status of the port (default = True)
351 :param project_name: The name of the project who owns the network.
352 Only administrative users can specify a project ID
353 other than their own. You cannot change this value
354 through authorization policies (optional)
355 :param mac_address: The MAC address. If you specify an address that is
356 not valid, a Bad Request (400) status code is
357 returned. If you do not specify a MAC address,
358 OpenStack Networking tries to allocate one. If a
359 failure occurs, a Service Unavailable (503) status
360 code is returned (optional)
361 :param ip_addrs: A list of dict objects where each contains two keys
362 'subnet_name' and 'ip' values which will get mapped to
363 self.fixed_ips. These values will be directly
364 translated into the fixed_ips dict (optional)
365 :param security_groups: One or more security group IDs.
366 :param port_security_enabled: When True, security groups will be
367 applied to the port else not
369 :param allowed_address_pairs: A dictionary containing a set of zero or
370 more allowed address pairs. An address
371 pair contains an IP address and MAC
373 :param opt_value: The extra DHCP option value (optional)
374 :param opt_name: The extra DHCP option name (optional)
375 :param device_owner: The ID of the entity that uses this port.
376 For example, a DHCP agent (optional)
377 :param device_id: The ID of the device that uses this port.
378 For example, a virtual server (optional)
379 :param extra_dhcp_opts: k/v of options to use with your DHCP (optional)
383 kwargs = kwargs['port']
385 self.name = kwargs.get('name')
386 self.network_name = kwargs.get('network_name')
388 if kwargs.get('admin_state_up') is not None:
389 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
391 self.admin_state_up = True
393 self.project_name = kwargs.get('project_name')
394 self.mac_address = kwargs.get('mac_address')
395 self.ip_addrs = kwargs.get('ip_addrs')
396 self.security_groups = kwargs.get('security_groups')
398 if kwargs.get('port_security_enabled') is not None:
399 self.port_security_enabled = str2bool(
400 str(kwargs['port_security_enabled']))
402 self.port_security_enabled = None
404 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
405 self.opt_value = kwargs.get('opt_value')
406 self.opt_name = kwargs.get('opt_name')
407 self.device_owner = kwargs.get('device_owner')
408 self.device_id = kwargs.get('device_id')
409 self.extra_dhcp_opts = kwargs.get('extra_dhcp_opts')
411 if not self.network_name:
412 raise PortConfigError(
413 'The attribute network_name is required')
415 def __get_fixed_ips(self, neutron):
417 Sets the self.fixed_ips value
418 :param neutron: the Neutron client
425 for ip_addr_dict in self.ip_addrs:
426 subnet = neutron_utils.get_subnet(
427 neutron, subnet_name=ip_addr_dict['subnet_name'])
428 if subnet and 'ip' in ip_addr_dict:
429 fixed_ips.append({'ip_address': ip_addr_dict['ip'],
430 'subnet_id': subnet.id})
432 raise PortConfigError(
433 'Invalid port configuration, subnet does not exist '
434 'with name - ' + ip_addr_dict['subnet_name'])
438 def dict_for_neutron(self, neutron, os_creds):
440 Returns a dictionary object representing this object.
441 This is meant to be converted into JSON designed for use by the Neutron
444 TODO - expand automated testing to exercise all parameters
445 :param neutron: the Neutron client
446 :param os_creds: the OpenStack credentials
447 :return: the dictionary object
452 session = keystone_utils.keystone_session(os_creds)
453 keystone = keystone_utils.keystone_client(os_creds, session)
455 network = neutron_utils.get_network(
456 neutron, keystone, network_name=self.network_name,
457 project_name=self.project_name)
459 keystone_utils.close_session(session)
462 raise PortConfigError(
463 'Cannot locate network with name - ' + self.network_name
464 + ' in project - ' + str(self.project_name))
466 out['network_id'] = network.id
468 if self.admin_state_up is not None:
469 out['admin_state_up'] = self.admin_state_up
471 out['name'] = self.name
472 if self.project_name:
473 project = keystone_utils.get_project(
474 keystone=keystone, project_name=self.project_name)
477 project_id = project.id
479 out['tenant_id'] = project_id
481 raise PortConfigError(
482 'Could not find project ID for project named - ' +
485 out['mac_address'] = self.mac_address
487 fixed_ips = self.__get_fixed_ips(neutron)
488 if fixed_ips and len(fixed_ips) > 0:
489 out['fixed_ips'] = fixed_ips
491 if self.security_groups:
493 for sec_grp_name in self.security_groups:
494 sec_grp = neutron_utils.get_security_group(
495 neutron, keystone, sec_grp_name=sec_grp_name,
496 project_name=self.project_name)
498 sec_grp_ids.append(sec_grp.id)
499 out['security_groups'] = sec_grp_ids
500 if self.port_security_enabled is not None:
501 out['port_security_enabled'] = self.port_security_enabled
502 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
503 out['allowed_address_pairs'] = self.allowed_address_pairs
505 out['opt_value'] = self.opt_value
507 out['opt_name'] = self.opt_name
508 if self.device_owner:
509 out['device_owner'] = self.device_owner
511 out['device_id'] = self.device_id
512 if self.extra_dhcp_opts:
513 out['extra_dhcp_opts'] = self.extra_dhcp_opts
516 def __eq__(self, other):
517 return (self.name == other.name and
518 self.network_name == other.network_name and
519 self.admin_state_up == other.admin_state_up and
520 self.project_name == other.project_name and
521 self.mac_address == other.mac_address and
522 self.ip_addrs == other.ip_addrs and
523 # self.fixed_ips == other.fixed_ips and
524 self.security_groups == other.security_groups and
525 self.allowed_address_pairs == other.allowed_address_pairs and
526 self.opt_value == other.opt_value and
527 self.opt_name == other.opt_name and
528 self.device_owner == other.device_owner and
529 self.device_id == other.device_id)
532 class PortConfigError(Exception):
534 Exception to be thrown when port settings attributes are incorrect