+#!/usr/bin/python
+
+#
+# Copyright 2017 RIFT.IO Inc
+#
+# 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
+import ipaddress
+from neutronclient.neutron import client as ntclient
+
+import neutronclient.common.exceptions as NeutronException
+
+
+class NeutronAPIVersionException(Exception):
+ def __init__(self, errors):
+ self.errors = errors
+ super(NeutronAPIVersionException, self).__init__("Multiple Exception Received")
+
+ def __str__(self):
+ return self.__repr__()
+
+ def __repr__(self):
+ msg = "{} : Following Exception(s) have occured during Neutron API discovery".format(self.__class__)
+ for n,e in enumerate(self.errors):
+ msg += "\n"
+ msg += " {}: {}".format(n, str(e))
+ return msg
+
+
+class NeutronDriver(object):
+ """
+ NeutronDriver Class for network orchestration
+ """
+ ### List of supported API versions in prioritized order
+ supported_versions = ["2"]
+
+ def __init__(self,
+ sess_handle,
+ region_name = 'RegionOne',
+ service_type = 'network',
+ logger = None):
+ """
+ Constructor for NeutronDriver class
+ Arguments:
+ sess_handle (instance of class SessionDriver)
+ region_name (string): Region Name
+ service_type(string): Type of service in service catalog
+ logger (instance of logging.Logger)
+ """
+
+ if logger is None:
+ self.log = logging.getLogger('rwcal.openstack.neutron')
+ self.log.setLevel(logging.DEBUG)
+ else:
+ self.log = logger
+
+ self._sess_handle = sess_handle
+
+ #### Attempt to use API versions in prioritized order defined in
+ #### NeutronDriver.supported_versions
+ def select_version(version):
+ try:
+ self.log.info("Attempting to use Neutron v%s APIs", version)
+ ntdrv = ntclient.Client(api_version = version,
+ region_name = region_name,
+ service_type = service_type,
+ session = self._sess_handle.session,
+ logger = self.log)
+ except Exception as e:
+ self.log.info(str(e))
+ raise
+ else:
+ self.log.info("Neutron API v%s selected", version)
+ return (version, ntdrv)
+
+ errors = []
+ for v in NeutronDriver.supported_versions:
+ try:
+ (self._version, self._nt_drv) = select_version(v)
+ except Exception as e:
+ errors.append(e)
+ else:
+ break
+ else:
+ raise NeutronAPIVersionException(errors)
+
+ @property
+ def neutron_endpoint(self):
+ return self._nt_drv.get_auth_info()['endpoint_url']
+
+ @property
+ def project_id(self):
+ return self._sess_handle.project_id
+
+ @property
+ def neutron_quota(self):
+ """
+ Returns Neutron Quota (a dictionary) for project
+ """
+ try:
+ quota = self._nt_drv.show_quota(self.project_id)
+ except Exception as e:
+ self.log.exception("Get Neutron quota operation failed. Exception: %s", str(e))
+ raise
+ return quota
+
+ def extensions_list(self):
+ """
+ Returns a list of available neutron extensions.
+ Arguments:
+ None
+ Returns:
+ A list of dictionaries. Each dictionary contains attributes for a single Neutron extension
+ """
+ try:
+ extensions = self._nt_drv.list_extensions()
+ except Exception as e:
+ self.log.exception("List extension operation failed. Exception: %s", str(e))
+ raise
+ if 'extensions' in extensions:
+ return extensions['extensions']
+ return list()
+
+
+ def _get_neutron_connection(self):
+ """
+ Returns instance of object neutronclient.neutron.client.Client
+ Use for DEBUG ONLY
+ """
+ return self._nt_drv
+
+ def _network_find(self, **kwargs):
+ """
+ Returns a network object dictionary based on the filters provided in kwargs
+
+ Arguments:
+ kwargs (dictionary): A dictionary of key-value pair filters
+
+ Returns:
+ One or more dictionary object associated with network
+ """
+ try:
+ networks = self._nt_drv.list_networks(**kwargs)['networks']
+ except Exception as e:
+ self.log.exception("List network operation failed. Exception: %s", str(e))
+ raise
+ return networks
+
+ def network_list(self):
+ """
+ Returns list of dictionaries. Each dictionary contains the attributes for a network
+ under project
+
+ Arguments: None
+
+ Returns:
+ A list of dictionaries
+ """
+ return self._network_find(**{'tenant_id':self.project_id}) + self._network_find(**{'shared':True})
+
+
+ def network_create(self, **kwargs):
+ """
+ Creates a new network for the project
+
+ Arguments:
+ A dictionary with following key-values
+ {
+ name (string) : Name of the network
+ admin_state_up(Boolean) : True/False (Defaults: True)
+ external_router(Boolean) : Connectivity with external router. True/False (Defaults: False)
+ shared(Boolean) : Shared among tenants. True/False (Defaults: False)
+ physical_network(string) : The physical network where this network object is implemented (optional).
+ network_type : The type of physical network that maps to this network resource (optional).
+ Possible values are: 'flat', 'vlan', 'vxlan', 'gre'
+ segmentation_id : An isolated segment on the physical network. The network_type attribute
+ defines the segmentation model. For example, if the network_type value
+ is vlan, this ID is a vlan identifier. If the network_type value is gre,
+ this ID is a gre key.
+ }
+ """
+ params = {'network':
+ {'name' : kwargs['name'],
+ 'admin_state_up' : kwargs['admin_state_up'],
+ 'tenant_id' : self.project_id,
+ 'shared' : kwargs['shared'],
+ #'port_security_enabled': port_security_enabled,
+ 'router:external' : kwargs['external_router']}}
+
+ if 'physical_network' in kwargs:
+ params['network']['provider:physical_network'] = kwargs['physical_network']
+ if 'network_type' in kwargs:
+ params['network']['provider:network_type'] = kwargs['network_type']
+ if 'segmentation_id' in kwargs:
+ params['network']['provider:segmentation_id'] = kwargs['segmentation_id']
+
+ try:
+ self.log.debug("Calling neutron create_network() with params: %s", str(params))
+ net = self._nt_drv.create_network(params)
+ except Exception as e:
+ self.log.exception("Create Network operation failed. Exception: %s", str(e))
+ raise
+
+ network_id = net['network']['id']
+ if not network_id:
+ raise Exception("Empty network id returned from create_network. (params: %s)" % str(params))
+
+ return network_id
+
+ def network_delete(self, network_id):
+ """
+ Deletes a network identified by network_id
+
+ Arguments:
+ network_id (string): UUID of the network
+
+ Returns: None
+ """
+ try:
+ self._nt_drv.delete_network(network_id)
+ except Exception as e:
+ self.log.exception("Delete Network operation failed. Exception: %s",str(e))
+ raise
+
+
+ def network_get(self, network_id='', network_name=''):
+ """
+ Returns a dictionary object describing the attributes of the network
+
+ Arguments:
+ network_id (string): UUID of the network
+
+ Returns:
+ A dictionary object of the network attributes
+ """
+ networks = self._network_find(**{'id': network_id, 'name': network_name})
+ if not networks:
+ raise NeutronException.NotFound("Could not find network. Network id: %s, Network name: %s " %(network_id, network_name))
+ return networks[0]
+
+
+ def subnet_create(self, **kwargs):
+ """
+ Creates a subnet on the network
+
+ Arguments:
+ A dictionary with following key value pairs
+ {
+ network_id(string) : UUID of the network where subnet needs to be created
+ subnet_cidr(string) : IPv4 address prefix (e.g. '1.1.1.0/24') for the subnet
+ ip_version (integer): 4 for IPv4 and 6 for IPv6
+
+ }
+
+ Returns:
+ subnet_id (string): UUID of the created subnet
+ """
+ params = {}
+ params['network_id'] = kwargs['network_id']
+ params['ip_version'] = kwargs['ip_version']
+
+ # if params['ip_version'] == 6:
+ # assert 0, "IPv6 is not supported"
+
+ if 'subnetpool_id' in kwargs:
+ params['subnetpool_id'] = kwargs['subnetpool_id']
+ else:
+ params['cidr'] = kwargs['cidr']
+
+ if 'gateway_ip' in kwargs:
+ params['gateway_ip'] = kwargs['gateway_ip']
+ else:
+ params['gateway_ip'] = None
+
+ if 'dhcp_params' in kwargs:
+ params['enable_dhcp'] = kwargs['dhcp_params']['enable_dhcp']
+ if 'start_address' in kwargs['dhcp_params'] and 'count' in kwargs['dhcp_params']:
+ end_address = (ipaddress.IPv4Address(kwargs['dhcp_params']['start_address']) + kwargs['dhcp_params']['count']).compressed
+ params['allocation_pools'] = [ {'start': kwargs['dhcp_params']['start_address'] ,
+ 'end' : end_address} ]
+
+ if 'dns_server' in kwargs:
+ params['dns_nameservers'] = []
+ for server in kwargs['dns_server']:
+ params['dns_nameservers'].append(server)
+
+ try:
+ subnet = self._nt_drv.create_subnet({'subnets': [params]})
+ except Exception as e:
+ self.log.exception("Create Subnet operation failed. Exception: %s",str(e))
+ raise
+
+ return subnet['subnets'][0]['id']
+
+ def subnet_list(self, **kwargs):
+ """
+ Returns a list of dictionaries. Each dictionary contains attributes describing the subnet
+
+ Arguments: None
+
+ Returns:
+ A dictionary of the objects of subnet attributes
+ """
+ try:
+ subnets = self._nt_drv.list_subnets(**kwargs)['subnets']
+ except Exception as e:
+ self.log.exception("List Subnet operation failed. Exception: %s", str(e))
+ raise
+ return subnets
+
+ def _subnet_get(self, subnet_id):
+ """
+ Returns a dictionary object describing the attributes of a subnet.
+
+ Arguments:
+ subnet_id (string): UUID of the subnet
+
+ Returns:
+ A dictionary object of the subnet attributes
+ """
+ subnets = self._nt_drv.list_subnets(id=subnet_id)
+ if not subnets['subnets']:
+ self.log.error("Get subnet operation failed for subnet_id: %s", subnet_id)
+ #raise NeutronException.NotFound("Could not find subnet_id %s" %(subnet_id))
+ return {'cidr': ''}
+ else:
+ return subnets['subnets'][0]
+
+ def subnet_get(self, subnet_id):
+ """
+ Returns a dictionary object describing the attributes of a subnet.
+
+ Arguments:
+ subnet_id (string): UUID of the subnet
+
+ Returns:
+ A dictionary object of the subnet attributes
+ """
+ return self._subnet_get(subnet_id)
+
+ def subnet_delete(self, subnet_id):
+ """
+ Deletes a subnet identified by subnet_id
+
+ Arguments:
+ subnet_id (string): UUID of the subnet to be deleted
+
+ Returns: None
+ """
+ assert subnet_id == self._subnet_get(self,subnet_id)
+ try:
+ self._nt_drv.delete_subnet(subnet_id)
+ except Exception as e:
+ self.log.exception("Delete Subnet operation failed for subnet_id : %s. Exception: %s", subnet_id, str(e))
+ raise
+
+ def port_list(self, **kwargs):
+ """
+ Returns a list of dictionaries. Each dictionary contains attributes describing the port
+
+ Arguments:
+ kwargs (dictionary): A dictionary for filters for port_list operation
+
+ Returns:
+ A dictionary of the objects of port attributes
+
+ """
+ ports = []
+
+ kwargs['tenant_id'] = self.project_id
+
+ try:
+ ports = self._nt_drv.list_ports(**kwargs)
+ except Exception as e:
+ self.log.exception("List Port operation failed. Exception: %s",str(e))
+ raise
+ return ports['ports']
+
+ def port_create(self, ports):
+ """
+ Create a port in network
+
+ Arguments:
+ Ports
+ List of dictionaries of following
+ {
+ name (string) : Name of the port
+ network_id(string) : UUID of the network_id identifying the network to which port belongs
+ ip_address(string) : (Optional) Static IP address to assign to the port
+ vnic_type(string) : Possible values are "normal", "direct", "macvtap"
+ admin_state_up : True/False
+ port_security_enabled : True/False
+ security_groups : A List of Neutron security group Ids
+ }
+ Returns:
+ A list of port_id (string)
+ """
+ params = dict()
+ params['ports'] = ports
+ self.log.debug("Port create params: {}".format(params))
+ try:
+ ports = self._nt_drv.create_port(params)
+ except Exception as e:
+ self.log.exception("Ports Create operation failed. Exception: %s",str(e))
+ raise
+ return [ p['id'] for p in ports['ports'] ]
+
+
+ def port_update(self, port_id, no_security_groups=None,port_security_enabled=None):
+ """
+ Update a port in network
+ """
+ params = {}
+ params["port"] = {}
+ if no_security_groups:
+ params["port"]["security_groups"] = []
+ if port_security_enabled == False:
+ params["port"]["port_security_enabled"] = False
+ elif port_security_enabled == True:
+ params["port"]["port_security_enabled"] = True
+
+ try:
+ port = self._nt_drv.update_port(port_id,params)
+ except Exception as e:
+ self.log.exception("Port Update operation failed. Exception: %s", str(e))
+ raise
+ return port['port']['id']
+
+ def _port_get(self, port_id):
+ """
+ Returns a dictionary object describing the attributes of the port
+
+ Arguments:
+ port_id (string): UUID of the port
+
+ Returns:
+ A dictionary object of the port attributes
+ """
+ port = self._nt_drv.list_ports(id=port_id)['ports']
+ if not port:
+ raise NeutronException.NotFound("Could not find port_id %s" %(port_id))
+ return port[0]
+
+ def port_get(self, port_id):
+ """
+ Returns a dictionary object describing the attributes of the port
+
+ Arguments:
+ port_id (string): UUID of the port
+
+ Returns:
+ A dictionary object of the port attributes
+ """
+ return self._port_get(port_id)
+
+ def port_delete(self, port_id):
+ """
+ Deletes a port identified by port_id
+
+ Arguments:
+ port_id (string) : UUID of the port
+
+ Returns: None
+ """
+ assert port_id == self._port_get(port_id)['id']
+ try:
+ self._nt_drv.delete_port(port_id)
+ except Exception as e:
+ self.log.exception("Port Delete operation failed for port_id : %s. Exception: %s",port_id, str(e))
+ raise
+
+ def security_group_list(self, **kwargs):
+ """
+ Returns a list of dictionaries. Each dictionary contains attributes describing the security group
+
+ Arguments:
+ None
+
+ Returns:
+ A dictionary of the objects of security group attributes
+ """
+ try:
+ kwargs['tenant_id'] = self.project_id
+ group_list = self._nt_drv.list_security_groups(**kwargs)
+ except Exception as e:
+ self.log.exception("List Security group operation, Exception: %s", str(e))
+ raise
+ return group_list['security_groups']
+
+
+ def subnetpool_list(self, **kwargs):
+ """
+ Returns a list of dictionaries. Each dictionary contains attributes describing a subnet prefix pool
+
+ Arguments:
+ None
+
+ Returns:
+ A dictionary of the objects of subnet prefix pool
+ """
+ try:
+ pool_list = self._nt_drv.list_subnetpools(**kwargs)
+ except Exception as e:
+ self.log.exception("List SubnetPool operation, Exception: %s",str(e))
+ raise
+
+ if 'subnetpools' in pool_list:
+ return pool_list['subnetpools']
+ else:
+ return []
+