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.
49 :param mtu: MTU setting (optional)
53 self.project_id = None
55 self.name = kwargs.get('name')
56 if kwargs.get('admin_state_up') is not None:
57 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
59 self.admin_state_up = True
61 if kwargs.get('shared') is not None:
62 self.shared = str2bool(str(kwargs['shared']))
66 self.project_name = kwargs.get('project_name')
68 if kwargs.get('external') is not None:
69 self.external = str2bool(str(kwargs.get('external')))
73 self.network_type = kwargs.get('network_type')
74 self.physical_network = kwargs.get('physical_network')
75 self.segmentation_id = kwargs.get('segmentation_id')
77 self.subnet_settings = list()
78 subnet_settings = kwargs.get('subnets')
79 if not subnet_settings:
80 subnet_settings = kwargs.get('subnet_settings', list())
82 for subnet_config in subnet_settings:
83 if isinstance(subnet_config, SubnetConfig):
84 self.subnet_settings.append(subnet_config)
86 self.subnet_settings.append(
87 SubnetConfig(**subnet_config['subnet']))
89 if not self.name or len(self.name) < 1:
90 raise NetworkConfigError('Name required for networks')
92 self.mtu = kwargs.get('mtu')
94 def get_project_id(self, os_creds):
96 Returns the project ID for a given project_name or None
97 :param os_creds: the credentials required for keystone client retrieval
98 :return: the ID or None
101 return self.project_id
103 if self.project_name:
104 session = keystone_utils.keystone_session(os_creds)
105 keystone = keystone_utils.keystone_client(os_creds, session)
107 project = keystone_utils.get_project(
108 keystone=keystone, project_name=self.project_name)
112 keystone_utils.close_session(session)
116 def dict_for_neutron(self, os_creds):
118 Returns a dictionary object representing this object.
119 This is meant to be converted into JSON designed for use by the Neutron
121 TODO - expand automated testing to exercise all parameters
123 :param os_creds: the OpenStack credentials
124 :return: the dictionary object
129 out['name'] = self.name
130 if self.admin_state_up is not None:
131 out['admin_state_up'] = self.admin_state_up
133 out['shared'] = self.shared
134 if self.project_name:
135 project_id = self.get_project_id(os_creds)
137 out['tenant_id'] = project_id
139 raise NetworkConfigError(
140 'Could not find project ID for project named - ' +
142 if self.network_type:
143 out['provider:network_type'] = self.network_type
144 if self.physical_network:
145 out['provider:physical_network'] = self.physical_network
146 if self.segmentation_id:
147 out['provider:segmentation_id'] = self.segmentation_id
149 out['router:external'] = self.external
151 out['mtu'] = self.mtu
152 return {'network': out}
155 class NetworkConfigError(Exception):
157 Exception to be thrown when networks settings attributes are incorrect
161 class IPv6Mode(enum.Enum):
166 stateful = 'dhcpv6-stateful'
167 stateless = 'dhcpv6-stateless'
170 class SubnetConfig(object):
172 Class representing a subnet configuration
175 def __init__(self, **kwargs):
177 Constructor - all parameters are optional except cidr (subnet mask)
178 :param name: The subnet name (required)
179 :param cidr: The CIDR (required)
180 :param ip_version: The IP version, which is 4 or 6 (required)
181 :param project_name: The name of the project who owns the network.
182 Only administrative users can specify a project ID
183 other than their own. You cannot change this value
184 through authorization policies (optional)
185 :param start: The start address for the allocation pools (optional)
186 :param end: The end address for the allocation pools (optional)
187 :param gateway_ip: The gateway IP address (optional). When not
188 configured, the IP address will be automatically
189 assigned; when 'none', no gateway address will be
190 assigned, else the value must be valid
191 :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is
193 :param dns_nameservers: A list of DNS name servers for the subnet.
194 Specify each name server as an IP address
195 and separate multiple entries with a space.
196 For example [8.8.8.7 8.8.8.8]
198 :param host_routes: A list of host route dictionaries for the subnet.
202 "destination":"0.0.0.0/0",
203 "nexthop":"123.456.78.9"
206 "destination":"192.168.0.0/24",
207 "nexthop":"192.168.0.1"
210 :param destination: The destination for static route (optional)
211 :param nexthop: The next hop for the destination (optional)
212 :param ipv6_ra_mode: an instance of the IPv6Mode enum
213 (optional when enable_dhcp is True)
214 :param ipv6_address_mode: an instance of the IPv6Mode enum
215 (optional when enable_dhcp is True)
216 :raise: SubnetConfigError when config does not have or cidr values
219 self.cidr = kwargs.get('cidr')
220 if kwargs.get('ip_version'):
221 self.ip_version = kwargs['ip_version']
225 # Optional attributes that can be set after instantiation
226 self.name = kwargs.get('name')
227 self.project_name = kwargs.get('project_name')
228 self.start = kwargs.get('start')
229 self.end = kwargs.get('end')
230 self.gateway_ip = kwargs.get('gateway_ip')
231 self.enable_dhcp = kwargs.get('enable_dhcp')
233 if 'dns_nameservers' in kwargs:
234 self.dns_nameservers = kwargs.get('dns_nameservers')
236 self.dns_nameservers = list()
238 self.host_routes = kwargs.get('host_routes')
239 self.destination = kwargs.get('destination')
240 self.nexthop = kwargs.get('nexthop')
241 self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode'))
242 self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode'))
244 if not self.name or not self.cidr:
245 raise SubnetConfigError('Name and cidr required for subnets')
247 def dict_for_neutron(self, os_creds, network=None):
249 Returns a dictionary object representing this object.
250 This is meant to be converted into JSON designed for use by the Neutron
252 :param os_creds: the OpenStack credentials
253 :param network: The network object on which the subnet will be created
255 :return: the dictionary object
259 'ip_version': self.ip_version,
263 out['network_id'] = network.id
265 out['name'] = self.name
266 if self.project_name:
267 session = keystone_utils.keystone_session(os_creds)
268 keystone = keystone_utils.keystone_client(os_creds, session)
270 project = keystone_utils.get_project(
271 keystone=keystone, project_name=self.project_name)
273 keystone_utils.close_session(session)
276 project_id = project.id
278 out['tenant_id'] = project_id
280 raise SubnetConfigError(
281 'Could not find project ID for project named - ' +
283 if self.start and self.end:
284 out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
286 if self.gateway_ip == 'none':
287 out['gateway_ip'] = None
289 out['gateway_ip'] = self.gateway_ip
290 if self.enable_dhcp is not None:
291 out['enable_dhcp'] = self.enable_dhcp
292 if self.dns_nameservers and len(self.dns_nameservers) > 0:
293 out['dns_nameservers'] = self.dns_nameservers
294 if self.host_routes and len(self.host_routes) > 0:
295 out['host_routes'] = self.host_routes
297 out['destination'] = self.destination
299 out['nexthop'] = self.nexthop
300 if self.ipv6_ra_mode:
301 out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
302 if self.ipv6_address_mode:
303 out['ipv6_address_mode'] = self.ipv6_address_mode.value
309 Takes a the direction value maps it to the Direction enum. When None return
311 :param mode: the mode value
312 :return: the IPv6Mode enum object
313 :raise: SubnetConfigError if value is invalid
317 if isinstance(mode, IPv6Mode):
319 elif isinstance(mode, str):
321 if mode_str == 'slaac':
322 return IPv6Mode.slaac
323 elif mode_str == 'dhcpv6-stateful':
324 return IPv6Mode.stateful
325 elif mode_str == 'stateful':
326 return IPv6Mode.stateful
327 elif mode_str == 'dhcpv6-stateless':
328 return IPv6Mode.stateless
329 elif mode_str == 'stateless':
330 return IPv6Mode.stateless
332 raise SubnetConfigError('Invalid mode - ' + mode_str)
334 return map_mode(mode.value)
337 class SubnetConfigError(Exception):
339 Exception to be thrown when subnet settings attributes are incorrect
343 class PortConfig(object):
345 Class representing a port configuration
348 def __init__(self, **kwargs):
351 :param name: A symbolic name for the port (optional).
352 :param network_name: The name of the network on which to create the
354 :param admin_state_up: A boolean value denoting the administrative
355 status of the port (default = True)
356 :param project_name: The name of the project who owns the network.
357 Only administrative users can specify a project ID
358 other than their own. You cannot change this value
359 through authorization policies (optional)
360 :param mac_address: The MAC address. If you specify an address that is
361 not valid, a Bad Request (400) status code is
362 returned. If you do not specify a MAC address,
363 OpenStack Networking tries to allocate one. If a
364 failure occurs, a Service Unavailable (503) status
365 code is returned (optional)
366 :param ip_addrs: A list of dict objects where each contains two keys
367 'subnet_name' and 'ip' values which will get mapped to
368 self.fixed_ips. These values will be directly
369 translated into the fixed_ips dict (optional)
370 :param security_groups: One or more security group IDs.
371 :param port_security_enabled: When True, security groups will be
372 applied to the port else not
374 :param allowed_address_pairs: A dictionary containing a set of zero or
375 more allowed address pairs. An address
376 pair contains an IP address and MAC
378 :param opt_value: The extra DHCP option value (optional)
379 :param opt_name: The extra DHCP option name (optional)
380 :param device_owner: The ID of the entity that uses this port.
381 For example, a DHCP agent (optional)
382 :param device_id: The ID of the device that uses this port.
383 For example, a virtual server (optional)
384 :param extra_dhcp_opts: k/v of options to use with your DHCP (optional)
388 kwargs = kwargs['port']
390 self.name = kwargs.get('name')
391 self.network_name = kwargs.get('network_name')
393 if kwargs.get('admin_state_up') is not None:
394 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
396 self.admin_state_up = True
398 self.project_name = kwargs.get('project_name')
399 self.mac_address = kwargs.get('mac_address')
400 self.ip_addrs = kwargs.get('ip_addrs')
401 self.security_groups = kwargs.get('security_groups')
403 if kwargs.get('port_security_enabled') is not None:
404 self.port_security_enabled = str2bool(
405 str(kwargs['port_security_enabled']))
407 self.port_security_enabled = None
409 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
410 self.opt_value = kwargs.get('opt_value')
411 self.opt_name = kwargs.get('opt_name')
412 self.device_owner = kwargs.get('device_owner')
413 self.device_id = kwargs.get('device_id')
414 self.extra_dhcp_opts = kwargs.get('extra_dhcp_opts')
416 if not self.network_name:
417 raise PortConfigError(
418 'The attribute network_name is required')
420 def __get_fixed_ips(self, neutron, network):
422 Sets the self.fixed_ips value
423 :param neutron: the Neutron client
424 :param network: the SNAPS-OO network domain object
430 for ip_addr_dict in self.ip_addrs:
431 subnet = neutron_utils.get_subnet(
432 neutron, network, subnet_name=ip_addr_dict['subnet_name'])
434 if 'ip' in ip_addr_dict:
435 fixed_ips.append({'ip_address': ip_addr_dict['ip'],
436 'subnet_id': subnet.id})
438 raise PortConfigError(
439 'Invalid port configuration, subnet does not exist '
440 'with name - ' + ip_addr_dict['subnet_name'])
444 def dict_for_neutron(self, neutron, os_creds):
446 Returns a dictionary object representing this object.
447 This is meant to be converted into JSON designed for use by the Neutron
450 TODO - expand automated testing to exercise all parameters
451 :param neutron: the Neutron client
452 :param os_creds: the OpenStack credentials
453 :return: the dictionary object
456 session = keystone_utils.keystone_session(os_creds)
457 keystone = keystone_utils.keystone_client(os_creds, session)
459 project_name = os_creds.project_name
460 if self.project_name:
461 project_name = project_name
463 network = neutron_utils.get_network(
464 neutron, keystone, network_name=self.network_name)
465 if network and not (network.shared or network.external):
466 network = neutron_utils.get_network(
467 neutron, keystone, network_name=self.network_name,
468 project_name=project_name)
471 keystone_utils.close_session(session)
474 raise PortConfigError(
475 'Cannot locate network with name - ' + self.network_name
476 + ' in project - ' + str(project_name))
478 out['network_id'] = network.id
480 if self.admin_state_up is not None:
481 out['admin_state_up'] = self.admin_state_up
483 out['name'] = self.name
484 if self.project_name:
485 project = keystone_utils.get_project(
486 keystone=keystone, project_name=self.project_name)
489 project_id = project.id
491 out['tenant_id'] = project_id
493 raise PortConfigError(
494 'Could not find project ID for project named - ' +
497 out['mac_address'] = self.mac_address
499 fixed_ips = self.__get_fixed_ips(neutron, network)
500 if fixed_ips and len(fixed_ips) > 0:
501 out['fixed_ips'] = fixed_ips
503 if self.security_groups:
505 for sec_grp_name in self.security_groups:
506 sec_grp = neutron_utils.get_security_group(
507 neutron, keystone, sec_grp_name=sec_grp_name,
508 project_name=self.project_name)
510 sec_grp_ids.append(sec_grp.id)
511 out['security_groups'] = sec_grp_ids
512 if self.port_security_enabled is not None:
513 out['port_security_enabled'] = self.port_security_enabled
514 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
515 out['allowed_address_pairs'] = self.allowed_address_pairs
517 out['opt_value'] = self.opt_value
519 out['opt_name'] = self.opt_name
520 if self.device_owner:
521 out['device_owner'] = self.device_owner
523 out['device_id'] = self.device_id
524 if self.extra_dhcp_opts:
525 out['extra_dhcp_opts'] = self.extra_dhcp_opts
528 def __eq__(self, other):
529 return (self.name == other.name and
530 self.network_name == other.network_name and
531 self.admin_state_up == other.admin_state_up and
532 self.project_name == other.project_name and
533 self.mac_address == other.mac_address and
534 self.ip_addrs == other.ip_addrs and
535 # self.fixed_ips == other.fixed_ips and
536 self.security_groups == other.security_groups and
537 self.allowed_address_pairs == other.allowed_address_pairs and
538 self.opt_value == other.opt_value and
539 self.opt_name == other.opt_name and
540 self.device_owner == other.device_owner and
541 self.device_id == other.device_id)
544 class PortConfigError(Exception):
546 Exception to be thrown when port settings attributes are incorrect