diff --git a/devstack/local.conf.example b/devstack/local.conf.example index cb81692da..1e83da6fb 100644 --- a/devstack/local.conf.example +++ b/devstack/local.conf.example @@ -51,6 +51,7 @@ FIXED_RANGE=${FIXED_RANGE:-15.0.0.0/24} enable_plugin tacker https://git.openstack.org/openstack/tacker master +enable_plugin networking-sfc git://git.openstack.org/openstack/networking-sfc master enable_service n-novnc enable_service n-cauth diff --git a/samples/tosca-templates/vnffgd/tosca-vnffg-vnfd1.yaml b/samples/tosca-templates/vnffgd/tosca-vnffg-vnfd1.yaml new file mode 100644 index 000000000..c59dd3843 --- /dev/null +++ b/samples/tosca-templates/vnffgd/tosca-vnffg-vnfd1.yaml @@ -0,0 +1,76 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: Demo example + +metadata: + template_name: sample-tosca-vnfd1 + +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + capabilities: + nfv_compute: + properties: + num_cpus: 1 + mem_size: 512 MB + disk_size: 1 GB + properties: + image: cirros-0.3.4-x86_64-uec + availability_zone: nova + mgmt_driver: noop + config: | + param0: key1 + param1: key2 + + CP11: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + order: 0 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL11 + - virtualBinding: + node: VDU1 + + CP12: + type: tosca.nodes.nfv.CP.Tacker + properties: + order: 1 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL12 + - virtualBinding: + node: VDU1 + + CP13: + type: tosca.nodes.nfv.CP.Tacker + properties: + order: 2 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL13 + - virtualBinding: + node: VDU1 + + VL11: + type: tosca.nodes.nfv.VL + properties: + network_name: net_mgmt + vendor: Tacker + + VL12: + type: tosca.nodes.nfv.VL + properties: + network_name: net0 + vendor: Tacker + + VL13: + type: tosca.nodes.nfv.VL + properties: + network_name: net1 + vendor: Tacker diff --git a/samples/tosca-templates/vnffgd/tosca-vnffg-vnfd2.yaml b/samples/tosca-templates/vnffgd/tosca-vnffg-vnfd2.yaml new file mode 100644 index 000000000..f9d52e812 --- /dev/null +++ b/samples/tosca-templates/vnffgd/tosca-vnffg-vnfd2.yaml @@ -0,0 +1,76 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: Demo example + +metadata: + template_name: sample-tosca-vnfd1 + +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + capabilities: + nfv_compute: + properties: + num_cpus: 1 + mem_size: 512 MB + disk_size: 1 GB + properties: + image: cirros-0.3.4-x86_64-uec + availability_zone: nova + mgmt_driver: noop + config: | + param0: key1 + param1: key2 + + CP21: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + order: 0 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL21 + - virtualBinding: + node: VDU1 + + CP22: + type: tosca.nodes.nfv.CP.Tacker + properties: + order: 1 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL22 + - virtualBinding: + node: VDU1 + + CP23: + type: tosca.nodes.nfv.CP.Tacker + properties: + order: 2 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL23 + - virtualBinding: + node: VDU1 + + VL21: + type: tosca.nodes.nfv.VL + properties: + network_name: net_mgmt + vendor: Tacker + + VL22: + type: tosca.nodes.nfv.VL + properties: + network_name: net0 + vendor: Tacker + + VL23: + type: tosca.nodes.nfv.VL + properties: + network_name: net1 + vendor: Tacker diff --git a/samples/tosca-templates/vnffgd/tosca-vnffgd-sample.yaml b/samples/tosca-templates/vnffgd/tosca-vnffgd-sample.yaml new file mode 100644 index 000000000..133cc4870 --- /dev/null +++ b/samples/tosca-templates/vnffgd/tosca-vnffgd-sample.yaml @@ -0,0 +1,39 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: Sample VNFFG template + +topology_template: + description: Sample VNFFG template + + node_templates: + + Forwarding_path1: + type: tosca.nodes.nfv.FP.Tacker + description: creates path (CP12->CP22) + properties: + id: 51 + policy: + type: ACL + criteria: + - network_src_port_id: 640dfd77-c92b-45a3-b8fc-22712de480e1 + - destination_port_range: 80-1024 + - ip_proto: 6 + - ip_dst_prefix: 192.168.1.2/24 + path: + - forwarder: VNFD1 + capability: CP12 + - forwarder: VNFD2 + capability: CP22 + + groups: + VNFFG1: + type: tosca.groups.nfv.VNFFG + description: HTTP to Corporate Net + properties: + vendor: tacker + version: 1.0 + number_of_endpoints: 5 + dependent_virtual_link: [VL12,VL22] + connection_point: [CP12,CP22] + constituent_vnfs: [VNFD1,VNFD2] + members: [Forwarding_path1] diff --git a/tacker/nfvo/drivers/vim/openstack_driver.py b/tacker/nfvo/drivers/vim/openstack_driver.py index ea6c41d81..8ba48fdf1 100644 --- a/tacker/nfvo/drivers/vim/openstack_driver.py +++ b/tacker/nfvo/drivers/vim/openstack_driver.py @@ -17,10 +17,12 @@ import os import six +from keystoneauth1 import identity +from keystoneauth1 import session from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient import exceptions -from keystoneclient import session +from neutronclient.common import exceptions as nc_exceptions from neutronclient.v2_0 import client as neutron_client from oslo_config import cfg from oslo_log import log as logging @@ -30,6 +32,7 @@ from tacker.agent.linux import utils as linux_utils from tacker.common import log from tacker.extensions import nfvo from tacker.nfvo.drivers.vim import abstract_vim_driver +from tacker.nfvo.drivers.vnffg import abstract_vnffg_driver from tacker.vnfm import keystone @@ -56,12 +59,27 @@ _VALID_RESOURCE_TYPES = {'network': {'client': neutron_client.Client, } } +FC_MAP = {'name': 'name', + 'description': 'description', + 'eth_type': 'ethertype', + 'ip_src_prefix': 'source_ip_prefix', + 'ip_dst_prefix': 'destination_ip_prefix', + 'source_port_min': 'source_port_range_min', + 'source_port_max': 'source_port_range_max', + 'destination_port_min': 'destination_port_range_min', + 'destination_port_max': 'destination_port_range_max', + 'network_src_port_id': 'logical_source_port', + 'network_dst_port_id': 'logical_destination_port'} + +CONNECTION_POINT = 'connection_points' + def config_opts(): return [('vim_keys', OPTS), ('vim_monitor', OPENSTACK_OPTS)] -class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver): +class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver, + abstract_vnffg_driver.VnffgAbstractDriver): """Driver for OpenStack VIM OpenStack driver handles interactions with local as well as @@ -274,3 +292,263 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver): auth_plugin = self._get_auth_plugin(keystone_version, **auth_cred) sess = session.Session(auth=auth_plugin) return client_type(session=sess) + + def create_flow_classifier(self, name, fc, symmetrical=False, + auth_attr=None): + def _translate_ip_protocol(ip_proto): + if ip_proto == '1': + return 'icmp' + elif ip_proto == '6': + return 'tcp' + elif ip_proto == '17': + return 'udp' + else: + return None + + if not auth_attr: + LOG.warning(_("auth information required for n-sfc driver")) + return None + + if symmetrical: + LOG.warning(_("n-sfc driver does not support symmetrical")) + raise NotImplementedError('symmetrical chain not supported') + LOG.debug(_('fc passed is %s'), fc) + sfc_classifier_params = {} + for field in fc: + if field in FC_MAP: + sfc_classifier_params[FC_MAP[field]] = fc[field] + elif field == 'ip_proto': + protocol = _translate_ip_protocol(str(fc[field])) + if not protocol: + raise ValueError('protocol %s not supported' % fc[field]) + sfc_classifier_params['protocol'] = protocol + else: + LOG.warning(_("flow classifier %s not supported by " + "networking-sfc driver"), field) + + LOG.debug(_('sfc_classifier_params is %s'), sfc_classifier_params) + if len(sfc_classifier_params) > 0: + neutronclient_ = NeutronClient(auth_attr) + + fc_id = neutronclient_.flow_classifier_create( + sfc_classifier_params) + return fc_id + + raise ValueError('empty match field for input flow classifier') + + def create_chain(self, name, fc_id, vnfs, symmetrical=False, + auth_attr=None): + if not auth_attr: + LOG.warning(_("auth information required for n-sfc driver")) + return None + + if symmetrical: + LOG.warning(_("n-sfc driver does not support symmetrical")) + raise NotImplementedError('symmetrical chain not supported') + + neutronclient_ = NeutronClient(auth_attr) + port_pair_group_list = [] + for vnf in vnfs: + # TODO(s3wong): once scaling is in place and VNFFG supports it + # that model needs to be implemented to concatenate all + # port-pairs into the port-pair-group + # port pair group could include port-pairs from different VNFs + port_pair_group = {} + port_pair_group['name'] = vnf['name'] + '-port-pair-group' + port_pair_group['description'] = \ + 'port pair group for %s' % vnf['name'] + port_pair_group['port_pairs'] = [] + if CONNECTION_POINT not in vnf: + LOG.warning(_("Chain creation failed due to missing " + "connection point info in VNF " + "%(vnfname)s"), {'vnfname': vnf['name']}) + return None + cp_list = vnf[CONNECTION_POINT] + num_cps = len(cp_list) + if num_cps != 1 and num_cps != 2: + LOG.warning(_("Chain creation failed due to wrong number of " + "connection points: expected [1 | 2], got " + "%(cps)d"), {'cps': num_cps}) + return None + port_pair = {} + port_pair['name'] = vnf['name'] + '-connection-points' + port_pair['description'] = 'port pair for %s' % vnf['name'] + if num_cps == 1: + port_pair['ingress'] = cp_list[0] + port_pair['egress'] = cp_list[0] + else: + port_pair['ingress'] = cp_list[0] + port_pair['egress'] = cp_list[1] + port_pair_id = neutronclient_.port_pair_create(port_pair) + if not port_pair_id: + LOG.warning(_("Chain creation failed due to port pair creation" + " failed for vnf %(vnf)s"), {'vnf': vnf['name']}) + return None + port_pair_group['port_pairs'].append(port_pair_id) + port_pair_group_id = \ + neutronclient_.port_pair_group_create(port_pair_group) + if not port_pair_group_id: + LOG.warning(_("Chain creation failed due to port pair group " + "creation failed for vnf " + "%(vnf)s"), {'vnf': vnf['name']}) + return None + port_pair_group_list.append(port_pair_group_id) + + # TODO(s3wong): should the chain name be given as a parameter? + port_chain = {} + port_chain['name'] = name + '-port-chain' + port_chain['description'] = 'port-chain for Tacker VNFFG' + port_chain['port_pair_groups'] = port_pair_group_list + port_chain['flow_classifiers'] = [fc_id] + return neutronclient_.port_chain_create(port_chain) + + def update_chain(self, chain_id, fc_ids, vnfs, + symmetrical=False, auth_attr=None): + # TODO(s3wong): chain can be updated either for + # the list of fc and/or list of port-pair-group + # since n-sfc driver does NOT track the ppg id + # it will look it up (or reconstruct) from + # networking-sfc DB --- but the caveat is that + # the VNF name MUST be unique + LOG.warning(_("n-sfc driver does not support sf chain update")) + raise NotImplementedError('sf chain update not supported') + + def delete_chain(self, chain_id, auth_attr=None): + if not auth_attr: + LOG.warning(_("auth information required for n-sfc driver")) + return None + + neutronclient_ = NeutronClient(auth_attr) + neutronclient_.port_chain_delete(chain_id) + + def update_flow_classifier(self, fc_id, fc, + symmetrical=False, auth_attr=None): + if not auth_attr: + LOG.warning(_("auth information required for n-sfc driver")) + return None + + if symmetrical: + LOG.warning(_("n-sfc driver does not support symmetrical")) + raise NotImplementedError('symmetrical chain not supported') + + # for now, the only parameters allowed for flow-classifier-update + # is 'name' and/or 'description' + + sfc_classifier_params = {} + sfc_classifier_params['name'] = fc['name'] + sfc_classifier_params['description'] = fc['description'] + + LOG.debug(_('sfc_classifier_params is %s'), sfc_classifier_params) + + neutronclient_ = NeutronClient(auth_attr) + return neutronclient_.flow_classifier_update(fc_id, + sfc_classifier_params) + + def delete_flow_classifier(self, fc_id, auth_attr=None): + if not auth_attr: + LOG.warning(_("auth information required for n-sfc driver")) + raise EnvironmentError('auth attribute required for' + ' networking-sfc driver') + + neutronclient_ = NeutronClient(auth_attr) + neutronclient_.flow_classifier_delete(fc_id) + + +class NeutronClient(object): + """Neutron Client class for networking-sfc driver""" + + def __init__(self, auth_attr): + auth = identity.Password(**auth_attr) + sess = session.Session(auth=auth) + self.client = neutron_client.Client(session=sess) + + def flow_classifier_create(self, fc_dict): + LOG.debug(_("fc_dict passed is {fc_dict}").format(fc_dict=fc_dict)) + fc = self.client.create_flow_classifier({'flow_classifier': fc_dict}) + if fc: + return fc['flow_classifier']['id'] + else: + return None + + def flow_classifier_update(self, fc_id, update_fc): + update_fc_dict = {'flow_classifier': update_fc} + return self.client.update_flow_classifier(fc_id, update_fc_dict) + + def flow_classifier_delete(self, fc_id): + try: + self.client.delete_flow_classifier(fc_id) + except nc_exceptions.NotFound: + LOG.warning(_("fc %s not found") % fc_id) + raise ValueError('fc %s not found' % fc_id) + + def port_pair_create(self, port_pair_dict): + try: + pp = self.client.create_port_pair({'port_pair': port_pair_dict}) + except nc_exceptions.BadRequest as e: + LOG.error(_("create port pair returns %s") % e) + raise ValueError(str(e)) + + if pp and len(pp): + return pp['port_pair']['id'] + else: + return None + + def port_pair_delete(self, port_pair_id): + try: + self.client.delete_port_pair(port_pair_id) + except nc_exceptions.NotFound: + LOG.warning(_('port pair %s not found') % port_pair_id) + raise ValueError('port pair %s not found' % port_pair_id) + + def port_pair_group_create(self, ppg_dict): + try: + ppg = self.client.create_port_pair_group( + {'port_pair_group': ppg_dict}) + except nc_exceptions.BadRequest as e: + LOG.warning(_('create port pair group returns %s') % e) + raise ValueError(str(e)) + + if ppg and len(ppg): + return ppg['port_pair_group']['id'] + else: + return None + + def port_pair_group_delete(self, ppg_id): + try: + self.client.delete_port_pair_group(ppg_id) + except nc_exceptions.NotFound: + LOG.warning(_('port pair group %s not found') % ppg_id) + raise ValueError('port pair group %s not found' % ppg_id) + + def port_chain_create(self, port_chain_dict): + try: + pc = self.client.create_port_chain( + {'port_chain': port_chain_dict}) + except nc_exceptions.BadRequest as e: + LOG.warning(_('create port chain returns %s') % e) + raise ValueError(str(e)) + + if pc and len(pc): + return pc['port_chain']['id'] + else: + return None + + def port_chain_delete(self, port_chain_id): + try: + port_chain = self.client.show_port_chain(port_chain_id) + if port_chain: + self.client.delete_port_chain(port_chain_id) + ppg_list = port_chain['port_chain'].get('port_pair_groups') + if ppg_list and len(ppg_list): + for i in xrange(0, len(ppg_list)): + ppg = self.client.show_port_pair_group(ppg_list[i]) + if ppg: + self.client.delete_port_pair_group(ppg_list[i]) + port_pairs = ppg['port_pair_group']['port_pairs'] + if port_pairs and len(port_pairs): + for j in xrange(0, len(port_pairs)): + pp_id = port_pairs[j] + self.client.delete_port_pair(pp_id) + except nc_exceptions.NotFound: + LOG.warning(_('port chain %s not found') % port_chain_id) + raise ValueError('port chain %s not found' % port_chain_id) diff --git a/tacker/nfvo/drivers/vnffg/abstract_vnffg_driver.py b/tacker/nfvo/drivers/vnffg/abstract_vnffg_driver.py index a8be9c168..66cb891b7 100755 --- a/tacker/nfvo/drivers/vnffg/abstract_vnffg_driver.py +++ b/tacker/nfvo/drivers/vnffg/abstract_vnffg_driver.py @@ -35,7 +35,8 @@ class VnffgAbstractDriver(extensions.PluginInterface): pass @abc.abstractmethod - def create_chain(self, fc_id, vnfs, symmetrical=False, auth_attr=None): + def create_chain(self, name, fc_id, vnfs, symmetrical=False, + auth_attr=None): """Create service function chain and returns an ID""" pass @@ -51,7 +52,8 @@ class VnffgAbstractDriver(extensions.PluginInterface): pass @abc.abstractmethod - def create_flow_classifier(self, fc, symmetrical=False, auth_attr=None): + def create_flow_classifier(self, name, fc, symmetrical=False, + auth_attr=None): """Create flow classifier and returns an ID""" pass diff --git a/tacker/nfvo/drivers/vnffg/sfc_drivers/__init__.py b/tacker/nfvo/drivers/vnffg/sfc_drivers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/nfvo/drivers/vnffg/sfc_drivers/noop.py b/tacker/nfvo/drivers/vnffg/sfc_drivers/noop.py index 492c9bf8a..847251e00 100644 --- a/tacker/nfvo/drivers/vnffg/sfc_drivers/noop.py +++ b/tacker/nfvo/drivers/vnffg/sfc_drivers/noop.py @@ -40,7 +40,7 @@ class VNFFGNoop(abstract_driver.SfcAbstractDriver): return 'VNFFG Noop driver' @log.log - def create_chain(self, fc_id, vnfs, auth_attr=None): + def create_chain(self, name, fc_id, vnfs, auth_attr=None): instance_id = str(uuid.uuid4()) self._instances.add(instance_id) return instance_id @@ -56,7 +56,7 @@ class VNFFGNoop(abstract_driver.SfcAbstractDriver): self._instances.remove(chain_id) @log.log - def create_flow_classifier(self, fc, auth_attr=None): + def create_flow_classifier(self, name, fc, auth_attr=None): instance_id = str(uuid.uuid4()) self._instances.add(instance_id) return instance_id diff --git a/tacker/nfvo/nfvo_plugin.py b/tacker/nfvo/nfvo_plugin.py index cf78ba318..959b40cab 100644 --- a/tacker/nfvo/nfvo_plugin.py +++ b/tacker/nfvo/nfvo_plugin.py @@ -14,10 +14,12 @@ # License for the specific language governing permissions and limitations # under the License. +import os import threading import time import uuid +from cryptography import fernet from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils @@ -39,6 +41,7 @@ from toscaparser import tosca_template LOG = logging.getLogger(__name__) +CONF = cfg.CONF def config_opts(): @@ -216,25 +219,28 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin): sfc = super(NfvoPlugin, self).get_sfc(context, nfp['chain_id']) match = super(NfvoPlugin, self).get_classifier(context, nfp['classifier_id'], - fields='match') + fields='match')['match'] # grab the first VNF to check it's VIM type # we have already checked that all VNFs are in the same VIM - vim_auth = self._get_vim_from_vnf(context, - list(vnffg_dict[ - 'vnf_mapping'].values())[0] - ) + vim_obj = self._get_vim_from_vnf(context, + list(vnffg_dict[ + 'vnf_mapping'].values())[0]) # TODO(trozet): figure out what auth info we actually need to pass # to the driver. Is it a session, or is full vim obj good enough? - driver_type = vim_auth['type'] + driver_type = vim_obj['type'] try: fc_id = self._vim_drivers.invoke(driver_type, 'create_flow_classifier', - fc=match, auth_attr=vim_auth, + name=vnffg_dict['name'], + fc=match, + auth_attr=vim_obj['auth_cred'], symmetrical=sfc['symmetrical']) - sfc_id = self._vim_drivers.invoke(driver_type, 'create_chain', + sfc_id = self._vim_drivers.invoke(driver_type, + 'create_chain', + name=vnffg_dict['name'], vnfs=sfc['chain'], fc_id=fc_id, symmetrical=sfc['symmetrical'], - auth_attr=vim_auth) + auth_attr=vim_obj['auth_cred']) except Exception: with excutils.save_and_reraise_exception(): self.delete_vnffg(context, vnffg_id=vnffg_dict['id']) @@ -276,24 +282,23 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin): LOG.debug(_('chain update: %s'), chain) sfc['chain'] = chain sfc['symmetrical'] = new_vnffg['symmetrical'] - vim_auth = self._get_vim_from_vnf(context, - list(vnffg_dict[ - 'vnf_mapping'].values())[0] - ) - driver_type = vim_auth['type'] + vim_obj = self._get_vim_from_vnf(context, + list(vnffg_dict[ + 'vnf_mapping'].values())[0]) + driver_type = vim_obj['type'] try: # we don't support updating the match criteria in first iteration # so this is essentially a noop. Good to keep for future use # though. self._vim_drivers.invoke(driver_type, 'update_flow_classifier', fc_id=fc['instance_id'], fc=fc['match'], - auth_attr=vim_auth, + auth_attr=vim_obj['auth_cred'], symmetrical=new_vnffg['symmetrical']) self._vim_drivers.invoke(driver_type, 'update_chain', vnfs=sfc['chain'], fc_ids=[fc['instance_id']], chain_id=sfc['instance_id'], - auth_attr=vim_auth, + auth_attr=vim_obj['auth_cred'], symmetrical=new_vnffg['symmetrical']) except Exception: with excutils.save_and_reraise_exception(): @@ -321,21 +326,20 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin): fc = super(NfvoPlugin, self).get_classifier(context, nfp['classifier_id']) - vim_auth = self._get_vim_from_vnf(context, - list(vnffg_dict[ - 'vnf_mapping'].values())[0] - ) - driver_type = vim_auth['type'] + vim_obj = self._get_vim_from_vnf(context, + list(vnffg_dict[ + 'vnf_mapping'].values())[0]) + driver_type = vim_obj['type'] try: if sfc['instance_id'] is not None: self._vim_drivers.invoke(driver_type, 'delete_chain', chain_id=sfc['instance_id'], - auth_attr=vim_auth) + auth_attr=vim_obj['auth_cred']) if fc['instance_id'] is not None: self._vim_drivers.invoke(driver_type, 'delete_flow_classifier', fc_id=fc['instance_id'], - auth_attr=vim_auth) + auth_attr=vim_obj['auth_cred']) except Exception: with excutils.save_and_reraise_exception(): vnffg_dict['status'] = constants.ERROR @@ -353,11 +357,37 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin): """ vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM'] vim_id = vnfm_plugin.get_vnf(context, vnf_id, fields=['vim_id']) - vim_obj = self.get_vim(context, vim_id['vim_id']) + vim_obj = self.get_vim(context, vim_id['vim_id'], mask_password=False) + vim_auth = vim_obj['auth_cred'] + vim_auth['password'] = self._decode_vim_auth(vim_obj['id'], + vim_auth['password']. + encode('utf-8')) + vim_auth['auth_url'] = vim_obj['auth_url'] if vim_obj is None: raise nfvo.VimFromVnfNotFoundException(vnf_id=vnf_id) + return vim_obj + def _decode_vim_auth(self, vim_id, cred): + """Decode Vim credentials + + Decrypt VIM cred. using Fernet Key + """ + vim_key = self._find_vim_key(vim_id) + f = fernet.Fernet(vim_key) + if not f: + LOG.warning(_('Unable to decode VIM auth')) + raise nfvo.VimNotFoundException('Unable to decode VIM auth key') + return f.decrypt(cred) + + @staticmethod + def _find_vim_key(vim_id): + key_file = os.path.join(CONF.vim_keys.openstack, vim_id) + LOG.debug(_('Attempting to open key file for vim id %s'), vim_id) + with open(key_file, 'r') as f: + return f.read() + LOG.warning(_('VIM id invalid or key not found for %s'), vim_id) + def _vim_resource_name_to_id(self, context, resource, name, vnf_id): """Converts a VIM resource name to its ID @@ -367,10 +397,10 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin): the classifier will apply to :return: ID of the resource name """ - vim_auth = self._get_vim_from_vnf(context, vnf_id) - driver_type = vim_auth['type'] + vim_obj = self._get_vim_from_vnf(context, vnf_id) + driver_type = vim_obj['type'] return self._vim_drivers.invoke(driver_type, 'get_vim_resource_id', - vim_auth=vim_auth, + vim_auth=vim_obj['auth_cred'], resource_type=resource, resource_name=name) diff --git a/tacker/tests/unit/vm/nfvo/drivers/vnffg/__init__.py b/tacker/tests/unit/vm/nfvo/drivers/vnffg/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/__init__.py b/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/__init__.py b/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/test_n_sfc.py b/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/test_n_sfc.py new file mode 100755 index 000000000..746dbaa3c --- /dev/null +++ b/tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/test_n_sfc.py @@ -0,0 +1,234 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import uuid + +from tacker import context +from tacker.nfvo.drivers.vim import openstack_driver +from tacker.tests.unit import base +from tacker.tests.unit.db import utils + + +class FakeNeutronClient(mock.Mock): + def __init__(self): + super(FakeNeutronClient, self).__init__() + self.__fc_dict = {} + self.__pp_dict = {} + self.__ppg_dict = {} + self.__chain_dict = {} + + def flow_classifier_create(self, fc_create_dict): + fc_id = uuid.uuid4() + self.__fc_dict[fc_id] = fc_create_dict + return fc_id + + def show_flow_classifier(self, fc_dict): + fc_name = fc_dict['name'] + for fc_id in self.__fc_dict: + fc = self.__fc_dict[fc_id] + if fc_name == fc['name']: + return {'id': fc_id} + + return None + + def flow_classifier_update(self, fc_id, fc_update_dict): + if fc_id not in self.__fc_dict: + return None + self.__fc_dict[fc_id] = fc_update_dict + return fc_update_dict + + def flow_classifier_delete(self, fc_id): + if fc_id not in self.__fc_dict: + raise ValueError('fc not found') + self.__fc_dict.pop(fc_id) + + def port_pair_create(self, port_pair): + pp_id = uuid.uuid4() + self.__pp_dict[pp_id] = port_pair + return pp_id + + def show_port_pair(self, port_pair_dict): + input_pp_name = port_pair_dict['name'] + for pp_id in self.__pp_dict: + port_pair = self.__pp_dict[pp_id] + if port_pair['name'] == input_pp_name: + return {'id': pp_id} + + return None + + def port_pair_group_create(self, port_pair_group): + ppg_id = uuid.uuid4() + self.__ppg_dict[ppg_id] = port_pair_group + return ppg_id + + def show_port_pair_group(self, port_pair_group_dict): + input_ppg_name = port_pair_group_dict['name'] + for ppg_id in self.__ppg_dict: + port_pair_group = self.__ppg_dict[ppg_id] + if port_pair_group['name'] == input_ppg_name: + return {'id': ppg_id} + + return None + + def port_chain_create(self, port_chain): + chain_id = uuid.uuid4() + self.__chain_dict[chain_id] = port_chain + return chain_id + + def show_port_chain(self, port_chain_dict): + input_chain_name = port_chain_dict['name'] + for chain_id in self.__chain_dict: + port_chain = self.__chain_dict[chain_id] + if port_chain['name'] == input_chain_name: + return {'id': chain_id} + return None + + def port_chain_delete(self, chain_id): + if chain_id not in self.__chain_dict: + raise ValueError('port chain delete failed') + self.__chain_dict.pop(chain_id) + + +class TestChainSFC(base.TestCase): + + def setUp(self): + super(TestChainSFC, self).setUp() + self.context = context.get_admin_context() + self.sfc_driver = openstack_driver.OpenStack_Driver() + self._mock_neutron_client() + self.addCleanup(mock.patch.stopall) + + def _mock_neutron_client(self): + self.neutron_client = mock.Mock(wraps=FakeNeutronClient()) + fake_neutron_client = mock.Mock() + fake_neutron_client.return_value = self.neutron_client + self._mock( + 'tacker.nfvo.drivers.vim.openstack_driver.' + 'NeutronClient', + fake_neutron_client) + + def _mock(self, target, new=mock.DEFAULT): + patcher = mock.patch(target, new) + return patcher.start() + + def test_create_flow_classifier(self): + flow_classifier = {'name': 'fake_fc', + 'source_port_range': '2005-2010', + 'ip_proto': 6, + 'destination_port_range': '80-180'} + result = self.sfc_driver.\ + create_flow_classifier(name='fake_ffg', fc=flow_classifier, + auth_attr=utils.get_vim_auth_obj()) + self.assertIsNotNone(result) + + def test_update_flow_classifier(self): + flow_classifier = {'name': 'next_fake_fc', + 'description': 'fake flow-classifier', + 'source_port_range': '2005-2010', + 'ip_proto': 6, + 'destination_port_range': '80-180'} + fc_id = self.sfc_driver.\ + create_flow_classifier(name='fake_ffg', fc=flow_classifier, + auth_attr=utils.get_vim_auth_obj()) + + self.assertIsNotNone(fc_id) + + flow_classifier['description'] = 'next fake flow-classifier' + + result = self.sfc_driver.\ + update_flow_classifier(fc_id=fc_id, + fc=flow_classifier, + auth_attr=utils.get_vim_auth_obj()) + self.assertIsNotNone(result) + + def test_delete_flow_classifier(self): + flow_classifier = {'name': 'another_fake_fc', + 'description': 'another flow-classifier', + 'source_port_range': '1999-2005', + 'ip_proto': 6, + 'destination_port_range': '80-100'} + fc_id = self.sfc_driver.\ + create_flow_classifier(name='fake_ffg', fc=flow_classifier, + auth_attr=utils.get_vim_auth_obj()) + + self.assertIsNotNone(fc_id) + + try: + self.sfc_driver.\ + delete_flow_classifier(fc_id=fc_id, + auth_attr=utils.get_vim_auth_obj()) + except Exception: + self.assertTrue(True) + + def test_create_chain(self): + auth_attr = utils.get_vim_auth_obj() + flow_classifier = {'name': 'test_create_chain_fc', + 'description': 'fc for testing create chain', + 'source_port_range': '1997-2008', + 'ip_proto': 6, + 'destination_port_range': '80-100'} + fc_id = self.sfc_driver.\ + create_flow_classifier(name='fake_ffg', fc=flow_classifier, + auth_attr=auth_attr) + + self.assertIsNotNone(fc_id) + + vnf_1 = {'name': 'test_create_chain_vnf_1', + 'connection_points': [uuid.uuid4(), uuid.uuid4()]} + vnf_2 = {'name': 'test_create_chain_vnf_2', + 'connection_points': [uuid.uuid4(), uuid.uuid4()]} + vnf_3 = {'name': 'test_create_chain_vnf_3', + 'connection_points': [uuid.uuid4(), uuid.uuid4()]} + vnfs = [vnf_1, vnf_2, vnf_3] + + result = self.sfc_driver.create_chain(name='fake_ffg', + fc_id=fc_id, + vnfs=vnfs, + auth_attr=auth_attr) + + self.assertIsNotNone(result) + + def test_delete_chain(self): + auth_attr = utils.get_vim_auth_obj() + flow_classifier = {'name': 'test_delete_chain_fc', + 'description': 'fc for testing delete chain', + 'source_port_range': '1000-2000', + 'ip_proto': 6, + 'destination_port_range': '80-180'} + fc_id = self.sfc_driver.\ + create_flow_classifier(name='fake_ffg', fc=flow_classifier, + auth_attr=auth_attr) + + self.assertIsNotNone(fc_id) + + vnf_1 = {'name': 'test_delete_chain_vnf_1', + 'connection_points': [uuid.uuid4(), uuid.uuid4()]} + vnf_2 = {'name': 'test_delete_chain_vnf_2', + 'connection_points': [uuid.uuid4(), uuid.uuid4()]} + vnf_3 = {'name': 'test_delete_chain_vnf_3', + 'connection_points': [uuid.uuid4(), uuid.uuid4()]} + vnfs = [vnf_1, vnf_2, vnf_3] + + chain_id = self.sfc_driver.create_chain(name='fake_ffg', + fc_id=fc_id, + vnfs=vnfs, + auth_attr=auth_attr) + + self.assertIsNotNone(chain_id) + + try: + self.sfc_driver.delete_chain(chain_id, + auth_attr=auth_attr) + except Exception: + self.assertTrue(True) diff --git a/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py b/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py index 25481c1a6..61224bfbe 100644 --- a/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py +++ b/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py @@ -35,9 +35,10 @@ SECRET_PASSWORD = '***' def dummy_get_vim(*args, **kwargs): - vim_auth = utils.get_vim_auth_obj() - vim_auth['type'] = 'openstack' - return vim_auth + vim_obj = dict() + vim_obj['auth_cred'] = utils.get_vim_auth_obj() + vim_obj['type'] = 'openstack' + return vim_obj class FakeDriverManager(mock.Mock): @@ -355,6 +356,7 @@ class TestNfvoPlugin(db_base.SqlTestCase): self.assertIn('status', result) self.assertEqual('PENDING_CREATE', result['status']) self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY, + name=mock.ANY, vnfs=mock.ANY, fc_id=mock.ANY, auth_attr=mock.ANY, @@ -375,6 +377,7 @@ class TestNfvoPlugin(db_base.SqlTestCase): self.assertIn('status', result) self.assertEqual('PENDING_CREATE', result['status']) self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY, + name=mock.ANY, vnfs=mock.ANY, fc_id=mock.ANY, auth_attr=mock.ANY,