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 if self.ip_version == 4:
225 self.dns_nameservers = ['8.8.8.8']
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 out['gateway_ip'] = self.gateway_ip
274 if self.enable_dhcp is not None:
275 out['enable_dhcp'] = self.enable_dhcp
276 if self.dns_nameservers and len(self.dns_nameservers) > 0:
277 out['dns_nameservers'] = self.dns_nameservers
278 if self.host_routes and len(self.host_routes) > 0:
279 out['host_routes'] = self.host_routes
281 out['destination'] = self.destination
283 out['nexthop'] = self.nexthop
284 if self.ipv6_ra_mode:
285 out['ipv6_ra_mode'] = self.ipv6_ra_mode.value
286 if self.ipv6_address_mode:
287 out['ipv6_address_mode'] = self.ipv6_address_mode.value
293 Takes a the direction value maps it to the Direction enum. When None return
295 :param mode: the mode value
296 :return: the IPv6Mode enum object
297 :raise: SubnetConfigError if value is invalid
301 if isinstance(mode, IPv6Mode):
303 elif isinstance(mode, str):
305 if mode_str == 'slaac':
306 return IPv6Mode.slaac
307 elif mode_str == 'dhcpv6-stateful':
308 return IPv6Mode.stateful
309 elif mode_str == 'stateful':
310 return IPv6Mode.stateful
311 elif mode_str == 'dhcpv6-stateless':
312 return IPv6Mode.stateless
313 elif mode_str == 'stateless':
314 return IPv6Mode.stateless
316 raise SubnetConfigError('Invalid mode - ' + mode_str)
318 return map_mode(mode.value)
321 class SubnetConfigError(Exception):
323 Exception to be thrown when subnet settings attributes are incorrect
327 class PortConfig(object):
329 Class representing a port configuration
332 def __init__(self, **kwargs):
335 :param name: A symbolic name for the port (optional).
336 :param network_name: The name of the network on which to create the
338 :param admin_state_up: A boolean value denoting the administrative
339 status of the port (default = True)
340 :param project_name: The name of the project who owns the network.
341 Only administrative users can specify a project ID
342 other than their own. You cannot change this value
343 through authorization policies (optional)
344 :param mac_address: The MAC address. If you specify an address that is
345 not valid, a Bad Request (400) status code is
346 returned. If you do not specify a MAC address,
347 OpenStack Networking tries to allocate one. If a
348 failure occurs, a Service Unavailable (503) status
349 code is returned (optional)
350 :param ip_addrs: A list of dict objects where each contains two keys
351 'subnet_name' and 'ip' values which will get mapped to
352 self.fixed_ips. These values will be directly
353 translated into the fixed_ips dict (optional)
354 :param security_groups: One or more security group IDs.
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)
368 kwargs = kwargs['port']
370 self.name = kwargs.get('name')
371 self.network_name = kwargs.get('network_name')
373 if kwargs.get('admin_state_up') is not None:
374 self.admin_state_up = str2bool(str(kwargs['admin_state_up']))
376 self.admin_state_up = True
378 self.project_name = kwargs.get('project_name')
379 self.mac_address = kwargs.get('mac_address')
380 self.ip_addrs = kwargs.get('ip_addrs')
381 self.security_groups = kwargs.get('security_groups')
382 self.allowed_address_pairs = kwargs.get('allowed_address_pairs')
383 self.opt_value = kwargs.get('opt_value')
384 self.opt_name = kwargs.get('opt_name')
385 self.device_owner = kwargs.get('device_owner')
386 self.device_id = kwargs.get('device_id')
388 if not self.network_name:
389 raise PortConfigError(
390 'The attribute network_name is required')
392 def __get_fixed_ips(self, neutron):
394 Sets the self.fixed_ips value
395 :param neutron: the Neutron client
402 for ip_addr_dict in self.ip_addrs:
403 subnet = neutron_utils.get_subnet(
404 neutron, subnet_name=ip_addr_dict['subnet_name'])
405 if subnet and 'ip' in ip_addr_dict:
406 fixed_ips.append({'ip_address': ip_addr_dict['ip'],
407 'subnet_id': subnet.id})
409 raise PortConfigError(
410 'Invalid port configuration, subnet does not exist '
411 'with name - ' + ip_addr_dict['subnet_name'])
415 def dict_for_neutron(self, neutron, os_creds):
417 Returns a dictionary object representing this object.
418 This is meant to be converted into JSON designed for use by the Neutron
421 TODO - expand automated testing to exercise all parameters
422 :param neutron: the Neutron client
423 :param os_creds: the OpenStack credentials
424 :return: the dictionary object
430 if self.project_name:
431 keystone = keystone_utils.keystone_client(os_creds)
432 project = keystone_utils.get_project(
433 keystone=keystone, project_name=self.project_name)
435 project_id = project.id
437 network = neutron_utils.get_network(
438 neutron, network_name=self.network_name, project_id=project_id)
440 raise PortConfigError(
441 'Cannot locate network with name - ' + self.network_name)
443 out['network_id'] = network.id
445 if self.admin_state_up is not None:
446 out['admin_state_up'] = self.admin_state_up
448 out['name'] = self.name
449 if self.project_name:
451 out['tenant_id'] = project_id
453 raise PortConfigError(
454 'Could not find project ID for project named - ' +
457 out['mac_address'] = self.mac_address
459 fixed_ips = self.__get_fixed_ips(neutron)
460 if fixed_ips and len(fixed_ips) > 0:
461 out['fixed_ips'] = fixed_ips
463 if self.security_groups:
464 out['security_groups'] = self.security_groups
465 if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
466 out['allowed_address_pairs'] = self.allowed_address_pairs
468 out['opt_value'] = self.opt_value
470 out['opt_name'] = self.opt_name
471 if self.device_owner:
472 out['device_owner'] = self.device_owner
474 out['device_id'] = self.device_id
477 def __eq__(self, other):
478 return (self.name == other.name and
479 self.network_name == other.network_name and
480 self.admin_state_up == other.admin_state_up and
481 self.project_name == other.project_name and
482 self.mac_address == other.mac_address and
483 self.ip_addrs == other.ip_addrs and
484 # self.fixed_ips == other.fixed_ips and
485 self.security_groups == other.security_groups and
486 self.allowed_address_pairs == other.allowed_address_pairs and
487 self.opt_value == other.opt_value and
488 self.opt_name == other.opt_name and
489 self.device_owner == other.device_owner and
490 self.device_id == other.device_id)
493 class PortConfigError(Exception):
495 Exception to be thrown when port settings attributes are incorrect