Merge "Add osm-im to the source install script"
[osm/RO.git] / osm_ro / vimconn_openstack.py
index c66d74c..e3d3334 100644 (file)
 ##
 
 '''
 ##
 
 '''
-osconnector implements all the methods to interact with openstack using the python-client.
+osconnector implements all the methods to interact with openstack using the python-neutronclient.
+
+For the VNF forwarding graph, The OpenStack VIM connector calls the
+networking-sfc Neutron extension methods, whose resources are mapped
+to the VIM connector's SFC resources as follows:
+- Classification (OSM) -> Flow Classifier (Neutron)
+- Service Function Instance (OSM) -> Port Pair (Neutron)
+- Service Function (OSM) -> Port Pair Group (Neutron)
+- Service Function Path (OSM) -> Port Chain (Neutron)
 '''
 '''
-__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research"
-__date__ ="$22-jun-2014 11:19:29$"
+__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C."
+__date__  = "$22-sep-2017 23:59:59$"
 
 import vimconn
 import json
 
 import vimconn
 import json
-import yaml
 import logging
 import netaddr
 import time
 import yaml
 import random
 import logging
 import netaddr
 import time
 import yaml
 import random
+import sys
+import re
+import copy
 
 from novaclient import client as nClient, exceptions as nvExceptions
 from keystoneauth1.identity import v2, v3
 
 from novaclient import client as nClient, exceptions as nvExceptions
 from keystoneauth1.identity import v2, v3
@@ -50,8 +60,11 @@ from httplib import HTTPException
 from neutronclient.neutron import client as neClient
 from neutronclient.common import exceptions as neExceptions
 from requests.exceptions import ConnectionError
 from neutronclient.neutron import client as neClient
 from neutronclient.common import exceptions as neExceptions
 from requests.exceptions import ConnectionError
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+
 
 
-'''contain the openstack virtual machine status to openmano status'''
+"""contain the openstack virtual machine status to openmano status"""
 vmStatus2manoFormat={'ACTIVE':'ACTIVE',
                      'PAUSED':'PAUSED',
                      'SUSPENDED': 'SUSPENDED',
 vmStatus2manoFormat={'ACTIVE':'ACTIVE',
                      'PAUSED':'PAUSED',
                      'SUSPENDED': 'SUSPENDED',
@@ -62,6 +75,8 @@ vmStatus2manoFormat={'ACTIVE':'ACTIVE',
 netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
                      }
 
 netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
                      }
 
+supportedClassificationTypes = ['legacy_flow_classifier']
+
 #global var to have a timeout creating and deleting volumes
 volume_timeout = 60
 server_timeout = 300
 #global var to have a timeout creating and deleting volumes
 volume_timeout = 60
 server_timeout = 300
@@ -77,12 +92,21 @@ class vimconnector(vimconn.vimconnector):
         if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
             raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
                                            "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
         if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
             raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
                                            "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
+        vim_type = config.get('vim_type')
+        if vim_type and vim_type not in ('vio', 'VIO'):
+            raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
+                            "Allowed values are 'vio' or 'VIO'".format(vim_type))
+
+        if config.get('dataplane_net_vlan_range') is not None:
+            #validate vlan ranges provided by user
+            self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'))
+
         vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
                                       config)
 
         self.insecure = self.config.get("insecure", False)
         if not url:
         vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
                                       config)
 
         self.insecure = self.config.get("insecure", False)
         if not url:
-            raise TypeError, 'url param can not be NoneType'
+            raise TypeError('url param can not be NoneType')
         self.persistent_info = persistent_info
         self.availability_zone = persistent_info.get('availability_zone', None)
         self.session = persistent_info.get('session', {'reload_client': True})
         self.persistent_info = persistent_info
         self.availability_zone = persistent_info.get('availability_zone', None)
         self.session = persistent_info.get('session', {'reload_client': True})
@@ -93,10 +117,22 @@ class vimconnector(vimconn.vimconnector):
         self.glancev1 = self.session.get('glancev1')
         self.keystone = self.session.get('keystone')
         self.api_version3 = self.session.get('api_version3')
         self.glancev1 = self.session.get('glancev1')
         self.keystone = self.session.get('keystone')
         self.api_version3 = self.session.get('api_version3')
+        self.vim_type = self.config.get("vim_type")
+        if self.vim_type:
+            self.vim_type = self.vim_type.upper()
+        if self.config.get("use_internal_endpoint"):
+            self.endpoint_type = "internalURL"
+        else:
+            self.endpoint_type = None
 
         self.logger = logging.getLogger('openmano.vim.openstack')
 
         self.logger = logging.getLogger('openmano.vim.openstack')
+
+        ####### VIO Specific Changes #########
+        if self.vim_type == "VIO":
+            self.logger = logging.getLogger('openmano.vim.vio')
+
         if log_level:
         if log_level:
-            self.logger.setLevel( getattr(logging, log_level) )
+            self.logger.setLevel( getattr(logging, log_level))
 
     def __getitem__(self, index):
         """Get individuals parameters.
 
     def __getitem__(self, index):
         """Get individuals parameters.
@@ -146,9 +182,9 @@ class vimconnector(vimconn.vimconnector):
                                    tenant_id=self.tenant_id)
             sess = session.Session(auth=auth, verify=not self.insecure)
             if self.api_version3:
                                    tenant_id=self.tenant_id)
             sess = session.Session(auth=auth, verify=not self.insecure)
             if self.api_version3:
-                self.keystone = ksClient_v3.Client(session=sess)
+                self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
             else:
             else:
-                self.keystone = ksClient_v2.Client(session=sess)
+                self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
             self.session['keystone'] = self.keystone
             # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
             # This implementation approach is due to the warning message in
             self.session['keystone'] = self.keystone
             # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
             # This implementation approach is due to the warning message in
@@ -159,11 +195,18 @@ class vimconnector(vimconn.vimconnector):
             version = self.config.get("microversion")
             if not version:
                 version = "2.1"
             version = self.config.get("microversion")
             if not version:
                 version = "2.1"
-            self.nova = self.session['nova'] = nClient.Client(str(version), session=sess)
-            self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess)
-            self.cinder = self.session['cinder'] = cClient.Client(2, session=sess)
-            self.glance = self.session['glance'] = glClient.Client(2, session=sess)
-            self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess)
+            self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type)
+            self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
+            self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
+            if self.endpoint_type == "internalURL":
+                glance_service_id = self.keystone.services.list(name="glance")[0].id
+                glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
+            else:
+                glance_endpoint = None
+            self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
+            #using version 1 of glance client in new_image()
+            self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
+                                                                       endpoint=glance_endpoint)
             self.session['reload_client'] = False
             self.persistent_info['session'] = self.session
             # add availablity zone info inside  self.persistent_info
             self.session['reload_client'] = False
             self.persistent_info['session'] = self.session
             # add availablity zone info inside  self.persistent_info
@@ -185,13 +228,117 @@ class vimconnector(vimconn.vimconnector):
             else:
                 net['type']='bridge'
 
             else:
                 net['type']='bridge'
 
+    def __classification_os2mano(self, class_list_dict):
+        """Transform the openstack format (Flow Classifier) to mano format
+        (Classification) class_list_dict can be a list of dict or a single dict
+        """
+        if isinstance(class_list_dict, dict):
+            class_list_ = [class_list_dict]
+        elif isinstance(class_list_dict, list):
+            class_list_ = class_list_dict
+        else:
+            raise TypeError(
+                "param class_list_dict must be a list or a dictionary")
+        for classification in class_list_:
+            id = classification.pop('id')
+            name = classification.pop('name')
+            description = classification.pop('description')
+            project_id = classification.pop('project_id')
+            tenant_id = classification.pop('tenant_id')
+            original_classification = copy.deepcopy(classification)
+            classification.clear()
+            classification['ctype'] = 'legacy_flow_classifier'
+            classification['definition'] = original_classification
+            classification['id'] = id
+            classification['name'] = name
+            classification['description'] = description
+            classification['project_id'] = project_id
+            classification['tenant_id'] = tenant_id
+
+    def __sfi_os2mano(self, sfi_list_dict):
+        """Transform the openstack format (Port Pair) to mano format (SFI)
+        sfi_list_dict can be a list of dict or a single dict
+        """
+        if isinstance(sfi_list_dict, dict):
+            sfi_list_ = [sfi_list_dict]
+        elif isinstance(sfi_list_dict, list):
+            sfi_list_ = sfi_list_dict
+        else:
+            raise TypeError(
+                "param sfi_list_dict must be a list or a dictionary")
+        for sfi in sfi_list_:
+            sfi['ingress_ports'] = []
+            sfi['egress_ports'] = []
+            if sfi.get('ingress'):
+                sfi['ingress_ports'].append(sfi['ingress'])
+            if sfi.get('egress'):
+                sfi['egress_ports'].append(sfi['egress'])
+            del sfi['ingress']
+            del sfi['egress']
+            params = sfi.get('service_function_parameters')
+            sfc_encap = False
+            if params:
+                correlation = params.get('correlation')
+                if correlation:
+                    sfc_encap = True
+            sfi['sfc_encap'] = sfc_encap
+            del sfi['service_function_parameters']
+
+    def __sf_os2mano(self, sf_list_dict):
+        """Transform the openstack format (Port Pair Group) to mano format (SF)
+        sf_list_dict can be a list of dict or a single dict
+        """
+        if isinstance(sf_list_dict, dict):
+            sf_list_ = [sf_list_dict]
+        elif isinstance(sf_list_dict, list):
+            sf_list_ = sf_list_dict
+        else:
+            raise TypeError(
+                "param sf_list_dict must be a list or a dictionary")
+        for sf in sf_list_:
+            del sf['port_pair_group_parameters']
+            sf['sfis'] = sf['port_pairs']
+            del sf['port_pairs']
+
+    def __sfp_os2mano(self, sfp_list_dict):
+        """Transform the openstack format (Port Chain) to mano format (SFP)
+        sfp_list_dict can be a list of dict or a single dict
+        """
+        if isinstance(sfp_list_dict, dict):
+            sfp_list_ = [sfp_list_dict]
+        elif isinstance(sfp_list_dict, list):
+            sfp_list_ = sfp_list_dict
+        else:
+            raise TypeError(
+                "param sfp_list_dict must be a list or a dictionary")
+        for sfp in sfp_list_:
+            params = sfp.pop('chain_parameters')
+            sfc_encap = False
+            if params:
+                correlation = params.get('correlation')
+                if correlation:
+                    sfc_encap = True
+            sfp['sfc_encap'] = sfc_encap
+            sfp['spi'] = sfp.pop('chain_id')
+            sfp['classifications'] = sfp.pop('flow_classifiers')
+            sfp['service_functions'] = sfp.pop('port_pair_groups')
+
+    # placeholder for now; read TODO note below
+    def _validate_classification(self, type, definition):
+        # only legacy_flow_classifier Type is supported at this point
+        return True
+        # TODO(igordcard): this method should be an abstract method of an
+        # abstract Classification class to be implemented by the specific
+        # Types. Also, abstract vimconnector should call the validation
+        # method before the implemented VIM connectors are called.
+
     def _format_exception(self, exception):
         '''Transform a keystone, nova, neutron  exception into a vimconn exception'''
         if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
                                   ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
                                   )):
     def _format_exception(self, exception):
         '''Transform a keystone, nova, neutron  exception into a vimconn exception'''
         if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
                                   ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
                                   )):
-            raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))            
-        elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException, 
+            raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
+        elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
                                     neExceptions.NeutronException, nvExceptions.BadRequest)):
             raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
         elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
                                     neExceptions.NeutronException, nvExceptions.BadRequest)):
             raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
         elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
@@ -270,6 +417,19 @@ class vimconnector(vimconn.vimconnector):
                 network_dict["provider:network_type"]     = "vlan"
                 if vlan!=None:
                     network_dict["provider:network_type"] = vlan
                 network_dict["provider:network_type"]     = "vlan"
                 if vlan!=None:
                     network_dict["provider:network_type"] = vlan
+
+                ####### VIO Specific Changes #########
+                if self.vim_type == "VIO":
+                    if vlan is not None:
+                        network_dict["provider:segmentation_id"] = vlan
+                    else:
+                        if self.config.get('dataplane_net_vlan_range') is None:
+                            raise vimconn.vimconnConflictException("You must provide "\
+                                "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
+                                "at config value before creating sriov network with vlan tag")
+
+                        network_dict["provider:segmentation_id"] = self._genrate_vlanID()
+
             network_dict["shared"]=shared
             new_net=self.neutron.create_network({'network':network_dict})
             #print new_net
             network_dict["shared"]=shared
             new_net=self.neutron.create_network({'network':network_dict})
             #print new_net
@@ -280,7 +440,7 @@ class vimconnector(vimconn.vimconnector):
                 #Fake subnet is required
                 subnet_rand = random.randint(0, 255)
                 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
                 #Fake subnet is required
                 subnet_rand = random.randint(0, 255)
                 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
-            if 'ip_version' not in ip_profile: 
+            if 'ip_version' not in ip_profile:
                 ip_profile['ip_version'] = "IPv4"
             subnet = {"name":net_name+"-subnet",
                     "network_id": new_net["network"]["id"],
                 ip_profile['ip_version'] = "IPv4"
             subnet = {"name":net_name+"-subnet",
                     "network_id": new_net["network"]["id"],
@@ -393,7 +553,7 @@ class vimconnector(vimconn.vimconnector):
                     error_msg:  #Text with VIM error message, if any. Or the VIM connection ERROR 
                     vim_info:   #Text with plain information obtained from vim (yaml.safe_dump)
 
                     error_msg:  #Text with VIM error message, if any. Or the VIM connection ERROR 
                     vim_info:   #Text with plain information obtained from vim (yaml.safe_dump)
 
-        '''        
+        '''
         net_dict={}
         for net_id in net_list:
             net = {}
         net_dict={}
         for net_id in net_list:
             net = {}
@@ -404,7 +564,7 @@ class vimconnector(vimconn.vimconnector):
                 else:
                     net["status"] = "OTHER"
                     net["error_msg"] = "VIM status reported " + net_vim['status']
                 else:
                     net["status"] = "OTHER"
                     net["error_msg"] = "VIM status reported " + net_vim['status']
-                    
+
                 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
                     net['status'] = 'DOWN'
                 try:
                 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
                     net['status'] = 'DOWN'
                 try:
@@ -499,7 +659,7 @@ class vimconnector(vimconn.vimconnector):
                     while name in fl_names:
                         name_suffix += 1
                         name = flavor_data['name']+"-" + str(name_suffix)
                     while name in fl_names:
                         name_suffix += 1
                         name = flavor_data['name']+"-" + str(name_suffix)
-                        
+
                 ram = flavor_data.get('ram',64)
                 vcpus = flavor_data.get('vcpus',1)
                 numa_properties=None
                 ram = flavor_data.get('ram',64)
                 vcpus = flavor_data.get('vcpus',1)
                 numa_properties=None
@@ -515,6 +675,9 @@ class vimconnector(vimconn.vimconnector):
                         numa_properties["hw:mem_page_size"] = "large"
                         numa_properties["hw:cpu_policy"] = "dedicated"
                         numa_properties["hw:numa_mempolicy"] = "strict"
                         numa_properties["hw:mem_page_size"] = "large"
                         numa_properties["hw:cpu_policy"] = "dedicated"
                         numa_properties["hw:numa_mempolicy"] = "strict"
+                        if self.vim_type == "VIO":
+                            numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
+                            numa_properties["vmware:latency_sensitivity_level"] = "high"
                         for numa in numas:
                             #overwrite ram and vcpus
                             ram = numa['memory']*1024
                         for numa in numas:
                             #overwrite ram and vcpus
                             ram = numa['memory']*1024
@@ -538,14 +701,14 @@ class vimconnector(vimconn.vimconnector):
                             #     if interface["dedicated"]=="yes":
                             #         raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
                             #     #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
                             #     if interface["dedicated"]=="yes":
                             #         raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
                             #     #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
-                                
+
                 #create flavor                 
                 #create flavor                 
-                new_flavor=self.nova.flavors.create(name, 
-                                ram, 
-                                vcpus, 
+                new_flavor=self.nova.flavors.create(name,
+                                ram,
+                                vcpus,
                                 flavor_data.get('disk',1),
                                 is_public=flavor_data.get('is_public', True)
                                 flavor_data.get('disk',1),
                                 is_public=flavor_data.get('is_public', True)
-                            ) 
+                            )
                 #add metadata
                 if numa_properties:
                     new_flavor.set_keys(numa_properties)
                 #add metadata
                 if numa_properties:
                     new_flavor.set_keys(numa_properties)
@@ -633,7 +796,7 @@ class vimconnector(vimconn.vimconnector):
             except IOError as e:  #can not open the file
                 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
                                                          http_code=vimconn.HTTP_Bad_Request)
             except IOError as e:  #can not open the file
                 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
                                                          http_code=vimconn.HTTP_Bad_Request)
-     
+
     def delete_image(self, image_id):
         '''Deletes a tenant image from openstack VIM. Returns the old id
         '''
     def delete_image(self, image_id):
         '''Deletes a tenant image from openstack VIM. Returns the old id
         '''
@@ -645,7 +808,7 @@ class vimconnector(vimconn.vimconnector):
             self._format_exception(e)
 
     def get_image_id_from_path(self, path):
             self._format_exception(e)
 
     def get_image_id_from_path(self, path):
-        '''Get the image id from image path in the VIM database. Returns the image_id''' 
+        '''Get the image id from image path in the VIM database. Returns the image_id'''
         try:
             self._reload_connection()
             images = self.nova.images.list()
         try:
             self._reload_connection()
             images = self.nova.images.list()
@@ -655,7 +818,7 @@ class vimconnector(vimconn.vimconnector):
             raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
         except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
             self._format_exception(e)
             raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
         except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
             self._format_exception(e)
-        
+
     def get_image_list(self, filter_dict={}):
         '''Obtain tenant images from VIM
         Filter_dict can be:
     def get_image_list(self, filter_dict={}):
         '''Obtain tenant images from VIM
         Filter_dict can be:
@@ -685,6 +848,41 @@ class vimconnector(vimconn.vimconnector):
         except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
             self._format_exception(e)
 
         except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
             self._format_exception(e)
 
+    @staticmethod
+    def _create_mimemultipart(content_list):
+        """Creates a MIMEmultipart text combining the content_list
+        :param content_list: list of text scripts to be combined
+        :return: str of the created MIMEmultipart. If the list is empty returns None, if the list contains only one
+        element MIMEmultipart is not created and this content is returned
+        """
+        if not content_list:
+            return None
+        elif len(content_list) == 1:
+            return content_list[0]
+        combined_message = MIMEMultipart()
+        for content in content_list:
+            if content.startswith('#include'):
+                format = 'text/x-include-url'
+            elif content.startswith('#include-once'):
+                format = 'text/x-include-once-url'
+            elif content.startswith('#!'):
+                format = 'text/x-shellscript'
+            elif content.startswith('#cloud-config'):
+                format = 'text/cloud-config'
+            elif content.startswith('#cloud-config-archive'):
+                format = 'text/cloud-config-archive'
+            elif content.startswith('#upstart-job'):
+                format = 'text/upstart-job'
+            elif content.startswith('#part-handler'):
+                format = 'text/part-handler'
+            elif content.startswith('#cloud-boothook'):
+                format = 'text/cloud-boothook'
+            else:  # by default
+                format = 'text/x-shellscript'
+            sub_message = MIMEText(content, format, sys.getdefaultencoding())
+            combined_message.attach(sub_message)
+        return combined_message.as_string()
+
     def __wait_for_vm(self, vm_id, status):
         """wait until vm is in the desired status and return True.
         If the VM gets in ERROR status, return false.
     def __wait_for_vm(self, vm_id, status):
         """wait until vm is in the desired status and return True.
         If the VM gets in ERROR status, return false.
@@ -732,36 +930,38 @@ class vimconnector(vimconn.vimconnector):
         else:
             self.availability_zone = self._get_openstack_availablity_zones()
 
         else:
             self.availability_zone = self._get_openstack_availablity_zones()
 
-    def _get_vm_availavility_zone(self, availavility_zone_index, nfv_availability_zones):
+    def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
         """
         """
-        Return a list with all availability zones create during datacenter attach.
-        :return: List with availability zones
+        Return thge availability zone to be used by the created VM.
+        :return: The VIM availability zone to be used or None
         """
         """
-        openstack_avilability_zone = self.availability_zone
-
-        # check if VIM offer enough availability zones describe in the VNFC
-        if self.availability_zone and availavility_zone_index is not None \
-                and 0 <= len(nfv_availability_zones) <= len(self.availability_zone):
-
-            if nfv_availability_zones:
-                vnf_azone = nfv_availability_zones[availavility_zone_index]
-                zones_available = []
-
-                for nfv_zone in nfv_availability_zones:
-                    for vim_zone in openstack_avilability_zone:
-                        if nfv_zone is vim_zone:
-                            zones_available.append(nfv_zone)
-
-                if len(zones_available) == len(openstack_avilability_zone) and vnf_azone in openstack_avilability_zone:
-                    return vnf_azone
-                else:
-                    return openstack_avilability_zone[availavility_zone_index]
+        if availability_zone_index is None:
+            if not self.config.get('availability_zone'):
+                return None
+            elif isinstance(self.config.get('availability_zone'), str):
+                return self.config['availability_zone']
+            else:
+                # TODO consider using a different parameter at config for default AV and AV list match
+                return self.config['availability_zone'][0]
+
+        vim_availability_zones = self.availability_zone
+        # check if VIM offer enough availability zones describe in the VNFD
+        if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
+            # check if all the names of NFV AV match VIM AV names
+            match_by_index = False
+            for av in availability_zone_list:
+                if av not in vim_availability_zones:
+                    match_by_index = True
+                    break
+            if match_by_index:
+                return vim_availability_zones[availability_zone_index]
+            else:
+                return availability_zone_list[availability_zone_index]
         else:
         else:
-            raise vimconn.vimconnConflictException("No enough availablity zones for this deployment")
-        return None
+            raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
 
 
-    def new_vminstance(self, name, description, start, image_id, flavor_id, net_list,cloud_config=None,disk_list=None,
-                       availavility_zone_index=None, nfv_availability_zones=None):
+    def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
+                       availability_zone_index=None, availability_zone_list=None):
         '''Adds a VM instance to VIM
         Params:
             start: indicates if VM must start or boot in pause mode. Ignored
         '''Adds a VM instance to VIM
         Params:
             start: indicates if VM must start or boot in pause mode. Ignored
@@ -793,8 +993,9 @@ class vimconnector(vimconn.vimconnector):
             'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
                 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
                 'size': (mandatory) string with the size of the disk in GB
             'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
                 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
                 'size': (mandatory) string with the size of the disk in GB
-            availavility_zone_index:counter for instance order in vim availability_zones availables
-            nfv_availability_zones: Lost given by user in the VNFC descriptor.
+            availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
+            availability_zone_list: list of availability zones given by user in the VNFD descriptor.  Ignore if
+                availability_zone_index is None
                 #TODO ip, security groups
         Returns the instance identifier
         '''
                 #TODO ip, security groups
         Returns the instance identifier
         '''
@@ -826,7 +1027,19 @@ class vimconnector(vimconn.vimconnector):
                             metadata_vpci["VF"]=[]
                         metadata_vpci["VF"].append([ net["vpci"], "" ])
                     port_dict["binding:vnic_type"]="direct"
                             metadata_vpci["VF"]=[]
                         metadata_vpci["VF"].append([ net["vpci"], "" ])
                     port_dict["binding:vnic_type"]="direct"
-                else:  # For PT
+                    ########## VIO specific Changes #######
+                    if self.vim_type == "VIO":
+                        #Need to create port with port_security_enabled = False and no-security-groups
+                        port_dict["port_security_enabled"]=False
+                        port_dict["provider_security_groups"]=[]
+                        port_dict["security_groups"]=[]
+                else: #For PT
+                    ########## VIO specific Changes #######
+                    #Current VIO release does not support port with type 'direct-physical'
+                    #So no need to create virtual port in case of PCI-device.
+                    #Will update port_dict code when support gets added in next VIO release
+                    if self.vim_type == "VIO":
+                        raise vimconn.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
                     if "vpci" in net:
                         if "PF" not in metadata_vpci:
                             metadata_vpci["PF"]=[]
                     if "vpci" in net:
                         if "PF" not in metadata_vpci:
                             metadata_vpci["PF"]=[]
@@ -870,24 +1083,27 @@ class vimconnector(vimconn.vimconnector):
                     #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
                     self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
                     metadata = {}
                     #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
                     self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
                     metadata = {}
-            
+
             self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
                               name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
             self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
                               name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
-            
+
             security_groups   = self.config.get('security_groups')
             if type(security_groups) is str:
                 security_groups = ( security_groups, )
             #cloud config
             userdata=None
             config_drive = None
             security_groups   = self.config.get('security_groups')
             if type(security_groups) is str:
                 security_groups = ( security_groups, )
             #cloud config
             userdata=None
             config_drive = None
+            userdata_list = []
             if isinstance(cloud_config, dict):
                 if cloud_config.get("user-data"):
             if isinstance(cloud_config, dict):
                 if cloud_config.get("user-data"):
-                    userdata=cloud_config["user-data"]
+                    if isinstance(cloud_config["user-data"], str):
+                        userdata_list.append(cloud_config["user-data"])
+                    else:
+                        for u in cloud_config["user-data"]:
+                            userdata_list.append(u)
                 if cloud_config.get("boot-data-drive") != None:
                     config_drive = cloud_config["boot-data-drive"]
                 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
                 if cloud_config.get("boot-data-drive") != None:
                     config_drive = cloud_config["boot-data-drive"]
                 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
-                    if userdata:
-                        raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
                     userdata_dict={}
                     #default user
                     if cloud_config.get("key-pairs"):
                     userdata_dict={}
                     #default user
                     if cloud_config.get("key-pairs"):
@@ -921,8 +1137,9 @@ class vimconnector(vimconn.vimconnector):
                             if file.get("owner"):
                                 file_info["owner"] = file["owner"]
                             userdata_dict["write_files"].append(file_info)
                             if file.get("owner"):
                                 file_info["owner"] = file["owner"]
                             userdata_dict["write_files"].append(file_info)
-                    userdata = "#cloud-config\n"
-                    userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
+                    userdata_list.append("#cloud-config\n" + yaml.safe_dump(userdata_dict, indent=4,
+                                                                              default_flow_style=False))
+                    userdata = self._create_mimemultipart(userdata_list)
                 self.logger.debug("userdata: %s", userdata)
             elif isinstance(cloud_config, str):
                 userdata = cloud_config
                 self.logger.debug("userdata: %s", userdata)
             elif isinstance(cloud_config, str):
                 userdata = cloud_config
@@ -967,7 +1184,7 @@ class vimconnector(vimconn.vimconnector):
                     raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
                                                    http_code=vimconn.HTTP_Request_Timeout)
             # get availability Zone
                     raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
                                                    http_code=vimconn.HTTP_Request_Timeout)
             # get availability Zone
-            vm_av_zone = self._get_vm_availavility_zone(availavility_zone_index, nfv_availability_zones)
+            vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
 
             self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
                               "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
 
             self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
                               "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
@@ -1113,7 +1330,7 @@ class vimconnector(vimconn.vimconnector):
                 console_dict = server.get_spice_console(console_type)
             else:
                 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
                 console_dict = server.get_spice_console(console_type)
             else:
                 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
-            
+
             console_dict1 = console_dict.get("console")
             if console_dict1:
                 console_url = console_dict1.get("url")
             console_dict1 = console_dict.get("console")
             if console_dict1:
                 console_url = console_dict1.get("url")
@@ -1125,14 +1342,14 @@ class vimconnector(vimconn.vimconnector):
                     if protocol_index < 0 or port_index<0 or suffix_index<0:
                         return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
                     console_dict={"protocol": console_url[0:protocol_index],
                     if protocol_index < 0 or port_index<0 or suffix_index<0:
                         return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
                     console_dict={"protocol": console_url[0:protocol_index],
-                                  "server":   console_url[protocol_index+2:port_index], 
-                                  "port":     console_url[port_index:suffix_index], 
-                                  "suffix":   console_url[suffix_index+1:] 
+                                  "server":   console_url[protocol_index+2:port_index],
+                                  "port":     console_url[port_index:suffix_index],
+                                  "suffix":   console_url[suffix_index+1:]
                                   }
                     protocol_index += 2
                     return console_dict
             raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
                                   }
                     protocol_index += 2
                     return console_dict
             raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
-            
+
         except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
             self._format_exception(e)
 
         except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
             self._format_exception(e)
 
@@ -1282,7 +1499,7 @@ class vimconnector(vimconn.vimconnector):
                 vm['error_msg'] = str(e)
             vm_dict[vm_id] = vm
         return vm_dict
                 vm['error_msg'] = str(e)
             vm_dict[vm_id] = vm
         return vm_dict
-    
+
     def action_vminstance(self, vm_id, action_dict):
         '''Send and action over a VM instance from VIM
         Returns the vm_id if the action was successfully sent to the VIM'''
     def action_vminstance(self, vm_id, action_dict):
         '''Send and action over a VM instance from VIM
         Returns the vm_id if the action was successfully sent to the VIM'''
@@ -1291,7 +1508,7 @@ class vimconnector(vimconn.vimconnector):
             self._reload_connection()
             server = self.nova.servers.find(id=vm_id)
             if "start" in action_dict:
             self._reload_connection()
             server = self.nova.servers.find(id=vm_id)
             if "start" in action_dict:
-                if action_dict["start"]=="rebuild":  
+                if action_dict["start"]=="rebuild":
                     server.rebuild()
                 else:
                     if server.status=="PAUSED":
                     server.rebuild()
                 else:
                     if server.status=="PAUSED":
@@ -1333,7 +1550,7 @@ class vimconnector(vimconn.vimconnector):
                 elif console_type == "spice-html5":
                     console_dict = server.get_spice_console(console_type)
                 else:
                 elif console_type == "spice-html5":
                     console_dict = server.get_spice_console(console_type)
                 else:
-                    raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), 
+                    raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
                                                    http_code=vimconn.HTTP_Bad_Request)
                 try:
                     console_url = console_dict["console"]["url"]
                                                    http_code=vimconn.HTTP_Bad_Request)
                 try:
                     console_url = console_dict["console"]["url"]
@@ -1344,33 +1561,94 @@ class vimconnector(vimconn.vimconnector):
                     if protocol_index < 0 or port_index<0 or suffix_index<0:
                         raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
                     console_dict2={"protocol": console_url[0:protocol_index],
                     if protocol_index < 0 or port_index<0 or suffix_index<0:
                         raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
                     console_dict2={"protocol": console_url[0:protocol_index],
-                                  "server":   console_url[protocol_index+2 : port_index], 
-                                  "port":     int(console_url[port_index+1 : suffix_index]), 
-                                  "suffix":   console_url[suffix_index+1:] 
+                                  "server":   console_url[protocol_index+2 : port_index],
+                                  "port":     int(console_url[port_index+1 : suffix_index]),
+                                  "suffix":   console_url[suffix_index+1:]
                                   }
                                   }
-                    return console_dict2               
+                    return console_dict2
                 except Exception as e:
                     raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
                 except Exception as e:
                     raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
-            
+
             return vm_id
         except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
             self._format_exception(e)
         #TODO insert exception vimconn.HTTP_Unauthorized
 
             return vm_id
         except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
             self._format_exception(e)
         #TODO insert exception vimconn.HTTP_Unauthorized
 
+    ####### VIO Specific Changes #########
+    def _genrate_vlanID(self):
+        """
+         Method to get unused vlanID
+            Args:
+                None
+            Returns:
+                vlanID
+        """
+        #Get used VLAN IDs
+        usedVlanIDs = []
+        networks = self.get_network_list()
+        for net in networks:
+            if net.get('provider:segmentation_id'):
+                usedVlanIDs.append(net.get('provider:segmentation_id'))
+        used_vlanIDs = set(usedVlanIDs)
+
+        #find unused VLAN ID
+        for vlanID_range in self.config.get('dataplane_net_vlan_range'):
+            try:
+                start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
+                for vlanID in xrange(start_vlanid, end_vlanid + 1):
+                    if vlanID not in used_vlanIDs:
+                        return vlanID
+            except Exception as exp:
+                raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
+        else:
+            raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
+                " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
+
+
+    def _validate_vlan_ranges(self, dataplane_net_vlan_range):
+        """
+        Method to validate user given vlanID ranges
+            Args:  None
+            Returns: None
+        """
+        for vlanID_range in dataplane_net_vlan_range:
+            vlan_range = vlanID_range.replace(" ", "")
+            #validate format
+            vlanID_pattern = r'(\d)*-(\d)*$'
+            match_obj = re.match(vlanID_pattern, vlan_range)
+            if not match_obj:
+                raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
+                "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
+
+            start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
+            if start_vlanid <= 0 :
+                raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
+                "Start ID can not be zero. For VLAN "\
+                "networks valid IDs are 1 to 4094 ".format(vlanID_range))
+            if end_vlanid > 4094 :
+                raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
+                "End VLAN ID can not be greater than 4094. For VLAN "\
+                "networks valid IDs are 1 to 4094 ".format(vlanID_range))
+
+            if start_vlanid > end_vlanid:
+                raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
+                    "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
+                    "start_ID < end_ID ".format(vlanID_range))
+
 #NOT USED FUNCTIONS
 #NOT USED FUNCTIONS
-    
+
     def new_external_port(self, port_data):
         #TODO openstack if needed
         '''Adds a external port to VIM'''
         '''Returns the port identifier'''
     def new_external_port(self, port_data):
         #TODO openstack if needed
         '''Adds a external port to VIM'''
         '''Returns the port identifier'''
-        return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented" 
-        
+        return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
+
     def connect_port_network(self, port_id, network_id, admin=False):
         #TODO openstack if needed
         '''Connects a external port to a network'''
         '''Returns status code of the VIM response'''
     def connect_port_network(self, port_id, network_id, admin=False):
         #TODO openstack if needed
         '''Connects a external port to a network'''
         '''Returns status code of the VIM response'''
-        return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented" 
-    
+        return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
+
     def new_user(self, user_name, user_passwd, tenant_id=None):
         '''Adds a new user to openstack VIM'''
         '''Returns the user identifier'''
     def new_user(self, user_name, user_passwd, tenant_id=None):
         '''Adds a new user to openstack VIM'''
         '''Returns the user identifier'''
@@ -1390,13 +1668,13 @@ class vimconnector(vimconn.vimconnector):
         #if reaching here is because an exception
         if self.debug:
             self.logger.debug("new_user " + error_text)
         #if reaching here is because an exception
         if self.debug:
             self.logger.debug("new_user " + error_text)
-        return error_value, error_text        
+        return error_value, error_text
 
     def delete_user(self, user_id):
         '''Delete a user from openstack VIM'''
         '''Returns the user identifier'''
         if self.debug:
 
     def delete_user(self, user_id):
         '''Delete a user from openstack VIM'''
         '''Returns the user identifier'''
         if self.debug:
-            print "osconnector: Deleting  a  user from VIM"
+            print("osconnector: Deleting  a  user from VIM")
         try:
             self._reload_connection()
             self.keystone.users.delete(user_id)
         try:
             self._reload_connection()
             self.keystone.users.delete(user_id)
@@ -1413,14 +1691,14 @@ class vimconnector(vimconn.vimconnector):
         #TODO insert exception vimconn.HTTP_Unauthorized
         #if reaching here is because an exception
         if self.debug:
         #TODO insert exception vimconn.HTTP_Unauthorized
         #if reaching here is because an exception
         if self.debug:
-            print "delete_tenant " + error_text
+            print("delete_tenant " + error_text)
         return error_value, error_text
         return error_value, error_text
+
     def get_hosts_info(self):
         '''Get the information of deployed hosts
         Returns the hosts content'''
         if self.debug:
     def get_hosts_info(self):
         '''Get the information of deployed hosts
         Returns the hosts content'''
         if self.debug:
-            print "osconnector: Getting Host info from VIM"
+            print("osconnector: Getting Host info from VIM")
         try:
             h_list=[]
             self._reload_connection()
         try:
             h_list=[]
             self._reload_connection()
@@ -1437,8 +1715,8 @@ class vimconnector(vimconn.vimconnector):
         #TODO insert exception vimconn.HTTP_Unauthorized
         #if reaching here is because an exception
         if self.debug:
         #TODO insert exception vimconn.HTTP_Unauthorized
         #if reaching here is because an exception
         if self.debug:
-            print "get_hosts_info " + error_text
-        return error_value, error_text        
+            print("get_hosts_info " + error_text)
+        return error_value, error_text
 
     def get_hosts(self, vim_tenant):
         '''Get the hosts and deployed instances
 
     def get_hosts(self, vim_tenant):
         '''Get the hosts and deployed instances
@@ -1466,7 +1744,297 @@ class vimconnector(vimconn.vimconnector):
         #TODO insert exception vimconn.HTTP_Unauthorized
         #if reaching here is because an exception
         if self.debug:
         #TODO insert exception vimconn.HTTP_Unauthorized
         #if reaching here is because an exception
         if self.debug:
-            print "get_hosts " + error_text
-        return error_value, error_text        
-  
+            print("get_hosts " + error_text)
+        return error_value, error_text
+
+    def new_classification(self, name, ctype, definition):
+        self.logger.debug(
+            'Adding a new (Traffic) Classification to VIM, named %s', name)
+        try:
+            new_class = None
+            self._reload_connection()
+            if ctype not in supportedClassificationTypes:
+                raise vimconn.vimconnNotSupportedException(
+                        'OpenStack VIM connector doesn\'t support provided '
+                        'Classification Type {}, supported ones are: '
+                        '{}'.format(ctype, supportedClassificationTypes))
+            if not self._validate_classification(ctype, definition):
+                raise vimconn.vimconnException(
+                    'Incorrect Classification definition '
+                    'for the type specified.')
+            classification_dict = definition
+            classification_dict['name'] = name
+
+            new_class = self.neutron.create_flow_classifier(
+                {'flow_classifier': classification_dict})
+            return new_class['flow_classifier']['id']
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            self.logger.error(
+                'Creation of Classification failed.')
+            self._format_exception(e)
+
+    def get_classification(self, class_id):
+        self.logger.debug(" Getting Classification %s from VIM", class_id)
+        filter_dict = {"id": class_id}
+        class_list = self.get_classification_list(filter_dict)
+        if len(class_list) == 0:
+            raise vimconn.vimconnNotFoundException(
+                "Classification '{}' not found".format(class_id))
+        elif len(class_list) > 1:
+            raise vimconn.vimconnConflictException(
+                "Found more than one Classification with this criteria")
+        classification = class_list[0]
+        return classification
+
+    def get_classification_list(self, filter_dict={}):
+        self.logger.debug("Getting Classifications from VIM filter: '%s'",
+                          str(filter_dict))
+        try:
+            self._reload_connection()
+            if self.api_version3 and "tenant_id" in filter_dict:
+                filter_dict['project_id'] = filter_dict.pop('tenant_id')
+            classification_dict = self.neutron.list_flow_classifier(
+                **filter_dict)
+            classification_list = classification_dict["flow_classifiers"]
+            self.__classification_os2mano(classification_list)
+            return classification_list
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            self._format_exception(e)
+
+    def delete_classification(self, class_id):
+        self.logger.debug("Deleting Classification '%s' from VIM", class_id)
+        try:
+            self._reload_connection()
+            self.neutron.delete_flow_classifier(class_id)
+            return class_id
+        except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
+                ksExceptions.ClientException, neExceptions.NeutronException,
+                ConnectionError) as e:
+            self._format_exception(e)
+
+    def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
+        self.logger.debug(
+            "Adding a new Service Function Instance to VIM, named '%s'", name)
+        try:
+            new_sfi = None
+            self._reload_connection()
+            correlation = None
+            if sfc_encap:
+                # TODO(igordc): must be changed to NSH in Queens
+                # (MPLS is a workaround)
+                correlation = 'mpls'
+            if len(ingress_ports) != 1:
+                raise vimconn.vimconnNotSupportedException(
+                    "OpenStack VIM connector can only have "
+                    "1 ingress port per SFI")
+            if len(egress_ports) != 1:
+                raise vimconn.vimconnNotSupportedException(
+                    "OpenStack VIM connector can only have "
+                    "1 egress port per SFI")
+            sfi_dict = {'name': name,
+                        'ingress': ingress_ports[0],
+                        'egress': egress_ports[0],
+                        'service_function_parameters': {
+                            'correlation': correlation}}
+            new_sfi = self.neutron.create_port_pair({'port_pair': sfi_dict})
+            return new_sfi['port_pair']['id']
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            if new_sfi:
+                try:
+                    self.neutron.delete_port_pair_group(
+                        new_sfi['port_pair']['id'])
+                except Exception:
+                    self.logger.error(
+                        'Creation of Service Function Instance failed, with '
+                        'subsequent deletion failure as well.')
+            self._format_exception(e)
+
+    def get_sfi(self, sfi_id):
+        self.logger.debug(
+            'Getting Service Function Instance %s from VIM', sfi_id)
+        filter_dict = {"id": sfi_id}
+        sfi_list = self.get_sfi_list(filter_dict)
+        if len(sfi_list) == 0:
+            raise vimconn.vimconnNotFoundException(
+                "Service Function Instance '{}' not found".format(sfi_id))
+        elif len(sfi_list) > 1:
+            raise vimconn.vimconnConflictException(
+                'Found more than one Service Function Instance '
+                'with this criteria')
+        sfi = sfi_list[0]
+        return sfi
+
+    def get_sfi_list(self, filter_dict={}):
+        self.logger.debug("Getting Service Function Instances from "
+                          "VIM filter: '%s'", str(filter_dict))
+        try:
+            self._reload_connection()
+            if self.api_version3 and "tenant_id" in filter_dict:
+                filter_dict['project_id'] = filter_dict.pop('tenant_id')
+            sfi_dict = self.neutron.list_port_pair(**filter_dict)
+            sfi_list = sfi_dict["port_pairs"]
+            self.__sfi_os2mano(sfi_list)
+            return sfi_list
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            self._format_exception(e)
+
+    def delete_sfi(self, sfi_id):
+        self.logger.debug("Deleting Service Function Instance '%s' "
+                          "from VIM", sfi_id)
+        try:
+            self._reload_connection()
+            self.neutron.delete_port_pair(sfi_id)
+            return sfi_id
+        except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
+                ksExceptions.ClientException, neExceptions.NeutronException,
+                ConnectionError) as e:
+            self._format_exception(e)
+
+    def new_sf(self, name, sfis, sfc_encap=True):
+        self.logger.debug("Adding a new Service Function to VIM, "
+                          "named '%s'", name)
+        try:
+            new_sf = None
+            self._reload_connection()
+            correlation = None
+            if sfc_encap:
+                # TODO(igordc): must be changed to NSH in Queens
+                # (MPLS is a workaround)
+                correlation = 'mpls'
+            for instance in sfis:
+                sfi = self.get_sfi(instance)
+                if sfi.get('sfc_encap') != correlation:
+                    raise vimconn.vimconnNotSupportedException(
+                        "OpenStack VIM connector requires all SFIs of the "
+                        "same SF to share the same SFC Encapsulation")
+            sf_dict = {'name': name,
+                       'port_pairs': sfis}
+            new_sf = self.neutron.create_port_pair_group({
+                'port_pair_group': sf_dict})
+            return new_sf['port_pair_group']['id']
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            if new_sf:
+                try:
+                    self.neutron.delete_port_pair_group(
+                        new_sf['port_pair_group']['id'])
+                except Exception:
+                    self.logger.error(
+                        'Creation of Service Function failed, with '
+                        'subsequent deletion failure as well.')
+            self._format_exception(e)
 
 
+    def get_sf(self, sf_id):
+        self.logger.debug("Getting Service Function %s from VIM", sf_id)
+        filter_dict = {"id": sf_id}
+        sf_list = self.get_sf_list(filter_dict)
+        if len(sf_list) == 0:
+            raise vimconn.vimconnNotFoundException(
+                "Service Function '{}' not found".format(sf_id))
+        elif len(sf_list) > 1:
+            raise vimconn.vimconnConflictException(
+                "Found more than one Service Function with this criteria")
+        sf = sf_list[0]
+        return sf
+
+    def get_sf_list(self, filter_dict={}):
+        self.logger.debug("Getting Service Function from VIM filter: '%s'",
+                          str(filter_dict))
+        try:
+            self._reload_connection()
+            if self.api_version3 and "tenant_id" in filter_dict:
+                filter_dict['project_id'] = filter_dict.pop('tenant_id')
+            sf_dict = self.neutron.list_port_pair_group(**filter_dict)
+            sf_list = sf_dict["port_pair_groups"]
+            self.__sf_os2mano(sf_list)
+            return sf_list
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            self._format_exception(e)
+
+    def delete_sf(self, sf_id):
+        self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
+        try:
+            self._reload_connection()
+            self.neutron.delete_port_pair_group(sf_id)
+            return sf_id
+        except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
+                ksExceptions.ClientException, neExceptions.NeutronException,
+                ConnectionError) as e:
+            self._format_exception(e)
+
+    def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
+        self.logger.debug("Adding a new Service Function Path to VIM, "
+                          "named '%s'", name)
+        try:
+            new_sfp = None
+            self._reload_connection()
+            if not sfc_encap:
+                raise vimconn.vimconnNotSupportedException(
+                    "OpenStack VIM connector only supports "
+                    "SFC-Encapsulated chains")
+            # TODO(igordc): must be changed to NSH in Queens
+            # (MPLS is a workaround)
+            correlation = 'mpls'
+            sfp_dict = {'name': name,
+                        'flow_classifiers': classifications,
+                        'port_pair_groups': sfs,
+                        'chain_parameters': {'correlation': correlation}}
+            if spi:
+                sfp_dict['chain_id'] = spi
+            new_sfp = self.neutron.create_port_chain({'port_chain': sfp_dict})
+            return new_sfp["port_chain"]["id"]
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            if new_sfp:
+                try:
+                    self.neutron.delete_port_chain(new_sfp['port_chain']['id'])
+                except Exception:
+                    self.logger.error(
+                        'Creation of Service Function Path failed, with '
+                        'subsequent deletion failure as well.')
+            self._format_exception(e)
+
+    def get_sfp(self, sfp_id):
+        self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
+        filter_dict = {"id": sfp_id}
+        sfp_list = self.get_sfp_list(filter_dict)
+        if len(sfp_list) == 0:
+            raise vimconn.vimconnNotFoundException(
+                "Service Function Path '{}' not found".format(sfp_id))
+        elif len(sfp_list) > 1:
+            raise vimconn.vimconnConflictException(
+                "Found more than one Service Function Path with this criteria")
+        sfp = sfp_list[0]
+        return sfp
+
+    def get_sfp_list(self, filter_dict={}):
+        self.logger.debug("Getting Service Function Paths from VIM filter: "
+                          "'%s'", str(filter_dict))
+        try:
+            self._reload_connection()
+            if self.api_version3 and "tenant_id" in filter_dict:
+                filter_dict['project_id'] = filter_dict.pop('tenant_id')
+            sfp_dict = self.neutron.list_port_chain(**filter_dict)
+            sfp_list = sfp_dict["port_chains"]
+            self.__sfp_os2mano(sfp_list)
+            return sfp_list
+        except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
+                neExceptions.NeutronException, ConnectionError) as e:
+            self._format_exception(e)
+
+    def delete_sfp(self, sfp_id):
+        self.logger.debug(
+            "Deleting Service Function Path '%s' from VIM", sfp_id)
+        try:
+            self._reload_connection()
+            self.neutron.delete_port_chain(sfp_id)
+            return sfp_id
+        except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
+                ksExceptions.ClientException, neExceptions.NeutronException,
+                ConnectionError) as e:
+            self._format_exception(e)