summaryrefslogtreecommitdiffstats
path: root/snaps/openstack/create_network.py
diff options
context:
space:
mode:
Diffstat (limited to 'snaps/openstack/create_network.py')
-rw-r--r--snaps/openstack/create_network.py519
1 files changed, 519 insertions, 0 deletions
diff --git a/snaps/openstack/create_network.py b/snaps/openstack/create_network.py
new file mode 100644
index 0000000..a214ba1
--- /dev/null
+++ b/snaps/openstack/create_network.py
@@ -0,0 +1,519 @@
+# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import logging
+
+from neutronclient.common.exceptions import NotFound
+
+from snaps.openstack.utils import keystone_utils, neutron_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('OpenStackNetwork')
+
+
+class OpenStackNetwork:
+ """
+ Class responsible for creating a network in OpenStack
+ """
+
+ def __init__(self, os_creds, network_settings):
+ """
+ Constructor - all parameters are required
+ :param os_creds: The credentials to connect with OpenStack
+ :param network_settings: The settings used to create a network
+ """
+ self.__os_creds = os_creds
+ self.network_settings = network_settings
+ self.__neutron = neutron_utils.neutron_client(self.__os_creds)
+
+ # Attributes instantiated on create()
+ self.__network = None
+ self.__subnets = list()
+
+ def create(self, cleanup=False):
+ """
+ Responsible for creating not only the network but then a private subnet, router, and an interface to the router.
+ :param cleanup: When true, only perform lookups for OpenStack objects.
+ :return: the created network object or None
+ """
+ try:
+ logger.info('Creating neutron network %s...' % self.network_settings.name)
+ net_inst = neutron_utils.get_network(self.__neutron, self.network_settings.name,
+ self.network_settings.get_project_id(self.__os_creds))
+ if net_inst:
+ self.__network = net_inst
+ else:
+ if not cleanup:
+ self.__network = neutron_utils.create_network(self.__neutron, self.__os_creds,
+ self.network_settings)
+ else:
+ logger.info('Network does not exist and will not create as in cleanup mode')
+ return
+ logger.debug("Network '%s' created successfully" % self.__network['network']['id'])
+
+ logger.debug('Creating Subnets....')
+ for subnet_setting in self.network_settings.subnet_settings:
+ sub_inst = neutron_utils.get_subnet_by_name(self.__neutron, subnet_setting.name)
+ if sub_inst:
+ self.__subnets.append(sub_inst)
+ logger.debug("Subnet '%s' created successfully" % sub_inst['subnet']['id'])
+ else:
+ if not cleanup:
+ self.__subnets.append(neutron_utils.create_subnet(self.__neutron, subnet_setting,
+ self.__os_creds, self.__network))
+
+ return self.__network
+ except Exception as e:
+ logger.error('Unexpected exception thrown while creating network - ' + str(e))
+ self.clean()
+ raise e
+
+ def clean(self):
+ """
+ Removes and deletes all items created in reverse order.
+ """
+ for subnet in self.__subnets:
+ try:
+ logger.info('Deleting subnet with name ' + subnet['subnet']['name'])
+ neutron_utils.delete_subnet(self.__neutron, subnet)
+ except NotFound as e:
+ logger.warn('Error deleting subnet with message - ' + e.message)
+ pass
+ self.__subnets = list()
+
+ if self.__network:
+ try:
+ neutron_utils.delete_network(self.__neutron, self.__network)
+ except NotFound:
+ pass
+
+ self.__network = None
+
+ def get_network(self):
+ """
+ Returns the created OpenStack network object
+ :return: the OpenStack network object
+ """
+ return self.__network
+
+ def get_subnets(self):
+ """
+ Returns the OpenStack subnet objects
+ :return:
+ """
+ return self.__subnets
+
+
+class NetworkSettings:
+ """
+ Class representing a network configuration
+ """
+
+ def __init__(self, config=None, name=None, admin_state_up=True, shared=None, project_name=None,
+ external=False, network_type=None, physical_network=None, subnet_settings=list()):
+ """
+ Constructor - all parameters are optional
+ :param config: Should be a dict object containing the configuration settings using the attribute names below
+ as each member's the key and overrides any of the other parameters.
+ :param name: The network name.
+ :param admin_state_up: The administrative status of the network. True = up / False = down (default True)
+ :param shared: Boolean value indicating whether this network is shared across all projects/tenants. By default,
+ only administrative users can change this value.
+ :param project_name: Admin-only. The name of the project that will own the network. This project can be
+ different from the project that makes the create network request. However, only
+ administrative users can specify a project ID other than their own. You cannot change this
+ value through authorization policies.
+ :param external: when true, will setup an external network (default False).
+ :param network_type: the type of network (i.e. vlan|flat).
+ :param physical_network: the name of the physical network (this is required when network_type is 'flat')
+ :param subnet_settings: List of SubnetSettings objects.
+ :return:
+ """
+
+ self.project_id = None
+
+ if config:
+ self.name = config.get('name')
+ if config.get('admin_state_up') is not None:
+ self.admin_state_up = bool(config['admin_state_up'])
+ else:
+ self.admin_state_up = admin_state_up
+
+ if config.get('shared') is not None:
+ self.shared = bool(config['shared'])
+ else:
+ self.shared = None
+
+ self.project_name = config.get('project_name')
+
+ if config.get('external') is not None:
+ self.external = bool(config.get('external'))
+ else:
+ self.external = external
+
+ self.network_type = config.get('network_type')
+ self.physical_network = config.get('physical_network')
+
+ self.subnet_settings = list()
+ if config.get('subnets'):
+ for subnet_config in config['subnets']:
+ self.subnet_settings.append(SubnetSettings(config=subnet_config['subnet']))
+
+ else:
+ self.name = name
+ self.admin_state_up = admin_state_up
+ self.shared = shared
+ self.project_name = project_name
+ self.external = external
+ self.network_type = network_type
+ self.physical_network = physical_network
+ self.subnet_settings = subnet_settings
+
+ if not self.name or len(self.name) < 1:
+ raise Exception('Name required for networks')
+
+ def get_project_id(self, os_creds):
+ """
+ Returns the project ID for a given project_name or None
+ :param os_creds: the credentials required for keystone client retrieval
+ :return: the ID or None
+ """
+ if self.project_id:
+ return self.project_id
+ else:
+ if self.project_name:
+ keystone = keystone_utils.keystone_client(os_creds)
+ project = keystone_utils.get_project(keystone, self.project_name)
+ if project:
+ return project.id
+
+ return None
+
+ def dict_for_neutron(self, os_creds):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron API
+
+ TODO - expand automated testing to exercise all parameters
+
+ :param os_creds: the OpenStack credentials
+ :return: the dictionary object
+ """
+ out = dict()
+
+ if self.name:
+ out['name'] = self.name
+ if self.admin_state_up is not None:
+ out['admin_state_up'] = self.admin_state_up
+ if self.shared:
+ out['shared'] = self.shared
+ if self.project_name:
+ project_id = self.get_project_id(os_creds)
+ if project_id:
+ out['project_id'] = project_id
+ else:
+ raise Exception('Could not find project ID for project named - ' + self.project_name)
+ if self.network_type:
+ out['provider:network_type'] = self.network_type
+ if self.physical_network:
+ out['provider:physical_network'] = self.physical_network
+ if self.external:
+ out['router:external'] = self.external
+ return {'network': out}
+
+
+class SubnetSettings:
+ """
+ Class representing a subnet configuration
+ """
+
+ def __init__(self, config=None, cidr=None, ip_version=4, name=None, project_name=None, start=None,
+ end=None, gateway_ip=None, enable_dhcp=None, dns_nameservers=None, host_routes=None, destination=None,
+ nexthop=None, ipv6_ra_mode=None, ipv6_address_mode=None):
+ """
+ Constructor - all parameters are optional except cidr (subnet mask)
+ :param config: Should be a dict object containing the configuration settings using the attribute names below
+ as each member's the key and overrides any of the other parameters.
+ :param cidr: The CIDR. REQUIRED if config parameter is None
+ :param ip_version: The IP version, which is 4 or 6.
+ :param name: The subnet name.
+ :param project_name: The name of the project who owns the network. Only administrative users can specify a
+ project ID other than their own. You cannot change this value through authorization
+ policies.
+ :param start: The start address for the allocation pools.
+ :param end: The end address for the allocation pools.
+ :param gateway_ip: The gateway IP address.
+ :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is disabled.
+ :param dns_nameservers: A list of DNS name servers for the subnet. Specify each name server as an IP address
+ and separate multiple entries with a space. For example [8.8.8.7 8.8.8.8].
+ :param host_routes: A list of host route dictionaries for the subnet. For example:
+ "host_routes":[
+ {
+ "destination":"0.0.0.0/0",
+ "nexthop":"123.456.78.9"
+ },
+ {
+ "destination":"192.168.0.0/24",
+ "nexthop":"192.168.0.1"
+ }
+ ]
+ :param destination: The destination for static route
+ :param nexthop: The next hop for the destination.
+ :param ipv6_ra_mode: A valid value is dhcpv6-stateful, dhcpv6-stateless, or slaac.
+ :param ipv6_address_mode: A valid value is dhcpv6-stateful, dhcpv6-stateless, or slaac.
+ :raise: Exception when config does not have or cidr values are None
+ """
+ if not dns_nameservers:
+ dns_nameservers = ['8.8.8.8']
+
+ if config:
+ self.cidr = config['cidr']
+ if config.get('ip_version'):
+ self.ip_version = config['ip_version']
+ else:
+ self.ip_version = ip_version
+
+ # Optional attributes that can be set after instantiation
+ self.name = config.get('name')
+ self.project_name = config.get('project_name')
+ self.start = config.get('start')
+ self.end = config.get('end')
+ self.gateway_ip = config.get('gateway_ip')
+ self.enable_dhcp = config.get('enable_dhcp')
+
+ if config.get('dns_nameservers'):
+ self.dns_nameservers = config.get('dns_nameservers')
+ else:
+ self.dns_nameservers = dns_nameservers
+
+ self.host_routes = config.get('host_routes')
+ self.destination = config.get('destination')
+ self.nexthop = config.get('nexthop')
+ self.ipv6_ra_mode = config.get('ipv6_ra_mode')
+ self.ipv6_address_mode = config.get('ipv6_address_mode')
+ else:
+ # Required attributes
+ self.cidr = cidr
+ self.ip_version = ip_version
+
+ # Optional attributes that can be set after instantiation
+ self.name = name
+ self.project_name = project_name
+ self.start = start
+ self.end = end
+ self.gateway_ip = gateway_ip
+ self.enable_dhcp = enable_dhcp
+ self.dns_nameservers = dns_nameservers
+ self.host_routes = host_routes
+ self.destination = destination
+ self.nexthop = nexthop
+ self.ipv6_ra_mode = ipv6_ra_mode
+ self.ipv6_address_mode = ipv6_address_mode
+
+ if not self.name or not self.cidr:
+ raise Exception('Name and cidr required for subnets')
+
+ def dict_for_neutron(self, os_creds, network=None):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron API
+ :param os_creds: the OpenStack credentials
+ :param network: (Optional) the network object on which the subnet will be created
+ :return: the dictionary object
+ """
+ out = {
+ 'cidr': self.cidr,
+ 'ip_version': self.ip_version,
+ }
+
+ if network:
+ out['network_id'] = network['network']['id']
+ if self.name:
+ out['name'] = self.name
+ if self.project_name:
+ keystone = keystone_utils.keystone_client(os_creds)
+ project = keystone_utils.get_project(keystone, self.project_name)
+ project_id = None
+ if project:
+ project_id = project.id
+ if project_id:
+ out['project_id'] = project_id
+ else:
+ raise Exception('Could not find project ID for project named - ' + self.project_name)
+ if self.start and self.end:
+ out['allocation_pools'] = [{'start': self.start, 'end': self.end}]
+ if self.gateway_ip:
+ out['gateway_ip'] = self.gateway_ip
+ if self.enable_dhcp is not None:
+ out['enable_dhcp'] = self.enable_dhcp
+ if self.dns_nameservers and len(self.dns_nameservers) > 0:
+ out['dns_nameservers'] = self.dns_nameservers
+ if self.host_routes and len(self.host_routes) > 0:
+ out['host_routes'] = self.host_routes
+ if self.destination:
+ out['destination'] = self.destination
+ if self.nexthop:
+ out['nexthop'] = self.nexthop
+ if self.ipv6_ra_mode:
+ out['ipv6_ra_mode'] = self.ipv6_ra_mode
+ if self.ipv6_address_mode:
+ out['ipv6_address_mode'] = self.ipv6_address_mode
+ return out
+
+
+class PortSettings:
+ """
+ Class representing a port configuration
+ """
+
+ def __init__(self, config=None, name=None, network_name=None, admin_state_up=True, project_name=None,
+ mac_address=None, ip_addrs=None, fixed_ips=None, security_groups=None, allowed_address_pairs=None,
+ opt_value=None, opt_name=None, device_owner=None, device_id=None):
+ """
+ Constructor - all parameters are optional
+ :param config: Should be a dict object containing the configuration settings using the attribute names below
+ as each member's the key and overrides any of the other parameters.
+ :param name: A symbolic name for the port.
+ :param network_name: The name of the network on which to create the port.
+ :param admin_state_up: A boolean value denoting the administrative status of the port. True = up / False = down
+ :param project_name: The name of the project who owns the network. Only administrative users can specify a
+ project ID other than their own. You cannot change this value through authorization
+ policies.
+ :param mac_address: The MAC address. If you specify an address that is not valid, a Bad Request (400) status
+ code is returned. If you do not specify a MAC address, OpenStack Networking tries to
+ allocate one. If a failure occurs, a Service Unavailable (503) status code is returned.
+ :param ip_addrs: A list of dict objects where each contains two keys 'subnet_name' and 'ip' values which will
+ get mapped to self.fixed_ips.
+ These values will be directly translated into the fixed_ips dict
+ :param fixed_ips: A dict where the key is the subnet IDs and value is the IP address to assign to the port
+ :param security_groups: One or more security group IDs.
+ :param allowed_address_pairs: A dictionary containing a set of zero or more allowed address pairs. An address
+ pair contains an IP address and MAC address.
+ :param opt_value: The extra DHCP option value.
+ :param opt_name: The extra DHCP option name.
+ :param device_owner: The ID of the entity that uses this port. For example, a DHCP agent.
+ :param device_id: The ID of the device that uses this port. For example, a virtual server.
+ :return:
+ """
+ self.network = None
+
+ if config:
+ self.name = config.get('name')
+ self.network_name = config.get('network_name')
+
+ if config.get('admin_state_up') is not None:
+ self.admin_state_up = bool(config['admin_state_up'])
+ else:
+ self.admin_state_up = admin_state_up
+
+ self.project_name = config.get('project_name')
+ self.mac_address = config.get('mac_address')
+ self.ip_addrs = config.get('ip_addrs')
+ self.fixed_ips = config.get('fixed_ips')
+ self.security_groups = config.get('security_groups')
+ self.allowed_address_pairs = config.get('allowed_address_pairs')
+ self.opt_value = config.get('opt_value')
+ self.opt_name = config.get('opt_name')
+ self.device_owner = config.get('device_owner')
+ self.device_id = config.get('device_id')
+ else:
+ self.name = name
+ self.network_name = network_name
+ self.admin_state_up = admin_state_up
+ self.project_name = project_name
+ self.mac_address = mac_address
+ self.ip_addrs = ip_addrs
+ self.fixed_ips = fixed_ips
+ self.security_groups = security_groups
+ self.allowed_address_pairs = allowed_address_pairs
+ self.opt_value = opt_value
+ self.opt_name = opt_name
+ self.device_owner = device_owner
+ self.device_id = device_id
+
+ if not self.name or not self.network_name:
+ raise Exception('The attributes neutron, name, and network_name are required for PortSettings')
+
+ def __set_fixed_ips(self, neutron):
+ """
+ Sets the self.fixed_ips value
+ :param neutron: the Neutron client
+ :return: None
+ """
+ if not self.fixed_ips and self.ip_addrs:
+ self.fixed_ips = list()
+
+ for ip_addr_dict in self.ip_addrs:
+ subnet = neutron_utils.get_subnet_by_name(neutron, ip_addr_dict['subnet_name'])
+ if subnet:
+ self.fixed_ips.append({'ip_address': ip_addr_dict['ip'], 'subnet_id': subnet['subnet']['id']})
+ else:
+ raise Exception('Invalid port configuration, subnet does not exist with name - ' +
+ ip_addr_dict['subnet_name'])
+
+ def dict_for_neutron(self, neutron, os_creds):
+ """
+ Returns a dictionary object representing this object.
+ This is meant to be converted into JSON designed for use by the Neutron API
+
+ TODO - expand automated testing to exercise all parameters
+ :param neutron: the Neutron client
+ :param os_creds: the OpenStack credentials
+ :return: the dictionary object
+ """
+ self.__set_fixed_ips(neutron)
+
+ out = dict()
+
+ project_id = None
+ if self.project_name:
+ keystone = keystone_utils.keystone_client(os_creds)
+ project = keystone_utils.get_project(keystone, self.project_name)
+ if project:
+ project_id = project.id
+
+ if not self.network:
+ self.network = neutron_utils.get_network(neutron, self.network_name, project_id)
+ if not self.network:
+ raise Exception('Cannot locate network with name - ' + self.network_name)
+
+ out['network_id'] = self.network['network']['id']
+
+ if self.admin_state_up is not None:
+ out['admin_state_up'] = self.admin_state_up
+ if self.name:
+ out['name'] = self.name
+ if self.project_name:
+ if project_id:
+ out['project_id'] = project_id
+ else:
+ raise Exception('Could not find project ID for project named - ' + self.project_name)
+ if self.mac_address:
+ out['mac_address'] = self.mac_address
+ if self.fixed_ips and len(self.fixed_ips) > 0:
+ out['fixed_ips'] = self.fixed_ips
+ if self.security_groups:
+ out['security_groups'] = self.security_groups
+ if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0:
+ out['allowed_address_pairs'] = self.allowed_address_pairs
+ if self.opt_value:
+ out['opt_value'] = self.opt_value
+ if self.opt_name:
+ out['opt_name'] = self.opt_name
+ if self.device_owner:
+ out['device_owner'] = self.device_owner
+ if self.device_id:
+ out['device_id'] = self.device_id
+ return {'port': out}