"Fossies" - the Fresh Open Source Software Archive

Member "tacker-3.0.1/tacker/nfvo/drivers/vim/openstack_driver.py" (29 Jul 2021, 42757 Bytes) of package /linux/misc/openstack/tacker-3.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "openstack_driver.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.0.0_vs_3.0.1.

    1 # Copyright 2016 Brocade Communications System, Inc.
    2 # All Rights Reserved.
    3 #
    4 #
    5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    6 #    not use this file except in compliance with the License. You may obtain
    7 #    a copy of the License at
    8 #
    9 #         http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 #    Unless required by applicable law or agreed to in writing, software
   12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   14 #    License for the specific language governing permissions and limitations
   15 #    under the License.
   16 
   17 import os
   18 import six
   19 import yaml
   20 
   21 from keystoneauth1 import exceptions
   22 from keystoneauth1 import identity
   23 from keystoneauth1.identity import v3
   24 from keystoneauth1 import session
   25 from neutronclient.common import exceptions as nc_exceptions
   26 from neutronclient.v2_0 import client as neutron_client
   27 from oslo_config import cfg
   28 from oslo_log import log as logging
   29 
   30 from tacker._i18n import _
   31 from tacker.common import log
   32 from tacker import context as t_context
   33 from tacker.extensions import nfvo
   34 from tacker.keymgr import API as KEYMGR_API
   35 from tacker.mistral import mistral_client
   36 from tacker.nfvo.drivers.vim import abstract_vim_driver
   37 from tacker.nfvo.drivers.vnffg import abstract_vnffg_driver
   38 from tacker.nfvo.drivers.workflow import workflow_generator
   39 from tacker.nfvo.nfvo_plugin import NfvoPlugin
   40 from tacker.plugins.common import constants
   41 from tacker.vnfm import keystone
   42 
   43 LOG = logging.getLogger(__name__)
   44 CONF = cfg.CONF
   45 
   46 OPTS = [cfg.StrOpt('openstack', default='/etc/tacker/vim/fernet_keys',
   47                    help='Dir.path to store fernet keys.'),
   48         cfg.BoolOpt('use_barbican', default=False,
   49                     help=_('Use barbican to encrypt vim password if True, '
   50                            'save vim credentials in local file system '
   51                            'if False'))
   52         ]
   53 
   54 # same params as we used in ping monitor driver
   55 OPENSTACK_OPTS = [
   56     cfg.StrOpt('count', default='1',
   57                help=_('Number of ICMP packets to send')),
   58     cfg.StrOpt('timeout', default='1',
   59                help=_('Number of seconds to wait for a response')),
   60     cfg.StrOpt('interval', default='1',
   61                help=_('Number of seconds to wait between packets'))
   62 ]
   63 cfg.CONF.register_opts(OPTS, 'vim_keys')
   64 cfg.CONF.register_opts(OPENSTACK_OPTS, 'vim_monitor')
   65 
   66 _VALID_RESOURCE_TYPES = {'network': {'client': neutron_client.Client,
   67                                      'cmd': 'list_networks',
   68                                      'vim_res_name': 'networks',
   69                                      'filter_attr': 'name'
   70                                      }
   71                          }
   72 
   73 FC_MAP = {'name': 'name',
   74           'description': 'description',
   75           'eth_type': 'ethertype',
   76           'ip_src_prefix': 'source_ip_prefix',
   77           'ip_dst_prefix': 'destination_ip_prefix',
   78           'source_port_min': 'source_port_range_min',
   79           'source_port_max': 'source_port_range_max',
   80           'destination_port_min': 'destination_port_range_min',
   81           'destination_port_max': 'destination_port_range_max',
   82           'network_src_port_id': 'logical_source_port',
   83           'network_dst_port_id': 'logical_destination_port'}
   84 
   85 CONNECTION_POINT = 'connection_points'
   86 SFC_ENCAP = 'sfc_encap'
   87 
   88 
   89 def config_opts():
   90     return [('vim_keys', OPTS), ('vim_monitor', OPENSTACK_OPTS)]
   91 
   92 
   93 class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
   94                        abstract_vnffg_driver.VnffgAbstractDriver):
   95     """Driver for OpenStack VIM
   96 
   97     OpenStack driver handles interactions with local as well as
   98     remote OpenStack instances. The driver invokes keystone service for VIM
   99     authorization and validation. The driver is also responsible for
  100     discovering placement attributes such as regions, availability zones
  101     """
  102 
  103     def __init__(self):
  104         self.keystone = keystone.Keystone()
  105         self.keystone.create_key_dir(CONF.vim_keys.openstack)
  106 
  107     def get_type(self):
  108         return 'openstack'
  109 
  110     def get_name(self):
  111         return 'OpenStack VIM Driver'
  112 
  113     def get_description(self):
  114         return 'OpenStack VIM Driver'
  115 
  116     def authenticate_vim(self, vim_obj):
  117         """Validate VIM auth attributes
  118 
  119         Initialize keystoneclient with provided authentication attributes.
  120         """
  121         verify = 'True' == vim_obj['auth_cred'].get('cert_verify', 'True') \
  122                  or False
  123         auth_url = vim_obj['auth_url']
  124         keystone_version = NfvoPlugin.validate_keystone_auth_url(
  125             auth_url=auth_url,
  126             verify=verify)
  127         auth_cred = self._get_auth_creds(vim_obj, keystone_version)
  128         return self._initialize_keystone(auth_cred)
  129 
  130     def _get_auth_creds(self, vim_obj, keystone_version):
  131         auth_cred = vim_obj['auth_cred']
  132         vim_project = vim_obj['vim_project']
  133         auth_cred['project_id'] = vim_project.get('id')
  134         auth_cred['project_name'] = vim_project.get('name')
  135         auth_cred['project_domain_name'] = vim_project.get(
  136             'project_domain_name')
  137         auth_cred['auth_url'] = vim_obj['auth_url']
  138         if keystone_version not in auth_cred['auth_url']:
  139             auth_cred['auth_url'] = auth_cred['auth_url'] + '/' + \
  140                 keystone_version
  141         return auth_cred
  142 
  143     def _get_auth_plugin(self, **kwargs):
  144         auth_plugin = v3.Password(**kwargs)
  145 
  146         return auth_plugin
  147 
  148     def _initialize_keystone(self, auth):
  149         ks_client = self.keystone.initialize_client(**auth)
  150         return ks_client
  151 
  152     def _find_regions(self, ks_client):
  153         region_info = ks_client.regions.list()
  154         region_list = [region.id for region in region_info]
  155         return region_list
  156 
  157     def discover_placement_attr(self, vim_obj, ks_client):
  158         """Fetch VIM placement information
  159 
  160         Attributes can include regions, AZ.
  161         """
  162         try:
  163             regions_list = self._find_regions(ks_client)
  164         except (exceptions.Unauthorized, exceptions.BadRequest) as e:
  165             LOG.warning("Authorization failed for user")
  166             raise nfvo.VimUnauthorizedException(message=e.message)
  167         vim_obj['placement_attr'] = {'regions': regions_list}
  168         return vim_obj
  169 
  170     @log.log
  171     def register_vim(self, vim_obj):
  172         """Validate and set VIM placements."""
  173 
  174         if 'key_type' in vim_obj['auth_cred']:
  175             vim_obj['auth_cred'].pop(u'key_type')
  176         if 'secret_uuid' in vim_obj['auth_cred']:
  177             vim_obj['auth_cred'].pop(u'secret_uuid')
  178 
  179         ks_client = self.authenticate_vim(vim_obj)
  180         self.discover_placement_attr(vim_obj, ks_client)
  181         self.encode_vim_auth(vim_obj['id'], vim_obj['auth_cred'])
  182         LOG.debug('VIM registration completed for %s', vim_obj)
  183 
  184     @log.log
  185     def deregister_vim(self, vim_obj):
  186         """Deregister VIM from NFVO
  187 
  188         Delete VIM keys from file system
  189         """
  190         self.delete_vim_auth(vim_obj['id'], vim_obj['auth_cred'])
  191 
  192     @log.log
  193     def delete_vim_auth(self, vim_id, auth):
  194         """Delete vim information
  195 
  196         Delete vim key stored in file system
  197         """
  198         LOG.debug('Attempting to delete key for vim id %s', vim_id)
  199 
  200         if auth.get('key_type') == 'barbican_key':
  201             try:
  202                 k_context = t_context.generate_tacker_service_context()
  203                 keystone_conf = CONF.keystone_authtoken
  204                 secret_uuid = auth['secret_uuid']
  205                 keymgr_api = KEYMGR_API(keystone_conf.auth_url)
  206                 keymgr_api.delete(k_context, secret_uuid)
  207                 LOG.debug('VIM key deleted successfully for vim %s',
  208                           vim_id)
  209             except Exception as ex:
  210                 LOG.warning('VIM key deletion failed for vim %s due to %s',
  211                             vim_id,
  212                             ex)
  213                 raise
  214         else:
  215             key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
  216             try:
  217                 os.remove(key_file)
  218                 LOG.debug('VIM key deleted successfully for vim %s',
  219                           vim_id)
  220             except OSError:
  221                 LOG.warning('VIM key deletion failed for vim %s',
  222                             vim_id)
  223 
  224     @log.log
  225     def encode_vim_auth(self, vim_id, auth):
  226         """Encode VIM credentials
  227 
  228          Store VIM auth using fernet key encryption
  229          """
  230         fernet_key, fernet_obj = self.keystone.create_fernet_key()
  231         encoded_auth = fernet_obj.encrypt(auth['password'].encode('utf-8'))
  232         auth['password'] = encoded_auth
  233 
  234         if CONF.vim_keys.use_barbican:
  235             try:
  236                 k_context = t_context.generate_tacker_service_context()
  237                 keystone_conf = CONF.keystone_authtoken
  238                 keymgr_api = KEYMGR_API(keystone_conf.auth_url)
  239                 secret_uuid = keymgr_api.store(k_context, fernet_key)
  240 
  241                 auth['key_type'] = 'barbican_key'
  242                 auth['secret_uuid'] = secret_uuid
  243                 LOG.debug('VIM auth successfully stored for vim %s',
  244                           vim_id)
  245             except Exception as ex:
  246                 LOG.warning('VIM key creation failed for vim %s due to %s',
  247                             vim_id,
  248                             ex)
  249                 raise
  250 
  251         else:
  252             auth['key_type'] = 'fernet_key'
  253             key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
  254             try:
  255                 with open(key_file, 'wb') as f:
  256                     if six.PY2:
  257                         f.write(fernet_key.decode('utf-8'))
  258                     else:
  259                         f.write(fernet_key)
  260                     LOG.debug('VIM auth successfully stored for vim %s',
  261                               vim_id)
  262             except IOError:
  263                 raise nfvo.VimKeyNotFoundException(vim_id=vim_id)
  264 
  265     @log.log
  266     def get_vim_resource_id(self, vim_obj, resource_type, resource_name):
  267         """Locates openstack resource by type/name and returns ID
  268 
  269         :param vim_obj: VIM info used to access openstack instance
  270         :param resource_type: type of resource to find
  271         :param resource_name: name of resource to locate
  272         :return: ID of resource
  273         """
  274         if resource_type in _VALID_RESOURCE_TYPES.keys():
  275             res_cmd_map = _VALID_RESOURCE_TYPES[resource_type]
  276             client_type = res_cmd_map['client']
  277             cmd = res_cmd_map['cmd']
  278             filter_attr = res_cmd_map.get('filter_attr')
  279             vim_res_name = res_cmd_map['vim_res_name']
  280         else:
  281             raise nfvo.VimUnsupportedResourceTypeException(type=resource_type)
  282 
  283         client = self._get_client(vim_obj, client_type)
  284         cmd_args = {}
  285         if filter_attr:
  286             cmd_args[filter_attr] = resource_name
  287 
  288         try:
  289             resources = getattr(client, "%s" % cmd)(**cmd_args)[vim_res_name]
  290             LOG.debug('resources output %s', resources)
  291         except Exception:
  292             raise nfvo.VimGetResourceException(
  293                 cmd=cmd, name=resource_name, type=resource_type)
  294 
  295         if len(resources) > 1:
  296             raise nfvo.VimGetResourceNameNotUnique(
  297                 cmd=cmd, name=resource_name)
  298         elif len(resources) < 1:
  299             raise nfvo.VimGetResourceNotFoundException(
  300                 cmd=cmd, name=resource_name)
  301 
  302         return resources[0]['id']
  303 
  304     @log.log
  305     def _get_client(self, vim_obj, client_type):
  306         """Initializes and returns an openstack client
  307 
  308         :param vim_obj: VIM Information
  309         :param client_type: openstack client to initialize
  310         :return: initialized client
  311         """
  312         verify = 'True' == vim_obj.get('cert_verify', 'True') or False
  313         auth_url = vim_obj['auth_url']
  314         keystone_version = NfvoPlugin.validate_keystone_auth_url(
  315             auth_url=auth_url,
  316             verify=verify)
  317         auth_cred = self._get_auth_creds(vim_obj, keystone_version)
  318         auth_plugin = self._get_auth_plugin(**auth_cred)
  319         sess = session.Session(auth=auth_plugin)
  320         return client_type(session=sess)
  321 
  322     def _translate_ip_protocol(self, ip_proto):
  323         if ip_proto == '1':
  324             return 'icmp'
  325         elif ip_proto == '6':
  326             return 'tcp'
  327         elif ip_proto == '17':
  328             return 'udp'
  329         else:
  330             return None
  331 
  332     def _create_classifier_params(self, fc):
  333         classifier_params = {}
  334         for field in fc:
  335             if field in FC_MAP:
  336                 classifier_params[FC_MAP[field]] = fc[field]
  337             elif field == 'ip_proto':
  338                 protocol = self._translate_ip_protocol(str(fc[field]))
  339                 if not protocol:
  340                     raise ValueError('protocol %s not supported' % fc[field])
  341                 classifier_params['protocol'] = protocol
  342             else:
  343                 LOG.warning("flow classifier %s not supported by "
  344                             "networking-sfc driver", field)
  345         return classifier_params
  346 
  347     def create_flow_classifier(self, name, fc, auth_attr=None):
  348         if not auth_attr:
  349             LOG.warning("auth information required for n-sfc driver")
  350             return None
  351         fc['name'] = name
  352         LOG.debug('fc passed is %s', fc)
  353 
  354         sfc_classifier_params = self._create_classifier_params(fc)
  355         LOG.debug('sfc_classifier_params is %s', sfc_classifier_params)
  356 
  357         if len(sfc_classifier_params) > 0:
  358             neutronclient_ = NeutronClient(auth_attr)
  359 
  360             fc_id = neutronclient_.flow_classifier_create(
  361                 sfc_classifier_params)
  362             return fc_id
  363 
  364         raise ValueError('empty match field for input flow classifier')
  365 
  366     def create_chain(self, name, path_id, fc_ids, vnfs, symmetrical=False,
  367                      correlation='mpls', auth_attr=None):
  368         if not auth_attr:
  369             LOG.warning("auth information required for n-sfc driver")
  370             return None
  371 
  372         neutronclient_ = NeutronClient(auth_attr)
  373         port_pairs_list = neutronclient_.port_pair_list()
  374         port_pair_groups_list = neutronclient_.port_pair_group_list()
  375         port_chains_list = neutronclient_.port_chain_list()
  376         port_pair_group_list = []
  377         new_ppgs = []
  378         new_pps = []
  379 
  380         try:
  381             for vnf in vnfs:
  382                 # TODO(s3wong): once scaling is in place and VNFFG supports it
  383                 # that model needs to be implemented to concatenate all
  384                 # port-pairs into the port-pair-group
  385                 # port pair group could include port-pairs from different VNFs
  386                 if CONNECTION_POINT not in vnf:
  387                     LOG.warning("Chain creation failed due to missing "
  388                                 "connection point info in VNF "
  389                                 "%(vnfname)s", {'vnfname': vnf['name']})
  390                     return None
  391                 cp_list = vnf[CONNECTION_POINT]
  392                 num_cps = len(cp_list)
  393                 if num_cps not in [1, 2]:
  394                     LOG.warning("Chain creation failed due to wrong number of "
  395                                 "connection points: expected [1 | 2], got "
  396                                 "%(cps)d", {'cps': num_cps})
  397                     return None
  398                 if num_cps == 1:
  399                     ingress = cp_list[0]
  400                     egress = cp_list[0]
  401                 else:
  402                     ingress = cp_list[0]
  403                     egress = cp_list[1]
  404 
  405                 # If sfc_encap is True, pp_corr is set to correlation to
  406                 # make use of correlation, otherwise pp_corr is set to None
  407                 # to install SFC proxy
  408                 sfc_encap = vnf.get(SFC_ENCAP, True)
  409                 pp_corr = correlation if sfc_encap else None
  410 
  411                 # valid_port_in_use function is used to find out the
  412                 # port_pair_group_id of the existing port pair group
  413                 # which was created by ingress and egress of current VNF
  414                 port_pair_group_id = self.valid_port_in_use(
  415                     ingress, egress, port_pairs_list, port_pair_groups_list)
  416                 if not port_pair_group_id:
  417                     # create the new port pair group if it is not existed
  418                     port_pair = dict()
  419                     port_pair['name'] = vnf['name'] + '-connection-points'
  420                     port_pair['description'] = 'port pair for ' + vnf['name']
  421                     port_pair['ingress'] = ingress
  422                     port_pair['egress'] = egress
  423                     port_pair['service_function_parameters'] = {
  424                         'correlation': pp_corr}
  425                     port_pair_id = neutronclient_.port_pair_create(port_pair)
  426                     if not port_pair_id:
  427                         LOG.warning("Chain creation failed due to port pair "
  428                                     "creation failed for vnf %(vnf)s",
  429                                     {'vnf': vnf['name']})
  430                         return None
  431                     new_pps.append(port_pair_id)
  432                     port_pair_group = {}
  433                     port_pair_group['name'] = vnf['name'] + '-port-pair-group'
  434                     port_pair_group['description'] = \
  435                         'port pair group for ' + vnf['name']
  436                     port_pair_group['port_pairs'] = []
  437                     port_pair_group['port_pairs'].append(port_pair_id)
  438                     port_pair_group_id = \
  439                         neutronclient_.port_pair_group_create(port_pair_group)
  440                     new_ppgs.append(port_pair_group_id)
  441                 if not port_pair_group_id:
  442                     LOG.warning("Chain creation failed due to port pair group "
  443                                 "creation failed for vnf "
  444                                 "%(vnf)s", {'vnf': vnf['name']})
  445                     raise nfvo.CreateChainException(
  446                         message="Failed to create port-pair-group")
  447                 port_pair_group_list.append(port_pair_group_id)
  448 
  449             # Check list port pair group between new port chain and the
  450             # existing port chains.  Networking-sfc does not allow to create
  451             # two port chains with the same port pair groups and the same order
  452             for pc in port_chains_list['port_chains']:
  453                 ppg_list = pc['port_pair_groups']
  454                 if ppg_list == port_pair_group_list:
  455                     # raise exception when the Vnffg path is already existing
  456                     raise nfvo.CreateChainException(
  457                         message="Vnffg path already exists")
  458         except nfvo.CreateChainException as e:
  459             # clean neutron resources such as port pair, port pair group and
  460             # flow classifier if we create it
  461             for ppg in new_ppgs:
  462                 neutronclient_.port_pair_group_delete(ppg)
  463             for pp in new_pps:
  464                 neutronclient_.port_pair_delete(pp)
  465             for fc_id in fc_ids:
  466                 neutronclient_.flow_classifier_delete(fc_id)
  467             raise e
  468 
  469         # TODO(s3wong): should the chain name be given as a parameter?
  470         port_chain = {}
  471         port_chain['name'] = name + '-port-chain'
  472         if path_id:
  473             port_chain['chain_id'] = path_id
  474         port_chain['description'] = 'port-chain for Tacker VNFFG'
  475         port_chain['port_pair_groups'] = port_pair_group_list
  476         port_chain['flow_classifiers'] = fc_ids
  477         port_chain['chain_parameters'] = {}
  478         port_chain['chain_parameters']['symmetric'] = symmetrical
  479         port_chain['chain_parameters']['correlation'] = correlation
  480         return neutronclient_.port_chain_create(port_chain)
  481 
  482     def update_chain(self, chain_id, fc_ids, vnfs,
  483                      symmetrical=None, auth_attr=None):
  484         # (s3wong): chain can be updated either for
  485         # the list of fc and/or list of port-pair-group
  486         # since n-sfc driver does NOT track the ppg id
  487         # it will look it up (or reconstruct) from
  488         # networking-sfc DB --- but the caveat is that
  489         # the VNF name MUST be unique
  490 
  491         # TODO(mardim) Currently we figure out which VNF belongs to what
  492         # port-pair-group or port-pair through the name of VNF.
  493         # This is not the best approach. The best approach for the future
  494         # propably is to maintain in the database the ID of the
  495         # port-pair-group and port-pair that VNF belongs to so we can
  496         # implemement the update in a more robust way.
  497 
  498         if not auth_attr:
  499             LOG.warning("auth information required for n-sfc driver")
  500             return None
  501 
  502         neutronclient_ = NeutronClient(auth_attr)
  503         port_pairs_list = neutronclient_.port_pair_list()
  504         port_pair_groups_list = neutronclient_.port_pair_group_list()
  505         port_chains_list = neutronclient_.port_chain_list()
  506         new_ppgs = []
  507         updated_port_chain = dict()
  508 
  509         pc_info = neutronclient_.port_chain_show(chain_id)
  510         if set(fc_ids) != set(pc_info['port_chain']['flow_classifiers']):
  511             updated_port_chain['flow_classifiers'] = fc_ids
  512         old_ppgs = pc_info['port_chain']['port_pair_groups']
  513         old_ppgs_dict = {neutronclient_.
  514                     port_pair_group_show(ppg_id)['port_pair_group']['name'].
  515                     split('-')[0]: ppg_id for ppg_id in old_ppgs}
  516         past_ppgs_dict = old_ppgs_dict.copy()
  517         try:
  518             for vnf in vnfs:
  519                 port_pair_group = {}
  520                 port_pair = {}
  521                 if vnf['name'] in old_ppgs_dict:
  522                     old_ppg_id = old_ppgs_dict.pop(vnf['name'])
  523                     new_ppgs.append(old_ppg_id)
  524                 else:
  525                     if CONNECTION_POINT not in vnf:
  526                         LOG.warning("Chain update failed due to missing "
  527                                     "connection point info in VNF "
  528                                     "%(vnfname)s", {'vnfname': vnf['name']})
  529                         raise nfvo.UpdateChainException(
  530                             message="Connection point not found")
  531                     cp_list = vnf[CONNECTION_POINT]
  532                     num_cps = len(cp_list)
  533                     if num_cps not in [1, 2]:
  534                         LOG.warning("Chain update failed due to wrong number "
  535                                     "of connection points: expected [1 | 2],"
  536                                     "got %(cps)d", {'cps': num_cps})
  537                         raise nfvo.UpdateChainException(
  538                             message="Invalid number of connection points")
  539                     if num_cps == 1:
  540                         ingress = cp_list[0]
  541                         egress = cp_list[0]
  542                     else:
  543                         ingress = cp_list[0]
  544                         egress = cp_list[1]
  545 
  546                     # valid_port_in_use function is used to find out the
  547                     # port_pair_group_id of the existing port pair group
  548                     # which was created by ingress and egress of current VNF
  549                     port_pair_group_id = self.valid_port_in_use(
  550                         ingress, egress, port_pairs_list,
  551                         port_pair_groups_list)
  552                     if not port_pair_group_id:
  553                         port_pair['name'] = vnf['name'] + '-connection-points'
  554                         port_pair['description'] = \
  555                             'port pair for ' + vnf['name']
  556                         port_pair['ingress'] = ingress
  557                         port_pair['egress'] = egress
  558                         port_pair_id = neutronclient_.port_pair_create(
  559                             port_pair)
  560                         if not port_pair_id:
  561                             LOG.warning("Chain update failed due to port pair "
  562                                         "creation failed for "
  563                                         "vnf %(vnf)s", {'vnf': vnf['name']})
  564                             raise nfvo.UpdateChainException(
  565                                 message="Failed to create port-pair")
  566                         port_pair_group['name'] = \
  567                             vnf['name'] + '-port-pair-group'
  568                         port_pair_group['description'] = \
  569                             'port pair group for ' + vnf['name']
  570                         port_pair_group['port_pairs'] = []
  571                         port_pair_group['port_pairs'].append(port_pair_id)
  572                         port_pair_group_id = neutronclient_.\
  573                             port_pair_group_create(port_pair_group)
  574                     if not port_pair_group_id:
  575                         LOG.warning("Chain update failed due to port pair "
  576                                     "group creation failed for vnf "
  577                                     "%(vnf)s", {'vnf': vnf['name']})
  578                         for pp_id in port_pair_group['port_pairs']:
  579                             neutronclient_.port_pair_delete(pp_id)
  580                         raise nfvo.UpdateChainException(
  581                             message="Failed to create port-pair-group")
  582                     new_ppgs.append(port_pair_group_id)
  583             for pc in port_chains_list['port_chains']:
  584                 ppg_list = pc['port_pair_groups']
  585                 if ppg_list == new_ppgs:
  586                     # raise exception when the Vnffg path already exists
  587                     nfvo.UpdateChainException(
  588                         message="Vnffg path already exists")
  589         except nfvo.UpdateChainException as e:
  590             self._delete_ppgs_and_pps(neutronclient_, new_ppgs,
  591                                       past_ppgs_dict, port_chains_list, fc_ids)
  592             raise e
  593 
  594         updated_port_chain['port_pair_groups'] = new_ppgs
  595         updated_port_chain['flow_classifiers'] = fc_ids
  596         try:
  597             pc_id = neutronclient_.port_chain_update(chain_id,
  598                                                      updated_port_chain)
  599         except (nc_exceptions.BadRequest, nfvo.UpdateChainException) as e:
  600             self._delete_ppgs_and_pps(neutronclient_, new_ppgs,
  601                                       past_ppgs_dict, port_chains_list)
  602             raise e
  603         for ppg_name in old_ppgs_dict:
  604             ppg_info = neutronclient_. \
  605                 port_pair_group_show(old_ppgs_dict[ppg_name])
  606             ppg_inuse = self.valid_ppg_for_multiple_chain(
  607                 ppg_info['port_pair_group']['id'], port_chains_list)
  608             if not ppg_inuse:
  609                 neutronclient_.port_pair_group_delete(old_ppgs_dict[ppg_name])
  610                 port_pairs = ppg_info['port_pair_group']['port_pairs']
  611                 if port_pairs and len(port_pairs):
  612                     for j in range(0, len(port_pairs)):
  613                         pp_id = port_pairs[j]
  614                         neutronclient_.port_pair_delete(pp_id)
  615         return pc_id
  616 
  617     def delete_chain(self, chain_id, auth_attr=None):
  618         if not auth_attr:
  619             LOG.warning("auth information required for n-sfc driver")
  620             return None
  621 
  622         neutronclient_ = NeutronClient(auth_attr)
  623         neutronclient_.port_chain_delete(chain_id)
  624 
  625     def update_flow_classifier(self, chain_id, fc, auth_attr=None):
  626         if not auth_attr:
  627             LOG.warning("auth information required for n-sfc driver")
  628             return None
  629 
  630         fc_id = fc.pop('instance_id')
  631         fc_status = fc.pop('status')
  632         match_dict = fc.pop('match')
  633         fc.update(match_dict)
  634 
  635         sfc_classifier_params = self._create_classifier_params(fc)
  636         neutronclient_ = NeutronClient(auth_attr)
  637         if fc_status == constants.PENDING_UPDATE:
  638             fc_info = neutronclient_.flow_classifier_show(fc_id)
  639             for field in sfc_classifier_params:
  640                 # If the new classifier is the same with the old one then
  641                 # no change needed.
  642                 if (fc_info['flow_classifier'].get(field) is not None) and \
  643                         (sfc_classifier_params[field] == fc_info[
  644                             'flow_classifier'][field]):
  645                     continue
  646 
  647                 # If the new classifier has different match criteria
  648                 # with the old one then we strip the classifier from
  649                 # the chain we delete the old classifier and we create
  650                 # a new one with the same name as before but with different
  651                 # match criteria. We are not using the flow_classifier_update
  652                 # from the n-sfc because it does not support match criteria
  653                 # update for an existing classifier yet.
  654                 else:
  655                     try:
  656                         self._dissociate_classifier_from_chain(chain_id,
  657                                                                [fc_id],
  658                                                                neutronclient_)
  659                     except Exception as e:
  660                         raise e
  661                     fc_id = neutronclient_.flow_classifier_create(
  662                         sfc_classifier_params)
  663                     if fc_id is None:
  664                         raise nfvo.UpdateClassifierException(
  665                             message="Failed to update classifiers")
  666                     break
  667 
  668         # If the new classifier is completely different from the existing
  669         # ones (name and match criteria) then we just create it.
  670         else:
  671             fc_id = neutronclient_.flow_classifier_create(
  672                 sfc_classifier_params)
  673             if fc_id is None:
  674                 raise nfvo.UpdateClassifierException(
  675                     message="Failed to update classifiers")
  676 
  677         return fc_id
  678 
  679     def _dissociate_classifier_from_chain(self, chain_id, fc_ids,
  680                                           neutronclient):
  681         pc_info = neutronclient.port_chain_show(chain_id)
  682         current_fc_list = pc_info['port_chain']['flow_classifiers']
  683         for fc_id in fc_ids:
  684             current_fc_list.remove(fc_id)
  685         pc_id = neutronclient.port_chain_update(chain_id,
  686                 {'flow_classifiers': current_fc_list})
  687         if pc_id is None:
  688             raise nfvo.UpdateClassifierException(
  689                 message="Failed to update classifiers")
  690         for fc_id in fc_ids:
  691             try:
  692                 neutronclient.flow_classifier_delete(fc_id)
  693             except ValueError as e:
  694                 raise e
  695 
  696     def remove_and_delete_flow_classifiers(self, chain_id, fc_ids,
  697                                            auth_attr=None):
  698         if not auth_attr:
  699             LOG.warning("auth information required for n-sfc driver")
  700             raise EnvironmentError('auth attribute required for'
  701                                    ' networking-sfc driver')
  702         neutronclient_ = NeutronClient(auth_attr)
  703         try:
  704             self._dissociate_classifier_from_chain(chain_id, fc_ids,
  705                                                    neutronclient_)
  706         except Exception as e:
  707             raise e
  708 
  709     def delete_flow_classifier(self, fc_id, auth_attr=None):
  710         if not auth_attr:
  711             LOG.warning("auth information required for n-sfc driver")
  712             raise EnvironmentError('auth attribute required for'
  713                                    ' networking-sfc driver')
  714 
  715         neutronclient_ = NeutronClient(auth_attr)
  716         neutronclient_.flow_classifier_delete(fc_id)
  717 
  718     def get_mistral_client(self, auth_dict):
  719         if not auth_dict:
  720             LOG.warning("auth dict required to instantiate mistral client")
  721             raise EnvironmentError('auth dict required for'
  722                                    ' mistral workflow driver')
  723         return mistral_client.MistralClient(
  724             keystone.Keystone().initialize_client(**auth_dict),
  725             auth_dict['token']).get_client()
  726 
  727     def prepare_and_create_workflow(self, resource, action,
  728                                     kwargs, auth_dict=None):
  729         mistral_client = self.get_mistral_client(auth_dict)
  730         wg = workflow_generator.WorkflowGenerator(resource, action)
  731         wg.task(**kwargs)
  732         if not wg.get_tasks():
  733             raise nfvo.NoTasksException(resource=resource, action=action)
  734         definition_yaml = yaml.safe_dump(wg.definition)
  735         workflow = mistral_client.workflows.create(definition_yaml)
  736         return {'id': workflow[0].id, 'input': wg.get_input_dict()}
  737 
  738     def execute_workflow(self, workflow, auth_dict=None):
  739         return self.get_mistral_client(auth_dict) \
  740             .executions.create(
  741             workflow_identifier=workflow['id'],
  742             workflow_input=workflow['input'],
  743             wf_params={})
  744 
  745     def get_execution(self, execution_id, auth_dict=None):
  746         return self.get_mistral_client(auth_dict) \
  747             .executions.get(execution_id)
  748 
  749     def delete_execution(self, execution_id, auth_dict=None):
  750         return self.get_mistral_client(auth_dict).executions \
  751             .delete(execution_id, force=True)
  752 
  753     def delete_workflow(self, workflow_id, auth_dict=None):
  754         return self.get_mistral_client(auth_dict) \
  755             .workflows.delete(workflow_id)
  756 
  757     def _delete_ppgs_and_pps(self, neutronclient, new_ppgs,
  758                              past_ppgs_dict, pcs_list, fc_ids):
  759         if new_ppgs:
  760             for item in new_ppgs:
  761                 if item not in past_ppgs_dict.values():
  762                     ppg_inuse = self.valid_ppg_for_multiple_chain(
  763                         item, pcs_list)
  764                     if not ppg_inuse:
  765                         # clean port pair and port pair group if
  766                         # it is not in used
  767                         new_ppg_info = neutronclient.port_pair_group_show(item)
  768                         neutronclient.port_pair_group_delete(item)
  769                         new_port_pairs = new_ppg_info['port_pair_group'][
  770                             'port_pairs']
  771                         if new_port_pairs and len(new_port_pairs):
  772                             for j in range(0, len(new_port_pairs)):
  773                                 new_pp_id = new_port_pairs[j]
  774                                 neutronclient.port_pair_delete(new_pp_id)
  775         # clean flow classifiers
  776         for fc_id in fc_ids:
  777             neutronclient.flow_classifier_delete(fc_id)
  778 
  779     def valid_port_in_use(self, ingress, egress, pps_list, ppgs_list):
  780         # This function checks the the ports are used or not and return the
  781         # port pair group id of these ports
  782         port_pair_list = pps_list['port_pairs']
  783         port_pair_group_list = ppgs_list['port_pair_groups']
  784         port_pair_id = None
  785         port_pair_group_id = None
  786         for pp in port_pair_list:
  787             if (ingress == pp['ingress']) and (egress == pp['egress']):
  788                 port_pair_id = pp['id']
  789                 break
  790         if port_pair_id:
  791             for ppg in port_pair_group_list:
  792                 if port_pair_id in ppg['port_pairs']:
  793                     port_pair_group_id = ppg['id']
  794                     break
  795         return port_pair_group_id
  796 
  797     def valid_ppg_for_multiple_chain(self, ppg_id, pcs_list):
  798         # This function returns True if a ppg belongs to more than one
  799         # port chain. If not return False.
  800         count = 0
  801         for pc in pcs_list['port_chains']:
  802             if ppg_id in pc['port_pair_groups']:
  803                 count = count + 1
  804         return True if count > 1 else False
  805 
  806 
  807 class NeutronClient(object):
  808     """Neutron Client class for networking-sfc driver"""
  809 
  810     def __init__(self, auth_attr):
  811         auth_cred = auth_attr.copy()
  812         verify = 'True' == auth_cred.pop('cert_verify', 'True') or False
  813         auth = identity.Password(**auth_cred)
  814         sess = session.Session(auth=auth, verify=verify)
  815         self.client = neutron_client.Client(session=sess)
  816 
  817     def flow_classifier_show(self, fc_id):
  818         try:
  819             fc = self.client.show_sfc_flow_classifier(fc_id)
  820             if fc is None:
  821                 raise ValueError('classifier %s not found' % fc_id)
  822             return fc
  823         except nc_exceptions.NotFound:
  824             LOG.error('classifier %s not found', fc_id)
  825             raise ValueError('classifier %s not found' % fc_id)
  826 
  827     def flow_classifier_create(self, fc_dict):
  828         LOG.debug("fc_dict passed is {fc_dict}".format(fc_dict=fc_dict))
  829         try:
  830             fc = self.client.create_sfc_flow_classifier(
  831                 {'flow_classifier': fc_dict})
  832             return fc['flow_classifier']['id']
  833         except Exception as ex:
  834             LOG.error("Error while creating Flow Classifier: %s", str(ex))
  835             raise nfvo.FlowClassiferCreationFailed(message=str(ex))
  836 
  837     def flow_classifier_update(self, fc_id, update_fc):
  838         update_fc_dict = {'flow_classifier': update_fc}
  839         return self.client.update_sfc_flow_classifier(fc_id, update_fc_dict)
  840 
  841     def flow_classifier_delete(self, fc_id):
  842         try:
  843             self.client.delete_sfc_flow_classifier(fc_id)
  844         except nc_exceptions.NotFound:
  845             LOG.warning("fc %s not found", fc_id)
  846             raise ValueError('fc %s not found' % fc_id)
  847 
  848     def port_pair_create(self, port_pair_dict):
  849         try:
  850             pp = self.client.create_sfc_port_pair(
  851                 {'port_pair': port_pair_dict})
  852         except nc_exceptions.BadRequest as e:
  853             LOG.error("create port pair returns %s", e)
  854             raise ValueError(str(e))
  855 
  856         if pp and len(pp):
  857             return pp['port_pair']['id']
  858         else:
  859             return None
  860 
  861     def port_pair_list(self):
  862         pp_list = self.client.list_sfc_port_pairs()
  863         return pp_list
  864 
  865     def port_pair_delete(self, port_pair_id):
  866         try:
  867             self.client.delete_sfc_port_pair(port_pair_id)
  868         except nc_exceptions.NotFound:
  869             LOG.warning('port pair %s not found', port_pair_id)
  870             raise ValueError('port pair %s not found' % port_pair_id)
  871 
  872     def port_pair_group_create(self, ppg_dict):
  873         try:
  874             ppg = self.client.create_sfc_port_pair_group(
  875                 {'port_pair_group': ppg_dict})
  876         except nc_exceptions.BadRequest as e:
  877             LOG.warning('create port pair group returns %s', e)
  878             raise ValueError(str(e))
  879 
  880         if ppg and len(ppg):
  881             return ppg['port_pair_group']['id']
  882         else:
  883             return None
  884 
  885     def port_pair_group_list(self):
  886         ppg_list = self.client.list_sfc_port_pair_groups()
  887         return ppg_list
  888 
  889     def port_pair_group_delete(self, ppg_id):
  890         try:
  891             self.client.delete_sfc_port_pair_group(ppg_id)
  892         except nc_exceptions.NotFound:
  893             LOG.warning('port pair group %s not found', ppg_id)
  894             raise ValueError('port pair group %s not found' % ppg_id)
  895 
  896     def port_chain_create(self, port_chain_dict):
  897         try:
  898             pc = self.client.create_sfc_port_chain(
  899                 {'port_chain': port_chain_dict})
  900         except nc_exceptions.BadRequest as e:
  901             LOG.warning('create port chain returns %s', e)
  902             raise ValueError(str(e))
  903 
  904         if pc and len(pc):
  905             return pc['port_chain']['id'], pc['port_chain']['chain_id']
  906         else:
  907             return None
  908 
  909     def port_chain_delete(self, port_chain_id):
  910         try:
  911             port_chain = self.client.show_sfc_port_chain(port_chain_id)
  912             if port_chain:
  913                 self.client.delete_sfc_port_chain(port_chain_id)
  914                 port_chain_list = \
  915                     self.client.list_sfc_port_chains()['port_chains']
  916                 ppg_list = port_chain['port_chain'].get('port_pair_groups')
  917                 if ppg_list and len(ppg_list):
  918                     for i in range(0, len(ppg_list)):
  919                         ppg_in_use = False
  920                         # Firstly, Tacker delete port chain, if a port pair
  921                         # group still belong to other port chains, Tacker
  922                         # will mark it as in_use and does not delete it.
  923                         for pc in port_chain_list:
  924                             if ppg_list[i] in pc['port_pair_groups']:
  925                                 ppg_in_use = True
  926                                 break
  927                         if not ppg_in_use:
  928                             ppg = self.client.show_sfc_port_pair_group(
  929                                 ppg_list[i])
  930                             if ppg:
  931                                 self.client.delete_sfc_port_pair_group(
  932                                     ppg_list[i])
  933                                 port_pairs = \
  934                                     ppg['port_pair_group']['port_pairs']
  935                                 if port_pairs and len(port_pairs):
  936                                     for j in range(0, len(port_pairs)):
  937                                         pp_id = port_pairs[j]
  938                                         self.client.delete_sfc_port_pair(pp_id)
  939         except nc_exceptions.NotFound:
  940             LOG.warning('port chain %s not found', port_chain_id)
  941             raise ValueError('port chain %s not found' % port_chain_id)
  942 
  943     def port_chain_update(self, port_chain_id, port_chain):
  944         try:
  945             pc = self.client.update_sfc_port_chain(port_chain_id,
  946                                     {'port_chain': port_chain})
  947         except nc_exceptions.BadRequest as e:
  948             LOG.warning('update port chain returns %s', e)
  949             raise ValueError(str(e))
  950         if pc and len(pc):
  951             return pc['port_chain']['id']
  952         else:
  953             raise nfvo.UpdateChainException(message="Failed to update "
  954                                                     "port-chain")
  955 
  956     def port_chain_list(self):
  957         pc_list = self.client.list_sfc_port_chains()
  958         return pc_list
  959 
  960     def port_chain_show(self, port_chain_id):
  961         try:
  962             port_chain = self.client.show_sfc_port_chain(port_chain_id)
  963             if port_chain is None:
  964                 raise ValueError('port chain %s not found' % port_chain_id)
  965 
  966             return port_chain
  967         except nc_exceptions.NotFound:
  968             LOG.error('port chain %s not found', port_chain_id)
  969             raise ValueError('port chain %s not found' % port_chain_id)
  970 
  971     def port_pair_group_show(self, ppg_id):
  972         try:
  973             port_pair_group = self.client.show_sfc_port_pair_group(ppg_id)
  974             if port_pair_group is None:
  975                 raise ValueError('port pair group %s not found' % ppg_id)
  976 
  977             return port_pair_group
  978         except nc_exceptions.NotFound:
  979             LOG.warning('port pair group %s not found', ppg_id)
  980             raise ValueError('port pair group %s not found' % ppg_id)