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). When not
179 configured, the IP address will be automatically
180 assigned; when 'none', no gateway address will be
181 assigned, else the value must be valid
182 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
184 :param dns_nameservers: A list of DNS name servers for the subnet.
185 Specify each name server as an IP address
186 and separate multiple entries with a space.
187 For example [8.8.8.7 8.8.8.8]
189 :param host_routes: A list of host route dictionaries for the subnet.
193 "destination":"0.0.0.0/0",
194 "nexthop":"123.456.78.9"
197 "destination":"192.168.0.0/24",
198 "nexthop":"192.168.0.1"
201 :param destination: The destination for static route (optional)
202 :param nexthop: The next hop for the destination (optional)
203 :param ipv6_ra_mode: an instance of the IPv6Mode enum
204 (optional when enable_dhcp is True)
205 :param ipv6_address_mode: an instance of the IPv6Mode enum
206 (optional when enable_dhcp is True)
207 :raise: SubnetConfigError when config does not have or cidr values
210 self.cidr = kwargs.get('cidr')
211 if kwargs.get('ip_version'):
212 self.ip_version = kwargs['ip_version']
216 # Optional attributes that can be set after instantiation
217 self.name = kwargs.get('name')
218 self.project_name = kwargs.get('project_name')
219 self.start = kwargs.get('start')
220 self.end = kwargs.get('end')
221 self.gateway_ip = kwargs.get('gateway_ip')
222 self.enable_dhcp = kwargs.get('enable_dhcp')
224 if 'dns_nameservers' in kwargs:
225 self.dns_nameservers = kwargs.get('dns_nameservers')
227 self.dns_nameservers = list()
229 self.host_routes = kwargs.get('host_routes')
230 self.destination = kwargs.get('destination')
231 self.nexthop = kwargs.get('nexthop')
232 self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
233 self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
235 if not self.name or not self.cidr:
236 raise SubnetConfigError('Name and cidr required for subnets')
238 def dict_for_neutron(self, os_creds, network=None):
240 Returns a dictionary object representing this object.
241 This is meant to be converted into JSON designed for use by the Neutron
243 :param os_creds: the OpenStack credentials
244 :param network: The network object on which the subnet will be created
246 :return: the dictionary object
250 'ip_version': self.ip_version,
254 out['network_id'] = network.id
256 out['name'] = self.name
257 if self.project_name:
258 keystone = keystone_utils.keystone_client(os_creds)
259 project = keystone_utils.get_project(
260 keystone=keystone, project_name=self.project_name)
263 project_id = project.id
265 out['tenant_id'] = project_id
267 raise SubnetConfigError(
268 'Could not find project ID for project named - ' +
270 if self.start and self.end:
271 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
273 if self.gateway_ip == 'none':
274 out['gateway_ip'] = None
276 out['gateway_ip'] = self.gateway_ip
277 if self.enable_dhcp is not None:
278 out['enable_dhcp'] = self.enable_dhcp
279 if self.dns_nameservers and len(self.dns_nameservers) > 0:
280 out['dns_nameservers'] = self.dns_nameservers
281 if self.host_routes and len(self.host_routes) > 0:
282 out['host_routes'] = self.host_routes
284 out['destination'] = self.destination
286 out['nexthop'] = self.nexthop
287 if self.ipv6_ra_mode:
288 out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
289 if self.ipv6_address_mode:
290 out['ipv6_address_mode'] = self.ipv6_address_mode.value
296 Takes a the direction value maps it to the Direction enum. When None return
298 :param mode: the mode value
299 :return: the IPv6Mode enum object
300 :raise: SubnetConfigError if value is invalid
304 if isinstance(mode, IPv6Mode):
306 elif isinstance(mode, str):
308 if mode_str == 'slaac':
309 return IPv6Mode.slaac
310 elif mode_str == 'dhcpv6-stateful':
311 return IPv6Mode.stateful
312 elif mode_str == 'stateful':
313 return IPv6Mode.stateful
314 elif mode_str == 'dhcpv6-stateless':
315 return IPv6Mode.stateless
316 elif mode_str == 'stateless':
317 return IPv6Mode.stateless
319 raise SubnetConfigError('Invalid mode - ' + mode_str)
321 return map_mode(mode.value)
324 class SubnetConfigError(Exception):
326 Exception to be thrown when subnet settings attributes are incorrect
330 class PortConfig(object):
332 Class representing a port configuration
335 def __init__(self, **kwargs):
338 :param name: A symbolic name for the port (optional).
339 :param network_name: The name of the network on which to create the
341 :param admin_state_up: A boolean value denoting the administrative
342 status of the port (default = True)
343 :param project_name: The name of the project who owns the network.
344 Only administrative users can specify a project ID
345 other than their own. You cannot change this value
346 through authorization policies (optional)
347 :param mac_address: The MAC address. If you specify an address that is
348 not valid, a Bad Request (400) status code is
349 returned. If you do not specify a MAC address,
350 OpenStack Networking tries to allocate one. If a
351 failure occurs, a Service Unavailable (503) status
352 code is returned (optional)
353 :param ip_addrs: A list of dict objects where each contains two keys
354 'subnet_name' and 'ip' values which will get mapped to
355 self.fixed_ips. These values will be directly
356 translated into the fixed_ips dict (optional)
357 :param security_groups: One or more security group IDs.
358 :param port_security_enabled: When True, security groups will be
359 applied to the port else not
361 :param allowed_address_pairs: A dictionary containing a set of zero or
362 more allowed address pairs. An address
363 pair contains an IP address and MAC
365 :param opt_value: The extra DHCP option value (optional)
366 :param opt_name: The extra DHCP option name (optional)
367 :param device_owner: The ID of the entity that uses this port.
368 For example, a DHCP agent (optional)
369 :param device_id: The ID of the device that uses this port.
370 For example, a virtual server (optional)
371 :param extra_dhcp_opts: k/v of options to use with your DHCP (optional)
375 kwargs = kwargs['port']
377 self.name = kwargs.get('name')
378 self.network_name = kwargs.get('network_name')
380 if kwargs.get('admin_state_up') is not None:
381 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
383 self.admin_state_up = True
385 self.project_name = kwargs.get('project_name')
386 self.mac_address = kwargs.get('mac_address')
387 self.ip_addrs = kwargs.get('ip_addrs')
388 self.security_groups = kwargs.get('security_groups')
390 if kwargs.get('port_security_enabled') is not None:
391 self.port_security_enabled = str2bool(
392 str(kwargs['port_security_enabled']))
394 self.port_security_enabled = None
396 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
397 self.opt_value = kwargs.get('opt_value')
398 self.opt_name = kwargs.get('opt_name')
399 self.device_owner = kwargs.get('device_owner')
400 self.device_id = kwargs.get('device_id')
401 self.extra_dhcp_opts = kwargs.get('extra_dhcp_opts')
403 if not self.network_name:
404 raise PortConfigError(
405 'The attribute network_name is required')
407 def __get_fixed_ips(self, neutron):
409 Sets the self.fixed_ips value
410 :param neutron: the Neutron client
417 for ip_addr_dict in self.ip_addrs:
418 subnet = neutron_utils.get_subnet(
419 neutron, subnet_name=ip_addr_dict['subnet_name'])
420 if subnet and 'ip' in ip_addr_dict:
421 fixed_ips.append({'ip_address': ip_addr_dict['ip'],
422 'subnet_id': subnet.id})
424 raise PortConfigError(
425 'Invalid port configuration, subnet does not exist '
426 'with name - ' + ip_addr_dict['subnet_name'])
430 def dict_for_neutron(self, neutron, os_creds):
432 Returns a dictionary object representing this object.
433 This is meant to be converted into JSON designed for use by the Neutron
436 TODO - expand automated testing to exercise all parameters
437 :param neutron: the Neutron client
438 :param os_creds: the OpenStack credentials
439 :return: the dictionary object
444 keystone = keystone_utils.keystone_client(os_creds)
445 network = neutron_utils.get_network(
446 neutron, keystone, network_name=self.network_name,
447 project_name=self.project_name)
449 raise PortConfigError(
450 'Cannot locate network with name - ' + self.network_name
451 + ' in project - ' + str(self.project_name))
453 out['network_id'] = network.id
455 if self.admin_state_up is not None:
456 out['admin_state_up'] = self.admin_state_up
458 out['name'] = self.name
459 if self.project_name:
460 project = keystone_utils.get_project(
461 keystone=keystone, project_name=self.project_name)
464 project_id = project.id
466 out['tenant_id'] = project_id
468 raise PortConfigError(
469 'Could not find project ID for project named - ' +
472 out['mac_address'] = self.mac_address
474 fixed_ips = self.__get_fixed_ips(neutron)
475 if fixed_ips and len(fixed_ips) > 0:
476 out['fixed_ips'] = fixed_ips
478 if self.security_groups:
480 for sec_grp_name in self.security_groups:
481 sec_grp = neutron_utils.get_security_group(
482 neutron, keystone, sec_grp_name=sec_grp_name,
483 project_name=self.project_name)
485 sec_grp_ids.append(sec_grp.id)
486 out['security_groups'] = sec_grp_ids
487 if self.port_security_enabled is not None:
488 out['port_security_enabled'] = self.port_security_enabled
489 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
490 out['allowed_address_pairs'] = self.allowed_address_pairs
492 out['opt_value'] = self.opt_value
494 out['opt_name'] = self.opt_name
495 if self.device_owner:
496 out['device_owner'] = self.device_owner
498 out['device_id'] = self.device_id
499 if self.extra_dhcp_opts:
500 out['extra_dhcp_opts'] = self.extra_dhcp_opts
503 def __eq__(self, other):
504 return (self.name == other.name and
505 self.network_name == other.network_name and
506 self.admin_state_up == other.admin_state_up and
507 self.project_name == other.project_name and
508 self.mac_address == other.mac_address and
509 self.ip_addrs == other.ip_addrs and
510 # self.fixed_ips == other.fixed_ips and
511 self.security_groups == other.security_groups and
512 self.allowed_address_pairs == other.allowed_address_pairs and
513 self.opt_value == other.opt_value and
514 self.opt_name == other.opt_name and
515 self.device_owner == other.device_owner and
516 self.device_id == other.device_id)
519 class PortConfigError(Exception):
521 Exception to be thrown when port settings attributes are incorrect