Implement the vnf-mapping update functionality

Although tacker client side supports the update of the VNFs which
are currently used in an existing VNFFG this functionality is not
implemented at the Tacker server side.

Through this patch we are trying to address exactly that problem.
Also this patch is the first step for the implementation of this:

Implements: blueprint update-vnffg

Change-Id: I147e9f8a52f3e307767cd855492eb9bb5867cf29
Signed-off-by: Dimitrios Markou <mardim@intracom-telecom.com>
This commit is contained in:
Dimitrios Markou 2017-10-20 15:22:51 +03:00
parent 82250ba7b4
commit 0a52d6fb4a
8 changed files with 439 additions and 91 deletions

View File

@ -257,6 +257,52 @@ sub-components for a rendered VNFFG:
tacker classifier-list
tacker classifier-show <classifier id>
Updating the VNFFG
~~~~~~~~~~~~~~~~~~
To update an already created VNFFG template the user needs to locate the VNFFG
which wants to update. To do so the following command is getting executed:
Using the below command query the list of existing VNFFG templates.
.. code-block:: console
tacker vnffg-list
+--------------------+---------+-------+-------------------------------------+
| id | name | status | vnffgd_id |
+--------------------+-----------------+-------------------------------------+
| f4438511-e33d-43df-| | | |
| 95d9-0199253db72e | myvnffg| ACTIVE | bd7829bf-85de-4f3b-960a-8482028bfb34|
+--------------------+---------+-------+-------------+--------+--------------+
After the user located the VNFFG the subsequent action is to update it.
Based on the appropriate choice, update VNFFG template.
Currently we support only the update of the vnf-mapping in a VNFFG.
The user needs to use a VNF which is actually derived from the VNFD which
is going to be used in the vnf-mapping parameter.
If the user is not sure which VNF was used for the mapping during the time
of the VNFFG creation he can execute:
Execute the below command to query the VNF that was used in mapping at the time
of VNFFG creation.
.. code-block:: console
tacker vnffg-show myvnffg
After user determined which VNF is used and which VNF is going to be used
in the update procedure he can execute:
To update the VNF mappings to VNFFG, execute the below command
.. code-block:: console
tacker vnffg-update --vnf-mapping VNFD1:vnf1,VNFD2:vnf2 myvnffg
Updated vnffg: myvnffg
Known Issues and Limitations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,4 @@
---
features:
- |
VNFFG Update - VNF mapping functionality

View File

@ -36,8 +36,9 @@ _ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE)
_ACTIVE_UPDATE_ERROR_DEAD = (
constants.PENDING_CREATE, constants.ACTIVE, constants.PENDING_UPDATE,
constants.ERROR, constants.DEAD)
_VALID_VNFFG_UPDATE_ATTRIBUTES = ('name', 'description', 'vnf_mapping')
_VALID_VNFFG_UPDATE_ATTRIBUTES = ('vnf_mapping',)
_VALID_SFC_UPDATE_ATTRIBUTES = ('chain', 'symmetrical')
_VALID_NFP_UPDATE_ATTRIBUTES = ('symmetrical',)
_VALID_FC_UPDATE_ATTRIBUTES = ()
MATCH_CRITERIA = (
'eth_type', 'eth_src', 'eth_dst', 'vlan_id', 'vlan_pcp', 'mpls_label',
@ -536,6 +537,31 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
if val is not None:
return val
def _validate_vnfd_in_vnf_mapping(self, vnf_mapping, vnf_members):
"""Validate whether or not the vnf_mapping is valid for update.
In the update_vnnfg procedure we need to know whether or not the
the vnf_mapping is valid so we can use it to update the chain.
"""
if not vnf_mapping:
raise nfvo.VnfMappingNotFoundException()
else:
for vnfd, vnf in vnf_mapping.items():
if vnfd not in vnf_members:
raise nfvo.VnfMappingNotValidException(vnfd=vnfd)
def _combine_current_and_new_vnf_mapping(self, context,
new_mapping, old_mapping):
"""Create an updated vnf mapping.
In this function we create an updated vnf mapping which is
a mix of the vnf_mapping which already exists in database
and the new mapping that the user passes.
"""
updated_vnf_mapping = old_mapping.copy()
updated_vnf_mapping.update(new_mapping)
return updated_vnf_mapping
def _get_vnf_mapping(self, context, vnf_mapping, vnf_members):
"""Creates/validates a mapping of VNFD names to VNF IDs for NFP.
@ -803,63 +829,82 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
constants.PENDING_UPDATE)
return self._make_vnffg_dict(vnffg_db)
def _update_vnffg_post(self, context, vnffg_id, new_status,
new_vnffg=None):
def _update_vnffg_post(self, context, vnffg_id,
updated_items,
n_sfc_chain_id=None):
vnffg = self.get_vnffg(context, vnffg_id)
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
sfc_id = nfp['chain_id']
classifier_ids = nfp['classifier_ids']
with context.session.begin(subtransactions=True):
query = (self._model_query(context, VnffgChain).
filter(VnffgChain.id == sfc_id).
filter(VnffgChain.status == constants.PENDING_UPDATE))
query.update({'status': new_status})
for classifier_id in classifier_ids:
query = (self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == classifier_id).
filter(VnffgClassifier.status ==
constants.PENDING_UPDATE))
query.update({'status': new_status})
query_chain = (self._model_query(context, VnffgChain).
filter(VnffgChain.id == nfp['chain_id']).
filter(VnffgChain.status ==
constants.PENDING_UPDATE))
query = (self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg['id']).
filter(Vnffg.status == constants.PENDING_UPDATE))
query.update({'status': new_status})
nfp_query = (self._model_query(context, VnffgNfp).
query_nfp = (self._model_query(context, VnffgNfp).
filter(VnffgNfp.id == nfp['id']).
filter(VnffgNfp.status == constants.PENDING_UPDATE))
nfp_query.update({'status': new_status})
filter(VnffgNfp.status ==
constants.PENDING_UPDATE))
if new_vnffg is not None:
query_vnffg = (self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg['id']).
filter(Vnffg.status ==
constants.PENDING_UPDATE))
if n_sfc_chain_id is None:
query_chain.update({'status': constants.ERROR})
else:
for key in _VALID_VNFFG_UPDATE_ATTRIBUTES:
query.update({key: new_vnffg[key]})
nfp_query.update({'symmetrical': new_vnffg['symmetrical']})
def _update_sfc_post(self, context, sfc_id, new_status, new_sfc=None):
with context.session.begin(subtransactions=True):
sfc_query = (self._model_query(context, VnffgChain).
filter(VnffgChain.id == sfc_id).
filter(VnffgChain.status == constants.PENDING_UPDATE))
sfc_query.update({'status': new_status})
if new_sfc is not None:
if updated_items.get(key) is not None:
query_vnffg.update({key: updated_items[key]})
for key in _VALID_NFP_UPDATE_ATTRIBUTES:
if updated_items.get(key) is not None:
query_nfp.update({key: updated_items[key]})
for key in _VALID_SFC_UPDATE_ATTRIBUTES:
sfc_query.update({key: new_sfc[key]})
if updated_items.get(key) is not None:
query_chain.update({key: updated_items[key]})
query_chain.update({'status': constants.ACTIVE})
def _update_classifier_post(self, context, classifier_ids, new_status,
new_fc=None):
def _update_vnffg_status(self, context, vnffg_id, error=False,
db_state=constants.ERROR):
query_cls = []
vnffg = self.get_vnffg(context, vnffg_id)
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
classifier_ids = nfp['classifier_ids']
chain_dict = self.get_sfc(context, nfp['chain_id'])
with context.session.begin(subtransactions=True):
for classifier_id in classifier_ids:
fc_query = (self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == classifier_id).
filter(VnffgClassifier.status ==
constants.PENDING_UPDATE))
fc_query.update({'status': new_status})
query_chain = (self._model_query(context, VnffgChain).
filter(VnffgChain.id == nfp['chain_id']))
if new_fc is not None:
for key in _VALID_FC_UPDATE_ATTRIBUTES:
fc_query.update({key: new_fc[key]})
for classifier_id in classifier_ids:
query_cl = (self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == classifier_id))
query_cls.append(query_cl)
query_nfp = (self._model_query(context, VnffgNfp).
filter(VnffgNfp.id == nfp['id']))
query_vnffg = (self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg['id']))
if not error and chain_dict['status'] == constants.ACTIVE:
for query_cl in query_cls:
query_cl.update({'status': constants.ACTIVE})
query_nfp.update({'status': constants.ACTIVE})
query_vnffg.update({'status': constants.ACTIVE})
else:
if db_state == constants.ACTIVE:
query_chain.update({'status': constants.ACTIVE})
for query_cl in query_cls:
query_cl.update({'status': constants.ACTIVE})
query_nfp.update({'status': constants.ACTIVE})
query_vnffg.update({'status': constants.ACTIVE})
else:
query_chain.update({'status': constants.ERROR})
for query_cl in query_cls:
query_cl.update({'status': constants.ERROR})
query_nfp.update({'status': constants.ERROR})
query_vnffg.update({'status': constants.ERROR})
def _get_vnffg_db(self, context, vnffg_id, current_statuses, new_status):
try:

View File

@ -240,6 +240,14 @@ class ClassifierNotFoundException(exceptions.NotFound):
message = _('Classifier %(classifier_id)s could not be found')
class VnfMappingNotFoundException(exceptions.NotFound):
message = _('VNF mapping not found/defined')
class VnfMappingNotValidException(exceptions.TackerException):
message = _('The %(vnfd)s is not found in constituent VNFDs')
class NSDInUse(exceptions.InUse):
message = _('NSD %(nsd_id)s is still in use')
@ -251,6 +259,11 @@ class NSInUse(exceptions.InUse):
class NoTasksException(exceptions.TackerException):
message = _('No tasks to run for %(action)s on %(resource)s')
class UpdateChainException(exceptions.TackerException):
message = _("%(message)s")
NAME_MAX_LEN = 255
RESOURCE_ATTRIBUTE_MAP = {

View File

@ -38,7 +38,6 @@ from tacker.nfvo.drivers.vnffg import abstract_vnffg_driver
from tacker.nfvo.drivers.workflow import workflow_generator
from tacker.vnfm import keystone
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -448,15 +447,109 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
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
symmetrical=None, auth_attr=None):
# (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')
# TODO(mardim) Currently we figure out which VNF belongs to what
# port-pair-group or port-pair through the name of VNF.
# This is not the best approach. The best approach for the future
# propably is to maintain in the database the ID of the
# port-pair-group and port-pair that VNF belongs to so we can
# implemement the update in a more robust way.
if not auth_attr:
LOG.warning("auth information required for n-sfc driver")
return None
neutronclient_ = NeutronClient(auth_attr)
new_ppgs = []
updated_port_chain = dict()
pc_info = neutronclient_.port_chain_show(chain_id)
old_ppgs = pc_info['port_chain']['port_pair_groups']
old_ppgs_dict = {neutronclient_.
port_pair_group_show(ppg_id)['port_pair_group']['name'].
split('-')[0]: ppg_id for ppg_id in old_ppgs}
past_ppgs_dict = old_ppgs_dict.copy()
try:
for vnf in vnfs:
port_pair_group = {}
port_pair = {}
if vnf['name'] in old_ppgs_dict:
old_ppg_id = old_ppgs_dict.pop(vnf['name'])
new_ppgs.append(old_ppg_id)
else:
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 update failed due to missing "
"connection point info in VNF "
"%(vnfname)s", {'vnfname': vnf['name']})
raise nfvo.UpdateChainException(
message="Connection point not found")
cp_list = vnf[CONNECTION_POINT]
num_cps = len(cp_list)
if num_cps != 1 and num_cps != 2:
LOG.warning("Chain update failed due to wrong number "
"of connection points: expected [1 | 2],"
"got %(cps)d", {'cps': num_cps})
raise nfvo.UpdateChainException(
message="Invalid number of connection points")
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 update failed due to port pair "
"creation failed for "
"vnf %(vnf)s", {'vnf': vnf['name']})
raise nfvo.UpdateChainException(
message="Failed to create port-pair")
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 update failed due to port pair "
"group creation failed for vnf "
"%(vnf)s", {'vnf': vnf['name']})
for pp_id in port_pair_group['port_pairs']:
neutronclient_.port_pair_delete(pp_id)
raise nfvo.UpdateChainException(
message="Failed to create port-pair-group")
new_ppgs.append(port_pair_group_id)
except nfvo.UpdateChainException as e:
self._delete_ppgs_and_pps(neutronclient_, new_ppgs, past_ppgs_dict)
raise e
updated_port_chain['port_pair_groups'] = new_ppgs
try:
pc_id = neutronclient_.port_chain_update(chain_id,
updated_port_chain)
except (nc_exceptions.BadRequest, nfvo.UpdateChainException) as e:
self._delete_ppgs_and_pps(neutronclient_, new_ppgs, past_ppgs_dict)
raise e
for ppg_name in old_ppgs_dict:
ppg_info = neutronclient_. \
port_pair_group_show(old_ppgs_dict[ppg_name])
neutronclient_.port_pair_group_delete(old_ppgs_dict[ppg_name])
port_pairs = ppg_info['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]
neutronclient_.port_pair_delete(pp_id)
return pc_id
def delete_chain(self, chain_id, auth_attr=None):
if not auth_attr:
@ -519,24 +612,38 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
return {'id': workflow[0].id, 'input': wg.get_input_dict()}
def execute_workflow(self, workflow, auth_dict=None):
return self.get_mistral_client(auth_dict)\
return self.get_mistral_client(auth_dict) \
.executions.create(
workflow_identifier=workflow['id'],
workflow_input=workflow['input'],
wf_params={})
workflow_identifier=workflow['id'],
workflow_input=workflow['input'],
wf_params={})
def get_execution(self, execution_id, auth_dict=None):
return self.get_mistral_client(auth_dict)\
return self.get_mistral_client(auth_dict) \
.executions.get(execution_id)
def delete_execution(self, execution_id, auth_dict=None):
return self.get_mistral_client(auth_dict).executions\
return self.get_mistral_client(auth_dict).executions \
.delete(execution_id)
def delete_workflow(self, workflow_id, auth_dict=None):
return self.get_mistral_client(auth_dict)\
return self.get_mistral_client(auth_dict) \
.workflows.delete(workflow_id)
def _delete_ppgs_and_pps(self, neutronclient, new_ppgs, past_ppgs_dict):
if new_ppgs:
for item in new_ppgs:
if item not in past_ppgs_dict.values():
new_ppg_info = neutronclient.port_pair_group_show(
item)
neutronclient.port_pair_group_delete(item)
new_port_pairs = new_ppg_info['port_pair_group'][
'port_pairs']
if new_port_pairs and len(new_port_pairs):
for j in xrange(0, len(new_port_pairs)):
new_pp_id = new_port_pairs[j]
neutronclient.port_pair_delete(new_pp_id)
class NeutronClient(object):
"""Neutron Client class for networking-sfc driver"""
@ -638,3 +745,38 @@ class NeutronClient(object):
except nc_exceptions.NotFound:
LOG.warning('port chain %s not found', port_chain_id)
raise ValueError('port chain %s not found' % port_chain_id)
def port_chain_update(self, port_chain_id, port_chain):
try:
pc = self.client.update_port_chain(port_chain_id,
{'port_chain': port_chain})
except nc_exceptions.BadRequest as e:
LOG.warning('update port chain returns %s', e)
raise ValueError(str(e))
if pc and len(pc):
return pc['port_chain']['id']
else:
raise nfvo.UpdateChainException(message="Failed to update "
"port-chain")
def port_chain_show(self, port_chain_id):
try:
port_chain = self.client.show_port_chain(port_chain_id)
if port_chain is None:
raise ValueError('port chain %s not found' % port_chain_id)
return port_chain
except nc_exceptions.NotFound:
LOG.error('port chain %s not found', port_chain_id)
raise ValueError('port chain %s not found' % port_chain_id)
def port_pair_group_show(self, ppg_id):
try:
port_pair_group = self.client.show_port_pair_group(ppg_id)
if port_pair_group is None:
raise ValueError('port pair group %s not found' % ppg_id)
return port_pair_group
except nc_exceptions.NotFound:
LOG.warning('port pair group %s not found', ppg_id)
raise ValueError('port pair group %s not found' % ppg_id)

View File

@ -41,7 +41,8 @@ class VnffgAbstractDriver(extensions.PluginInterface):
pass
@abc.abstractmethod
def update_chain(self, chain_id, fc_ids, vnfs, symmetrical=False,
def update_chain(self, chain_id, fc_ids, vnfs,
symmetrical=False,
auth_attr=None):
"""Update service function chain"""
pass

View File

@ -254,7 +254,7 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
known_forwarders = set()
for element in path:
if element.get('forwarder') in known_forwarders:
if prev_element is not None and element.get('forwarder')\
if prev_element is not None and element.get('forwarder') \
!= prev_element['forwarder']:
raise nfvo.VnffgdDuplicateForwarderException(
forwarder=element.get('forwarder')
@ -378,10 +378,27 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
in nfp['classifier_ids']]
template_db = self._get_resource(context, vnffg_db.VnffgTemplate,
vnffg_dict['vnffgd_id'])
vnf_members = self._get_vnffg_property(template_db.template,
vnfd_members = self._get_vnffg_property(template_db.template,
'constituent_vnfs')
new_vnffg['vnf_mapping'] = super(NfvoPlugin, self)._get_vnf_mapping(
context, new_vnffg.get('vnf_mapping'), vnf_members)
try:
super(NfvoPlugin, self)._validate_vnfd_in_vnf_mapping(
new_vnffg.get('vnf_mapping'), vnfd_members)
combined_vnf_mapping = super(
NfvoPlugin, self)._combine_current_and_new_vnf_mapping(
context, new_vnffg['vnf_mapping'],
vnffg_dict['vnf_mapping'])
new_vnffg['vnf_mapping'] = super(
NfvoPlugin, self)._get_vnf_mapping(context,
combined_vnf_mapping,
vnfd_members)
except Exception:
with excutils.save_and_reraise_exception():
super(NfvoPlugin, self)._update_vnffg_status(
context, vnffg_id, error=True, db_state=constants.ACTIVE)
template_id = vnffg_dict['vnffgd_id']
template_db = self._get_resource(context, vnffg_db.VnffgTemplate,
template_id)
@ -396,7 +413,10 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
nfp['name'])
LOG.debug('chain update: %s', chain)
sfc['chain'] = chain
sfc['symmetrical'] = new_vnffg['symmetrical']
# Symmetrical update currently is not supported
del new_vnffg['symmetrical']
vim_obj = self._get_vim_from_vnf(context,
list(vnffg_dict[
'vnf_mapping'].values())[0])
@ -416,27 +436,23 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
fc=classifier['match'],
auth_attr=vim_obj['auth_cred'])
classifier_instances.append(classifier['instance_id'])
self._vim_drivers.invoke(driver_type, 'update_chain',
vnfs=sfc['chain'],
fc_ids=classifier_instances,
chain_id=sfc['instance_id'],
auth_attr=vim_obj['auth_cred'],
symmetrical=new_vnffg['symmetrical'])
n_sfc_chain_id = self._vim_drivers.invoke(
driver_type, 'update_chain',
vnfs=sfc['chain'], fc_ids=classifier_instances,
chain_id=sfc['instance_id'], auth_attr=vim_obj['auth_cred'])
except Exception:
with excutils.save_and_reraise_exception():
vnffg_dict['status'] = constants.ERROR
super(NfvoPlugin, self)._update_vnffg_post(context, vnffg_id,
constants.ERROR)
super(NfvoPlugin, self)._update_vnffg_status(context,
vnffg_id,
error=True)
new_vnffg['chain'] = chain
super(NfvoPlugin, self)._update_vnffg_post(context, vnffg_id,
constants.ACTIVE, new_vnffg)
# update chain
super(NfvoPlugin, self)._update_sfc_post(context, sfc['id'],
constants.ACTIVE, sfc)
# update classifier - this is just updating status until functional
# updates are supported to classifier
super(NfvoPlugin, self)._update_classifier_post(context,
nfp['classifier_ids'],
constants.ACTIVE)
new_vnffg,
n_sfc_chain_id)
super(NfvoPlugin, self)._update_vnffg_status(context,
vnffg_id,
db_state=constants.ACTIVE)
return vnffg_dict
@log.log
@ -702,7 +718,7 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
if req_val in nsd_dict['topology_template']['node_templates']:
param_values[vnfd_name]['substitution_mappings'][
res_name] = nsd_dict['topology_template'][
'node_templates'][req_val]
'node_templates'][req_val]
param_values[vnfd_name]['substitution_mappings'][
'requirements'] = req_dict
@ -874,6 +890,7 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
auth_dict=self.get_auth_dict(context))
super(NfvoPlugin, self).delete_ns_post(context, ns_id, exec_obj,
error_reason)
if workflow:
self.spawn_n(_delete_ns_wait, ns['id'], mistral_execution.id)
else:

View File

@ -102,13 +102,16 @@ class FakeVNFMPlugin(mock.Mock):
super(FakeVNFMPlugin, self).__init__()
self.vnf1_vnfd_id = 'eb094833-995e-49f0-a047-dfb56aaf7c4e'
self.vnf1_vnf_id = '91e32c20-6d1f-47a4-9ba7-08f5e5effe07'
self.vnf1_update_vnf_id = '91e32c20-6d1f-47a4-9ba7-08f5e5effaf6'
self.vnf3_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
self.vnf2_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
self.vnf3_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
self.vnf3_update_vnf_id = '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'
self.cp11_id = 'd18c8bae-898a-4932-bff8-d5eac981a9c9'
self.cp11_update_id = 'a18c8bae-898a-4932-bff8-d5eac981a9b8'
self.cp12_id = 'c8906342-3e30-4b2a-9401-a251a7a9b5dd'
self.cp12_update_id = 'b8906342-3e30-4b2a-9401-a251a7a9b5cc'
self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'
self.cp32_update_id = '064c0d99-5a61-4711-9597-2a44dc5da14b'
@ -143,6 +146,8 @@ class FakeVNFMPlugin(mock.Mock):
def get_vnf(self, *args, **kwargs):
if self.vnf1_vnf_id in args:
return self.get_dummy_vnf1()
elif self.vnf1_update_vnf_id in args:
return self.get_dummy_vnf1_update()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf3()
elif self.vnf3_update_vnf_id in args:
@ -151,6 +156,8 @@ class FakeVNFMPlugin(mock.Mock):
def get_vnf_resources(self, *args, **kwargs):
if self.vnf1_vnf_id in args:
return self.get_dummy_vnf1_details()
elif self.vnf1_update_vnf_id in args:
return self.get_dummy_vnf1_update_details()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf3_details()
elif self.vnf3_update_vnf_id in args:
@ -160,6 +167,10 @@ class FakeVNFMPlugin(mock.Mock):
return [{'name': 'CP11', 'id': self.cp11_id},
{'name': 'CP12', 'id': self.cp12_id}]
def get_dummy_vnf1_update_details(self):
return [{'name': 'CP11', 'id': self.cp11_update_id},
{'name': 'CP12', 'id': self.cp12_update_id}]
def get_dummy_vnf3_details(self):
return [{'name': 'CP32', 'id': self.cp32_id}]
@ -174,6 +185,14 @@ class FakeVNFMPlugin(mock.Mock):
'name': 'dummy_vnf1',
'attributes': {}}
def get_dummy_vnf1_update(self):
return {'description': 'dummy_vnf_description',
'vnfd_id': self.vnf1_vnfd_id,
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnf1_update',
'attributes': {}}
def get_dummy_vnf3(self):
return {'description': 'dummy_vnf_description',
'vnfd_id': self.vnf3_vnfd_id,
@ -680,13 +699,63 @@ class TestNfvoPlugin(db_base.SqlTestCase):
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
updated_vnf_mapping = \
{'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
{'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effaf6',
'VNF3': '5c7f5631-9e74-46e8-b3d2-397c0eda9d0b'}
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.assertRaises(nfvo.VnffgInvalidMappingException,
self.nfvo_plugin.update_vnffg,
self.context, vnffg['id'], updated_vnffg)
def test_update_vnffg_empty_vnf_mapping_dict(self):
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock.patch('tacker.common.driver_manager.DriverManager',
side_effect=FakeDriverManager()).start()
self._insert_dummy_vnffg_template()
vnffg = self._insert_dummy_vnffg()
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
updated_vnf_mapping = {}
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.assertRaises(nfvo.VnfMappingNotFoundException,
self.nfvo_plugin.update_vnffg,
self.context, vnffg['id'], updated_vnffg)
def test_update_vnffg_vnf_mapping_key_none(self):
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock.patch('tacker.common.driver_manager.DriverManager',
side_effect=FakeDriverManager()).start()
self._insert_dummy_vnffg_template()
vnffg = self._insert_dummy_vnffg()
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
updated_vnf_mapping = None
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.assertRaises(nfvo.VnfMappingNotFoundException,
self.nfvo_plugin.update_vnffg,
self.context, vnffg['id'], updated_vnffg)
def test_update_vnffg_vnfd_not_in_vnffg_template(self):
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock.patch('tacker.common.driver_manager.DriverManager',
side_effect=FakeDriverManager()).start()
self._insert_dummy_vnffg_template()
vnffg = self._insert_dummy_vnffg()
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
updated_vnf_mapping = \
{'VNF2': '91e32c20-6d1f-47a4-9ba7-08f5e5effad7',
'VNF3': '5c7f5631-9e74-46e8-b3d2-397c0eda9d0b'}
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.assertRaises(nfvo.VnfMappingNotValidException,
self.nfvo_plugin.update_vnffg,
self.context, vnffg['id'], updated_vnffg)
def test_update_vnffg(self):
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
@ -697,18 +766,29 @@ class TestNfvoPlugin(db_base.SqlTestCase):
vnffg = self._insert_dummy_vnffg()
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
expected_mapping = {'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
'VNF3': '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'}
updated_vnf_mapping = \
{'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
{'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effaf6',
'VNF3': '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'}
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.nfvo_plugin.update_vnffg(self.context, vnffg['id'],
updated_vnffg)
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
result = self.nfvo_plugin.update_vnffg(self.context, vnffg['id'],
updated_vnffg)
self.assertIn('id', result)
self.assertIn('status', result)
self.assertIn('vnf_mapping', result)
self.assertEqual('ffc1a59b-65bb-4874-94d3-84f639e63c74',
result['id'])
self.assertEqual('PENDING_UPDATE', result['status'])
for vnfd, vnf in result['vnf_mapping'].items():
self.assertIn(vnfd, expected_mapping)
self.assertEqual(vnf, expected_mapping[vnfd])
self._driver_manager.invoke.assert_called_with(mock.ANY,
mock.ANY,
vnfs=mock.ANY,
fc_ids=mock.ANY,
chain_id=mock.ANY,
auth_attr=mock.ANY,
symmetrical=True)
auth_attr=mock.ANY)
def test_delete_vnffg(self):
self._insert_dummy_vnffg_template()