Support ChangeCurrentVNFPackage for VNF of v2 API

This patch implements change current vnf package task
defined in ETSI NFV-SOL003 v3.3.1 5.5.2.11a.

Retry and rollback operation of change current vnf package
API are also supported.

Note that currently only 'RollingUpdate' is supported for
VNF image's update.

This patch refactors the userdata_utils file and writes
the common methods into the new common/common_script_utils.py
file, so some changes are made to the existing instantiate
code.

Implements: blueprint upgrade-vnf-package
Change-Id: Ic810a3e9063e660e43e094d65111340d92917cf0
This commit is contained in:
Yi Feng 2022-01-25 16:44:18 +09:00
parent 30b6f74c64
commit 78dbba65c7
75 changed files with 8182 additions and 919 deletions

View File

@ -0,0 +1,8 @@
---
features:
- |
Add Change Current VNF Package API based on ETSI NFV specifications.
Tacker supports a VNF upgrade using this API.
Currently, it only supports "RollingUpdate" out of several methods for
a VNF upgrade.

View File

@ -129,6 +129,15 @@ rules = [
'path': VNF_INSTANCES_ID_PATH + '/change_ext_conn'}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('change_vnfpkg'),
check_str=RULE_ANY,
description="Change vnf package.",
operations=[
{'method': 'POST',
'path': VNF_INSTANCES_ID_PATH + '/change_vnfpkg'}
]
),
# NOTE: add when the operation supported
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('subscription_create'),

View File

@ -40,6 +40,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter):
("/vnf_instances/{id}/terminate", {"POST": "terminate"}),
("/vnf_instances/{id}/scale", {"POST": "scale"}),
("/vnf_instances/{id}/change_ext_conn", {"POST": "change_ext_conn"}),
("/vnf_instances/{id}/change_vnfpkg", {"POST": "change_vnfpkg"}),
("/api_versions", {"GET": "api_versions"}),
("/subscriptions", {"GET": "subscription_list",
"POST": "subscription_create"}),

View File

@ -77,6 +77,33 @@ TerminateVnfRequest_V200 = {
'additionalProperties': True,
}
# SOL002 5.5.2.11a
# SOL003 5.5.2.11a
ChangeCurrentVnfPkgRequest_V200 = {
'type': 'object',
'properties': {
'vnfdId': common_types.Identifier,
'extVirtualLinks': {
'type': 'array',
'items': common_types.ExtVirtualLinkData},
'extManagedVirtualLinks': {
'type': 'array',
'items': common_types.ExtManagedVirtualLinkData},
# NOTE: 'vimConnectionInfo' field supports only NFV-SOL 003
'vimConnectionInfo': {
'type': 'object',
'patternProperties': {
'^.*$': common_types.VimConnectionInfo
},
},
'additionalParams': parameter_types.keyvalue_pairs,
'extensions': parameter_types.keyvalue_pairs,
'vnfConfigurableProperties': parameter_types.keyvalue_pairs
},
'required': ['vnfdId'],
'additionalProperties': True,
}
# SOL003 5.5.2.5
ScaleVnfRequest_V200 = {
'type': 'object',
@ -263,6 +290,7 @@ _LifecycleChangeNotificationsFilter = {
'SCALE',
'SCALE_TO_LEVEL',
'CHANGE_FLAVOUR',
'CHANGE_VNFPKG',
'TERMINATE',
'HEAL',
'OPERATE',

View File

@ -0,0 +1,45 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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.
from openstack import connection
from openstack import exceptions as os_ex
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class CinderClient(object):
def __init__(self, vim_info):
auth = dict(
auth_url=vim_info.interfaceInfo['endpoint'],
username=vim_info.accessInfo['username'],
password=vim_info.accessInfo['password'],
project_name=vim_info.accessInfo['project'],
user_domain_name=vim_info.accessInfo['userDomain'],
project_domain_name=vim_info.accessInfo['projectDomain']
)
self.conn = connection.Connection(
region_name=vim_info.accessInfo.get('region'),
auth=auth,
identity_interface='internal')
def get_volume(self, volume_id):
try:
return self.conn.volume.get_volume(volume_id)
except os_ex.ResourceNotFound:
LOG.debug("volume %s not found.", volume_id)

View File

@ -0,0 +1,279 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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.
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnfd_utils
def get_vnfd(vnfd_id, csar_dir):
vnfd = vnfd_utils.Vnfd(vnfd_id)
vnfd.init_from_csar_dir(csar_dir)
return vnfd
def get_vdu_info(grant, inst, vnfd):
volume_name = ''
volume_size = ''
flavour_id = inst['instantiatedVnfInfo']['flavourId']
vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
storage_nodes = vnfd.get_storage_nodes(flavour_id)
vdu_info_dict = {}
for name, node in vdu_nodes.items():
flavor = get_param_flavor(name, flavour_id, vnfd, grant)
image = get_param_image(name, flavour_id, vnfd, grant)
vdu_storage_names = vnfd.get_vdu_storages(node)
for vdu_storage_name in vdu_storage_names:
if storage_nodes[vdu_storage_name].get(
'properties', {}).get('sw_image_data'):
image = get_param_image(vdu_storage_name, flavour_id, vnfd,
grant)
volume_name = vdu_storage_name
volume_size = storage_nodes[vdu_storage_name].get(
'properties', {}).get(
'virtual_block_storage_data', '').get(
'size_of_storage', ''
)
volume_size = volume_size.rstrip(' GB')
if not volume_size.isdigit():
raise sol_ex.VmRunningFailed(
error_info='The volume size set in VNFD is invalid.')
break
vdu_info_dict[name] = {
"flavor": flavor,
"image": image
}
if volume_name:
vdu_info_dict[name]['volume_info'] = {
"volume_name": volume_name,
"volume_size": volume_size
}
return vdu_info_dict
def init_nfv_dict(hot_template):
get_params = []
def _get_get_param(prop):
if isinstance(prop, dict):
for key, value in prop.items():
if key == 'get_param':
get_params.append(value)
else:
_get_get_param(value)
elif isinstance(prop, list):
for value in prop:
_get_get_param(value)
for res in hot_template.get('resources', {}).values():
_get_get_param(res.get('properties', {}))
nfv = {}
for param in get_params:
if (not isinstance(param, list) or len(param) < 4 or
param[0] != 'nfv'):
continue
parent = nfv
for item in param[1:-1]:
parent.setdefault(item, {})
parent = parent[item]
parent[param[-1]] = None
# TODO(YiFeng): enhance to handle list
# NOTE: List is not considered here and only 'fixed_ips' is treated as
# list in userdata_default.py at the moment.
# Note that if handling list is enhanced, userdata_default.py is
# necessary to modify.
return nfv
def get_param_flavor(vdu_name, flavour_id, vnfd, grant):
# try to get from grant
if 'vimAssets' in grant:
assets = grant['vimAssets']
if 'computeResourceFlavours' in assets:
flavours = assets['computeResourceFlavours']
for flavour in flavours:
if flavour['vnfdVirtualComputeDescId'] == vdu_name:
return flavour['vimFlavourId']
# if specified in VNFD, use it
# NOTE: if not found. parameter is set to None.
# may be error when stack create
return vnfd.get_compute_flavor(flavour_id, vdu_name)
def get_param_image(vdu_name, flavour_id, vnfd, grant):
# try to get from grant
if 'vimAssets' in grant:
assets = grant['vimAssets']
if 'softwareImages' in assets:
images = assets['softwareImages']
for image in images:
if image['vnfdSoftwareImageId'] == vdu_name:
return image['vimSoftwareImageId']
# if specified in VNFD, use it
# NOTE: if not found. parameter is set to None.
# may be error when stack create
sw_images = vnfd.get_sw_image(flavour_id)
for name, image in sw_images.items():
if name == vdu_name:
return image
def get_param_zone(vdu_name, grant_req, grant):
if 'zones' not in grant or 'addResources' not in grant:
return
for res in grant['addResources']:
if 'zoneId' not in res:
continue
for req_res in grant_req['addResources']:
if req_res['id'] == res['resourceDefinitionId']:
if req_res.get('resourceTemplateId') == vdu_name:
for zone in grant['zones']:
if zone['id'] == res['zoneId']: # must be found
return zone['zoneId']
def get_current_capacity(vdu_name, inst):
count = 0
inst_vnfcs = (inst.get('instantiatedVnfInfo', {})
.get('vnfcResourceInfo', []))
for inst_vnfc in inst_vnfcs:
if inst_vnfc['vduId'] == vdu_name:
count += 1
return count
def get_param_capacity(vdu_name, inst, grant_req):
# NOTE: refer grant_req here since interpretation of VNFD was done when
# making grant_req.
count = get_current_capacity(vdu_name, inst)
add_reses = grant_req.get('addResources', [])
for res_def in add_reses:
if (res_def['type'] == 'COMPUTE' and
res_def['resourceTemplateId'] == vdu_name):
count += 1
rm_reses = grant_req.get('removeResources', [])
for res_def in rm_reses:
if (res_def['type'] == 'COMPUTE' and
res_def['resourceTemplateId'] == vdu_name):
count -= 1
return count
def _get_fixed_ips_from_extcp(extcp):
fixed_ips = []
for cp_conf in extcp['cpConfig'].values():
if 'cpProtocolData' not in cp_conf:
continue
for prot_data in cp_conf['cpProtocolData']:
if 'ipOverEthernet' not in prot_data:
continue
if 'ipAddresses' not in prot_data['ipOverEthernet']:
continue
for ip in prot_data['ipOverEthernet']['ipAddresses']:
data = {}
if 'fixedAddresses' in ip:
# pick up only one ip address
data['ip_address'] = str(ip['fixedAddresses'][0])
if 'subnetId' in ip:
data['subnet'] = ip['subnetId']
if data:
fixed_ips.append(data)
return fixed_ips
def get_param_network(cp_name, grant, req):
# see grant first then instantiateVnfRequest
vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', [])
for vl in vls:
for extcp in vl['extCps']:
if extcp['cpdId'] == cp_name:
return vl['resourceId']
def get_param_fixed_ips(cp_name, grant, req):
# see grant first then instantiateVnfRequest
vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', [])
for vl in vls:
for extcp in vl['extCps']:
if extcp['cpdId'] == cp_name:
return _get_fixed_ips_from_extcp(extcp)
def get_param_network_from_inst(cp_name, inst):
for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []):
for extcp in vl.get('currentVnfExtCpData', []):
if extcp['cpdId'] == cp_name:
return vl['resourceHandle']['resourceId']
def get_param_fixed_ips_from_inst(cp_name, inst):
for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []):
for extcp in vl.get('currentVnfExtCpData', []):
if extcp['cpdId'] == cp_name:
return _get_fixed_ips_from_extcp(extcp)
def apply_ext_managed_vls(hot_dict, req, grant):
# see grant first then instantiateVnfRequest
mgd_vls = (grant.get('extManagedVirtualLinks', []) +
req.get('extManagedVirtualLinks', []))
# NOTE: refer HOT only here, not refer VNFD.
# HOT and VNFD must be consistent.
for mgd_vl in mgd_vls:
vl_name = mgd_vl['vnfVirtualLinkDescId']
network_id = mgd_vl['resourceId']
get_res = {'get_resource': vl_name}
def _change(item):
if not isinstance(item, dict):
return
for key, value in item.items():
if value == get_res:
item[key] = network_id
else:
_change(value)
del_reses = []
for res_name, res_data in hot_dict.get('resources', {}).items():
# delete network definition
if res_name == vl_name:
del_reses.append(res_name)
# delete subnet definition
if res_data['type'] == 'OS::Neutron::Subnet':
net = (res_data.get('properties', {})
.get('network', {})
.get('get_resource'))
if net == vl_name:
del_reses.append(res_name)
# change '{get_resource: vl_name}' to network_id
_change(res_data)
for res_name in del_reses:
hot_dict['resources'].pop(res_name)

View File

@ -114,6 +114,10 @@ class VnfInstanceNotFound(SolHttpError404):
message = _("VnfInstance %(inst_id)s not found.")
class NotSupportUpgradeType(SolHttpError400):
message = _("not support upgrade_type %(upgrade_type)s")
class VnfInstanceIsInstantiated(SolHttpError409):
message = _("VnfInstance %(inst_id)s is instantiated.")
@ -122,6 +126,10 @@ class VnfInstanceIsNotInstantiated(SolHttpError409):
message = _("VnfInstance %(inst_id)s isn't instantiated.")
class VnfInstanceIsNotChanged(SolHttpError409):
message = _("VnfInstance %(inst_id)s isn't changed.")
class LccnSubscriptionNotFound(SolHttpError404):
message = _("LccnSubscription %(subsc_id)s not found.")
@ -245,3 +253,24 @@ class DeltaMissingInVnfd(SolHttpError400):
class ConductorProcessingError(SolException):
title = 'Internal Server Error'
message = _("Failure due to conductor processing error.")
class InvalidVolumeSize(SolHttpError400):
message = _("The volume size set in VNFD is invalid.")
class VduIdNotFound(SolHttpError404):
message = _("This vdu_id '%(vdu_id)s' does not exist"
" in current VnfInstance.")
class SshIpNotFoundException(SolHttpError404):
message = _("Ssh ip not found.")
class CoordinateVNFExecutionFailed(SolHttpError422):
message = _('CoordinateVNF execution failed.')
class VmRunningFailed(SolHttpError422):
message = _("VM is running incorrectly. Reason: '%(error_info)s'")

View File

@ -126,6 +126,8 @@ def _make_affected_vnfc(vnfc, change_type, strgs):
changeType=change_type,
computeResource=vnfc.computeResource
)
if vnfc.obj_attr_is_set('metadata'):
affected_vnfc.metadata = vnfc.metadata
if vnfc.obj_attr_is_set('vnfcCpInfo'):
cp_ids = [cp.id for cp in vnfc.vnfcCpInfo]
affected_vnfc.affectedVnfcCpIds = cp_ids
@ -381,7 +383,15 @@ def update_lcmocc(lcmocc, inst_saved, inst):
for strg in inst_info.virtualStorageResourceInfo
if strg.id in added_strgs]
removed_vnfcs, added_vnfcs, _ = _calc_diff('vnfcResourceInfo')
removed_vnfcs, added_vnfcs, common_objs = _calc_diff('vnfcResourceInfo')
updated_vnfcs = []
if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG:
updated_vnfcs = [
obj.id for obj in inst_info.vnfcResourceInfo
if obj.metadata.get('current_vnfd_id') != inst_saved.vnfdId
and obj.id in common_objs and
obj.metadata.get('current_vnfd_id') is not None]
affected_vnfcs = []
if removed_vnfcs:
affected_vnfcs += [
@ -396,6 +406,11 @@ def update_lcmocc(lcmocc, inst_saved, inst):
if vnfc.id in added_vnfcs
]
if updated_vnfcs:
affected_vnfcs += [_make_affected_vnfc(vnfc, 'MODIFIED', added_strgs)
for vnfc in inst_info.vnfcResourceInfo
if vnfc.id in updated_vnfcs]
removed_vls, added_vls, common_vls = _calc_diff(
'vnfVirtualLinkResourceInfo')
affected_vls = []
@ -491,6 +506,17 @@ def update_lcmocc(lcmocc, inst_saved, inst):
lcmocc.changedExtConnectivity = chg_ext_conn
def get_inst_lcmocc(context, inst):
lcmoccs = objects.VnfLcmOpOccV2.get_by_filter(
context, vnfInstanceId=inst.id,
operationState=fields.LcmOperationStateType.COMPLETED,
operation=fields.LcmOperationType.INSTANTIATE)
inst_lcmocc = [inst_lcmocc for inst_lcmocc in lcmoccs
if inst_lcmocc.startTime ==
max([lcmocc.startTime for lcmocc in lcmoccs])][0]
return inst_lcmocc
def get_grant_req_and_grant(context, lcmocc):
if lcmocc.operation == fields.LcmOperationType.MODIFY_INFO:
return None, None

View File

@ -49,6 +49,7 @@ def make_inst_links(inst, endpoint):
links.scale = objects.Link(href=self_href + "/scale")
links.heal = objects.Link(href=self_href + "/heal")
links.changeExtConn = objects.Link(href=self_href + "/change_ext_conn")
links.changeVnfPkg = objects.Link(href=self_href + "/change_vnfpkg")
# NOTE: add when the operation supported
return links

View File

@ -18,10 +18,10 @@ import io
import os
import shutil
import tempfile
import yaml
import zipfile
from oslo_log import log as logging
import yaml
from tacker.sol_refactored.common import exceptions as sol_ex

View File

@ -14,6 +14,7 @@
# under the License.
from oslo_log import log as logging
from oslo_utils import uuidutils
from tacker.common import log
from tacker import context as tacker_context
@ -103,8 +104,12 @@ class ConductorV2(object):
self.endpoint)
try:
vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId,
all_contents=True)
if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG:
vnfd = self.nfvo_client.get_vnfd(
context, lcmocc.operationParams.vnfdId, all_contents=True)
else:
vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId,
all_contents=True)
# NOTE: perform grant exchange mainly but also perform
# something to do at STATING phase ex. request check.
@ -237,20 +242,38 @@ class ConductorV2(object):
try:
vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId)
grant_req, grant = lcmocc_utils.get_grant_req_and_grant(context,
lcmocc)
grant_req, grant = lcmocc_utils.get_grant_req_and_grant(
context, lcmocc)
self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req,
grant, vnfd)
self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req,
grant, vnfd)
if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG:
inst_lcmocc = lcmocc_utils.get_inst_lcmocc(context, inst)
inst_grant_req = objects.GrantRequestV1(
vnfInstanceId=inst.id,
vnfLcmOpOccId=inst_lcmocc.id,
operation=inst_lcmocc.operation,
isAutomaticInvocation=lcmocc.isAutomaticInvocation
)
inst_grant = objects.GrantV1(
id=uuidutils.generate_uuid(),
vnfInstanceId=inst_grant_req.vnfInstanceId,
vnfLcmOpOccId=inst_grant_req.vnfLcmOpOccId
)
self.vnflcm_driver.rollback(
context, lcmocc, inst, inst_grant_req, inst_grant, vnfd)
else:
self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req,
grant, vnfd)
lcmocc.operationState = fields.LcmOperationStateType.ROLLED_BACK
with context.session.begin(subtransactions=True):
lcmocc.update(context)
# NOTE: Basically inst is not changed. But there is a case
# that VIM resources may be changed while rollback. Only
# change_ext_conn_rollback at the moment.
if lcmocc.operation == fields.LcmOperationType.CHANGE_EXT_CONN:
# change_ext_conn_rollback and change_vnfpkg at the moment.
if lcmocc.operation in [
fields.LcmOperationType.CHANGE_EXT_CONN,
fields.LcmOperationType.CHANGE_VNFPKG]:
inst.update(context)
# grant_req and grant are not necessary any more.
if grant_req is not None:

View File

@ -64,10 +64,13 @@ class VnfLcmDriverV2(object):
grant_req = objects.GrantRequestV1(
vnfInstanceId=inst.id,
vnfLcmOpOccId=lcmocc.id,
vnfdId=inst.vnfdId,
operation=lcmocc.operation,
isAutomaticInvocation=lcmocc.isAutomaticInvocation
)
if lcmocc.operation == v2fields.LcmOperationType.CHANGE_VNFPKG:
grant_req.vnfdId = lcmocc.operationParams.get('vnfdId')
else:
grant_req.vnfdId = inst.vnfdId
grant_req._links = objects.GrantRequestV1_Links(
vnfLcmOpOcc=objects.Link(
href=lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint)),
@ -953,6 +956,78 @@ class VnfLcmDriverV2(object):
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')
def change_vnfpkg_grant(self, grant_req, req, inst, vnfd):
inst_info = inst.instantiatedVnfInfo
grant_req.flavourId = inst_info.flavourId
target_vdu_ids = [
vdu_param.get('vdu_id')
for vdu_param in req.additionalParams.get('vdu_params', [])
]
if req.additionalParams.get('upgrade_type') == 'RollingUpdate':
update_reses = []
add_reses = []
remove_reses = []
if inst_info.obj_attr_is_set('vnfcResourceInfo'):
for inst_vnc in inst_info.vnfcResourceInfo:
if inst_vnc.vduId in target_vdu_ids:
vdu_res_id = uuidutils.generate_uuid()
res_def = objects.ResourceDefinitionV1(
id=vdu_res_id,
type='COMPUTE',
resourceTemplateId=inst_vnc.vduId)
update_reses.append(res_def)
nodes = vnfd.get_vdu_nodes(inst_info.flavourId)
vdu_storage_names = vnfd.get_vdu_storages(
nodes[inst_vnc.vduId])
for vdu_storage_name in vdu_storage_names:
res_def = objects.ResourceDefinitionV1(
id=_make_combination_id(
vdu_storage_name, vdu_res_id),
type='STORAGE',
resourceTemplateId=vdu_storage_name)
add_reses.append(res_def)
if inst_vnc.obj_attr_is_set('storageResourceIds'):
inst_stor_info = (
inst_info.virtualStorageResourceInfo)
for str_info in inst_stor_info:
if str_info.id in inst_vnc.storageResourceIds:
res_def = objects.ResourceDefinitionV1(
id=uuidutils.generate_uuid(),
type='STORAGE',
resourceTemplateId=(
str_info.virtualStorageDescId),
resource=str_info.storageResource)
remove_reses.append(res_def)
if update_reses:
grant_req.updateResources = update_reses
if add_reses:
grant_req.addResources = add_reses
if remove_reses:
grant_req.removeResources = remove_reses
else:
# TODO(YiFeng): Blue-Green type will be supported in Zed release.
# not reach here at the moment
pass
def change_vnfpkg_process(
self, context, lcmocc, inst, grant_req, grant, vnfd):
inst_saved = inst.obj_clone()
req = lcmocc.operationParams
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack()
try:
driver.change_vnfpkg(req, inst, grant_req, grant, vnfd)
except Exception as ex:
lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst)
raise Exception from ex
else:
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')
def change_ext_conn_rollback(self, context, lcmocc, inst, grant_req,
grant, vnfd):
req = lcmocc.operationParams
@ -963,3 +1038,15 @@ class VnfLcmDriverV2(object):
else:
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')
def change_vnfpkg_rollback(
self, context, lcmocc, inst, grant_req, grant, vnfd):
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
req = lcmocc.operationParams
driver = openstack.Openstack()
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver.change_vnfpkg_rollback(
req, inst, grant_req, grant, vnfd, lcmocc)
else:
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')

View File

@ -379,6 +379,61 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
return sol_wsgi.SolResponse(202, None, location=location)
@validator.schema(schema.ChangeCurrentVnfPkgRequest_V200, '2.0.0')
@coordinate.lock_vnf_instance('{id}')
def change_vnfpkg(self, request, id, body):
context = request.context
inst = inst_utils.get_inst(context, id)
vnfd_id = body['vnfdId']
if inst.instantiationState != 'INSTANTIATED':
raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id)
lcmocc_utils.check_lcmocc_in_progress(context, id)
pkg_info = self.nfvo_client.get_vnf_package_info_vnfd(
context, vnfd_id)
if pkg_info.operationalState != "ENABLED":
raise sol_ex.VnfdIdNotEnabled(vnfd_id=vnfd_id)
additional_params = body.get('additionalParams')
if additional_params is None:
raise sol_ex.SolValidationError(
detail="Change Current VNF Package "
"operation must have 'additionalParams'")
upgrade_type = additional_params.get('upgrade_type', '')
if upgrade_type != 'RollingUpdate':
raise sol_ex.NotSupportUpgradeType(upgrade_type=upgrade_type)
if additional_params.get('vdu_params'):
vdu_ids = [vdu.get('vdu_id') for vdu in
additional_params.get('vdu_params')]
if None in vdu_ids:
raise sol_ex.SolValidationError(
detail="If you set vdu_params in additionalParams, you"
"must set vduId for each element.")
for vdu in additional_params.get('vdu_params'):
for attr in ['old_vnfc_param', 'new_vnfc_param']:
if vdu.get(attr):
vdu_keys = vdu.get(attr).keys()
if set(vdu_keys).difference(set(
['cp_name', 'username', 'password'])):
raise sol_ex.SolValidationError(
detail=f"If you set {attr} in "
f"additionalParams, you must set"
f" 'cp_name', 'username' and"
f" 'password'")
lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.CHANGE_VNFPKG,
body)
lcmocc.create(context)
self.conductor_rpc.start_lcm_op(context, lcmocc.id)
location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint)
return sol_wsgi.SolResponse(202, None, location=location)
@validator.schema(schema.LccnSubscriptionRequest_V200, '2.0.0')
def subscription_create(self, request, body):
context = request.context

View File

@ -49,7 +49,7 @@ class HeatClient(object):
self.wait_stack_create(fields["stack_name"])
def update_stack(self, stack_name, fields, wait=True):
path = "stacks/{}".format(stack_name)
path = f"stacks/{stack_name}"
resp, body = self.client.do_request(path, "PATCH",
expected_status=[202], body=fields)
@ -57,7 +57,7 @@ class HeatClient(object):
self.wait_stack_update(stack_name)
def delete_stack(self, stack_name, wait=True):
path = "stacks/{}".format(stack_name)
path = f"stacks/{stack_name}"
resp, body = self.client.do_request(path, "DELETE",
expected_status=[204, 404])
@ -65,7 +65,7 @@ class HeatClient(object):
self.wait_stack_delete(stack_name)
def get_status(self, stack_name):
path = "stacks/{}".format(stack_name)
path = f"stacks/{stack_name}"
resp, body = self.client.do_request(path, "GET",
expected_status=[200, 404])
@ -78,7 +78,7 @@ class HeatClient(object):
def get_resources(self, stack_name):
# NOTE: Because it is necessary to get nested stack info, it is
# necessary to specify 'nested_depth=2'.
path = "stacks/{}/resources?nested_depth=2".format(stack_name)
path = f"stacks/{stack_name}/resources?nested_depth=2"
resp, body = self.client.do_request(path, "GET",
expected_status=[200])
@ -134,23 +134,29 @@ class HeatClient(object):
raise sol_ex.StackOperationFailed
return body
def get_resource_info(self, stack_name, stack_id, resource_name):
path = f"stacks/{stack_name}/{stack_id}/resources/{resource_name}"
def get_resource_info(self, nested_stack_id, resource_name):
path = f"stacks/{nested_stack_id}/resources/{resource_name}"
resp, body = self.client.do_request(path, "GET",
expected_status=[200, 404])
if resp.status_code == 404:
return resp, None
return resp, body['resource']
return None
return body['resource']
def get_resource_list(self, stack_id):
path = f"stacks/{stack_id}/resources"
resp, body = self.client.do_request(path, "GET",
expected_status=[200, 404])
return body
def get_parameters(self, stack_name):
path = "stacks/{}".format(stack_name)
path = f"stacks/{stack_name}"
resp, body = self.client.do_request(path, "GET",
expected_status=[200])
return body["stack"]["parameters"]
def mark_unhealthy(self, stack_id, resource_name):
path = "stacks/{}/resources/{}".format(stack_id, resource_name)
path = f"stacks/{stack_id}/resources/{resource_name}"
fields = {
"mark_unhealthy": True,
"resource_status_reason": "marked by tacker"
@ -159,14 +165,14 @@ class HeatClient(object):
expected_status=[200], body=fields)
def get_template(self, stack_name):
path = "stacks/{}/template".format(stack_name)
path = f"stacks/{stack_name}/template"
resp, body = self.client.do_request(path, "GET",
expected_status=[200])
return body
def get_files(self, stack_name):
path = "stacks/{}/files".format(stack_name)
path = f"stacks/{stack_name}/files"
resp, body = self.client.do_request(path, "GET",
expected_status=[200])
@ -205,9 +211,34 @@ def get_resource_stack_id(heat_res):
return "{}/{}".format(items[-2], items[-1])
def get_parent_nested_id(res):
for link in res.get('links', []):
if link['rel'] == 'nested':
items = link['href'].split('/')
return "{}/{}".format(items[-2], items[-1])
def get_parent_resource(heat_res, heat_reses):
parent = heat_res.get('parent_resource')
if parent:
for res in heat_reses:
if res['resource_name'] == parent:
return res
def get_group_stack_id(heat_reses, vdu_id):
parent_resources = [heat_res for heat_res in heat_reses
if heat_res.get('resource_name') == vdu_id]
if parent_resources:
parent_resource = parent_resources[0].get('parent_resource')
else:
raise sol_ex.VduIdNotFound(vdu_id=vdu_id)
group_resource_name = [heat_res for heat_res in
heat_reses if
heat_res.get('resource_name') ==
parent_resource][0].get('parent_resource')
group_stack_id = [heat_res for heat_res in
heat_reses if
heat_res.get('resource_name') ==
group_resource_name][0].get('physical_resource_id')
return group_stack_id

View File

@ -14,21 +14,25 @@
# under the License.
from dateutil import parser
import eventlet
import copy
import json
import os
import pickle
import subprocess
from dateutil import parser
import eventlet
from oslo_log import log as logging
from oslo_utils import uuidutils
import yaml
from tacker.sol_refactored.common import cinder_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import heat_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_default
from tacker.sol_refactored.nfvo import glance_utils
from tacker.sol_refactored import objects
from tacker.sol_refactored.objects.v2 import fields as v2fields
@ -292,6 +296,261 @@ class Openstack(object):
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_reses)
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
group_vdu_ids = []
if req.additionalParams.get('upgrade_type') == 'RollingUpdate':
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
stack_id = heat_client.get_stack_resource(
stack_name)['stack']["id"]
vdu_infos = self._get_vdu_info(vnfd, grant, inst)
new_res_ids = {}
for vdu_param in req.additionalParams.get('vdu_params'):
vdu_id = vdu_param.get('vdu_id')
new_res_ids[vdu_id] = []
body = heat_client.get_resource_info(
f"{stack_name}/{stack_id}", vdu_id)
if uuidutils.is_uuid_like(
vdu_infos[vdu_id]['image']):
vdu_image_id_flag = True
else:
vdu_image_id_flag = False
# The previous processing is to obtain the VM information under
# the current stack according to vduId, excluding the resources
# under nested. When the status_code is 404, it means that
# there is no VM under the stack, and the VM is created by
# `OS::Heat::AutoScalingGroup` by default.
if body is None:
# handle VM under `OS::Heat::AutoScalingGroup`
# In this case, the VM does not support changing from
# image creation to volume creation, or changing from
# volume creation to image creation. Because it cannot
# change VDU.yaml under nested directory.
heat_reses = heat_client.get_resources(stack_name)
group_stack_id = heat_utils.get_group_stack_id(
heat_reses, vdu_id)
templates = heat_client.get_template(
group_stack_id)
reses = heat_client.get_resource_list(
group_stack_id)['resources']
# update VM one by one
for res in reses:
templates['resources'][res.get('resource_name')][
'properties']['image'] = vdu_infos[
vdu_id].get('image')
templates['resources'][res.get('resource_name')][
'properties']['flavor'] = vdu_infos[
vdu_id].get('flavor')
fields = {
"stack_id": group_stack_id,
"template": templates
}
try:
heat_client.update_stack(
group_stack_id, fields)
except sol_ex.StackOperationFailed:
self._handle_exception(
res, new_res_ids, vdu_infos,
vdu_id, heat_client, req, inst, vnfd,
stack_name, get_res_flag=True)
self._get_new_res_info(
res.get('resource_name'),
new_res_ids[vdu_id],
heat_utils.get_parent_nested_id(res),
vdu_infos[vdu_id], vdu_id,
heat_client)
# execute coordinate_vnf_script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd,
heat_utils.get_parent_nested_id(res),
heat_client, vdu_param, vim_info,
vdu_image_id_flag)
except (sol_ex.SshIpNotFoundException,
sol_ex.CoordinateVNFExecutionFailed) as ex:
self._handle_exception(
res, new_res_ids, vdu_infos,
vdu_id, heat_client, req, inst, vnfd,
stack_name, ex=ex)
group_vdu_ids.append(vdu_id)
else:
# handle single VM
res = {
"resource_name": stack_name,
"physical_resource_id": stack_id,
}
new_template = vnfd.get_base_hot(
inst.instantiatedVnfInfo.flavourId)['template']
heat_parameter = self._update_vnf_template_and_parameter(
stack_id, vdu_infos, vdu_id, heat_client, new_template)
fields = {
"stack_id": stack_id,
"parameters": {"nfv": heat_parameter.get('nfv')},
"template": yaml.safe_dump(
heat_parameter.get('templates')),
}
try:
heat_client.update_stack(stack_id, fields, wait=True)
except sol_ex.StackOperationFailed:
self._handle_exception(
res, new_res_ids, vdu_infos,
vdu_id, heat_client, req, inst, vnfd,
stack_name, get_res_flag=True)
self._get_new_res_info(
stack_name,
new_res_ids[vdu_id],
f'{stack_name}/{stack_id}',
vdu_infos[vdu_id], vdu_id,
heat_client)
# execute coordinate_vnf_script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd,
f"{stack_name}/{stack_id}",
heat_client, vdu_param, vim_info,
vdu_image_id_flag,
operation='change_vnfpkg')
except (sol_ex.SshIpNotFoundException,
sol_ex.CoordinateVNFExecutionFailed) as ex:
self._handle_exception(
res, new_res_ids, vdu_infos,
vdu_id, heat_client, req, inst, vnfd,
stack_name, ex=ex)
# Because external parameters are not updated after the image
# of the nested-VM is updated, scale will create the original
# VM again, so the overall update needs to be performed
# at the end.
fields = self._get_entire_stack_fields(
heat_client, stack_id, group_vdu_ids, vdu_infos)
try:
heat_client.update_stack(stack_id, fields)
except sol_ex.StackOperationFailed:
self._handle_exception(
res, new_res_ids, vdu_infos,
vdu_id, heat_client, req, inst, vnfd,
stack_name)
self._update_vnf_instantiated_info(
req, new_res_ids, inst, vnfd,
heat_client, stack_name)
inst.vnfdId = req.vnfdId
else:
# TODO(YiFeng): Blue-Green type will be supported in Zed release.
raise sol_ex.NotSupportUpgradeType(
upgrade_type=req.additionalParams.get('upgrade_type'))
def change_vnfpkg_rollback(
self, req, inst, grant_req, grant, vnfd, lcmocc):
group_vdu_ids = []
if req.additionalParams.get('upgrade_type') == 'RollingUpdate':
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
stack_id = heat_client.get_stack_resource(
stack_name)['stack']["id"]
vdu_infos = self._get_vdu_info(vnfd, grant, inst)
new_res_ids = {}
templates = {}
affected_vnfcs = [affected_vnfc for affected_vnfc in
lcmocc.resourceChanges.affectedVnfcs if
affected_vnfc.changeType in {'ADDED', 'MODIFIED'}]
for lcmocc_vnf in affected_vnfcs:
if new_res_ids.get(lcmocc_vnf.vduId) is None:
new_res_ids[lcmocc_vnf.vduId] = []
image = vdu_infos[lcmocc_vnf.vduId]['image']
if uuidutils.is_uuid_like(image):
vdu_image_id_flag = True
else:
vdu_image_id_flag = False
if lcmocc_vnf.metadata.get(
'current_vnfd_id') is not inst.vnfdId:
parent_resource_name = lcmocc_vnf.metadata.get(
'parent_resource_name')
if parent_resource_name:
vdu_stack_id = lcmocc_vnf.metadata.get(
'stack_id')
if not templates.get(lcmocc_vnf.vduId):
templates[lcmocc_vnf.vduId] = {}
heat_reses = heat_client.get_resources(stack_name)
group_stack_id = heat_utils.get_group_stack_id(
heat_reses, lcmocc_vnf.vduId)
template = heat_client.get_template(
group_stack_id)
templates[lcmocc_vnf.vduId] = template
template['resources'][parent_resource_name][
'properties']['image'] = vdu_infos[
lcmocc_vnf.vduId].get('image')
template['resources'][parent_resource_name][
'properties']['flavor'] = vdu_infos[
lcmocc_vnf.vduId].get('flavor')
fields = {
"stack_id": group_stack_id,
"template": template
}
heat_client.update_stack(
group_stack_id, fields, wait=True)
self._get_new_res_info(
parent_resource_name,
new_res_ids[lcmocc_vnf.vduId], vdu_stack_id,
vdu_infos[lcmocc_vnf.vduId],
lcmocc_vnf.vduId, heat_client)
vdu_param = [vdu_param for vdu_param in
req.get('additionalParams').get(
'vdu_params')
if vdu_param.get('vdu_id')
== lcmocc_vnf.vduId][0]
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd,
vdu_stack_id,
heat_client, vdu_param, vim_info,
vdu_image_id_flag,
operation="change_vnfpkg_rollback")
group_vdu_ids.append(lcmocc_vnf.vduId)
else:
vdu_stack_id = lcmocc_vnf.metadata.get('stack_id')
new_template = vnfd.get_base_hot(
inst.instantiatedVnfInfo.flavourId)['template']
heat_parameter = (
self._update_vnf_template_and_parameter(
stack_id, vdu_infos,
lcmocc_vnf.vduId, heat_client, new_template))
fields = {
"stack_id": stack_id,
"parameters": {"nfv": heat_parameter.get('nfv')},
"template": yaml.safe_dump(
heat_parameter.get('templates')),
}
heat_client.update_stack(stack_id,
fields, wait=True)
self._get_new_res_info(
stack_name, new_res_ids[lcmocc_vnf.vduId],
vdu_stack_id, vdu_infos[lcmocc_vnf.vduId],
lcmocc_vnf.vduId, heat_client)
vdu_param = [vdu_param for vdu_param in
req.get('additionalParams').get(
'vdu_params')
if vdu_param.get('vdu_id')
== lcmocc_vnf.vduId][0]
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd, vdu_stack_id,
heat_client, vdu_param, vim_info,
vdu_image_id_flag,
operation="change_vnfpkg_rollback")
fields = self._get_entire_stack_fields(
heat_client, stack_id, group_vdu_ids, vdu_infos)
heat_client.update_stack(stack_id, fields, wait=True)
self._update_vnf_instantiated_info(
req, new_res_ids, inst, vnfd,
heat_client, stack_name, operation='change_vnfpkg_rollback')
else:
# TODO(YiFeng): Blue-Green type will be supported in Zed release.
raise sol_ex.NotSupportUpgradeType(
upgrade_type=req.additionalParams.get('upgrade_type'))
def _make_hot(self, req, inst, grant_req, grant, vnfd):
if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE:
flavour_id = req.flavourId
@ -372,6 +631,60 @@ class Openstack(object):
obj.maxAddress = range_data.maxAddress
return obj
def _execute_coordinate_vnf_script(
self, req, inst, grant_req, grant,
vnfd, nested_stack_id, heat_client, vdu_param,
vim_info, vdu_image_id_flag, operation='change_vnfpkg'):
coordinate_vnf = None
coordinate_vnf_class = None
if req.obj_attr_is_set('additionalParams'):
if operation == 'change_vnfpkg':
coordinate_vnf = req.additionalParams.get(
'lcm-operation-coordinate-new-vnf')
coordinate_vnf_class = req.additionalParams.get(
'lcm-operation-coordinate-new-vnf-class')
else:
coordinate_vnf = req.additionalParams.get(
'lcm-operation-coordinate-old-vnf')
coordinate_vnf_class = req.additionalParams.get(
'lcm-operation-coordinate-old-vnf-class')
if coordinate_vnf and coordinate_vnf_class:
if operation == 'change_vnfpkg':
ssh_ip = self._get_ssh_ip(nested_stack_id,
vdu_param.get('new_vnfc_param'),
heat_client)
else:
ssh_ip = self._get_ssh_ip(nested_stack_id,
vdu_param.get('old_vnfc_param'),
heat_client)
if not ssh_ip:
raise sol_ex.SshIpNotFoundException
image, flavor = self._get_current_vdu_image_and_flavor(
nested_stack_id, vdu_param.get('vdu_id'),
heat_client, vim_info, vdu_image_id_flag)
tmp_csar_dir = vnfd.make_tmp_csar_dir()
script_dict = {
"request": req.to_dict(),
"vnf_instance": inst.to_dict(),
"grant_request": grant_req.to_dict(),
"grant_response": grant.to_dict(),
"tmp_csar_dir": tmp_csar_dir,
"vdu_info": {
"ssh_ip": ssh_ip,
"new_image": image,
"new_flavor": flavor,
"vdu_param": vdu_param
}
}
script_path = os.path.join(tmp_csar_dir, coordinate_vnf)
out = subprocess.run(["python3", script_path],
input=pickle.dumps(script_dict),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if out.returncode != 0:
LOG.error(out)
raise sol_ex.CoordinateVNFExecutionFailed
def _proto_data_to_info(self, proto_data):
# make CpProtocolInfo (5.5.3.9b) from CpProtocolData (4.4.1.10b)
proto_info = objects.CpProtocolInfoV2(
@ -463,6 +776,285 @@ class Openstack(object):
return ext_vl
def _get_vdu_info(self, vnfd, grant, inst):
flavour_id = inst.instantiatedVnfInfo.flavourId
vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
storage_nodes = vnfd.get_storage_nodes(flavour_id)
vdu_info_dict = {}
for name, node in vdu_nodes.items():
flavor = self._get_param_flavor(vnfd, name, flavour_id, grant)
image = self._get_param_image(vnfd, name, flavour_id, grant)
vdu_storage_names = vnfd.get_vdu_storages(node)
volume_name = ''
volume_size = ''
for vdu_storage_name in vdu_storage_names:
if storage_nodes[vdu_storage_name].get(
'properties').get('sw_image_data'):
image = self._get_param_image(
vnfd, vdu_storage_name, flavour_id, grant)
volume_name = vdu_storage_name
volume_size = storage_nodes[vdu_storage_name].get(
'properties', {}).get(
'virtual_block_storage_data', '').get(
'size_of_storage', ''
)
volume_size = volume_size.rstrip(' GB')
if not volume_size.isdigit():
raise sol_ex.InvalidVolumeSize
break
vdu_info_dict[name] = {
"flavor": flavor,
"image": image
}
if volume_name:
vdu_info_dict[name]['volume_info'] = {
"volume_name": volume_name,
"volume_size": volume_size
}
return vdu_info_dict
def _get_param_flavor(self, vnfd, vdu_name, flavour_id, grant):
# try to get from grant
if grant.obj_attr_is_set('vimAssets'):
assets = grant.vimAssets
if assets.obj_attr_is_set('computeResourceFlavours'):
flavours = assets.computeResourceFlavours
for flavour in flavours:
if flavour.vnfdVirtualComputeDescId == vdu_name:
return flavour.vimFlavourId
# if specified in VNFD, use it
# NOTE: if not found. parameter is set to None.
# may be error when stack create
return vnfd.get_compute_flavor(flavour_id, vdu_name)
def _get_param_image(self, vnfd, vdu_name, flavour_id, grant):
# try to get from grant
if grant.obj_attr_is_set('vimAssets'):
assets = grant.vimAssets
if assets.obj_attr_is_set('softwareImages'):
images = assets.softwareImages
for image in images:
if image.vnfdSoftwareImageId == vdu_name:
return image.vimSoftwareImageId
# if specified in VNFD, use it
# NOTE: if not found. parameter is set to None.
# may be error when stack create
sw_images = vnfd.get_sw_image(flavour_id)
for name, image in sw_images.items():
if name == vdu_name:
return image
return None
def _get_current_vdu_image_and_flavor(
self, nested_stack_id, resource_name,
heat_client, vim_info, vdu_image_id_flag):
vdu_info = heat_client.get_resource_info(
nested_stack_id, resource_name)
if vdu_info.get('attributes').get('image'):
image = vdu_info.get('attributes').get('image').get('id')
if not vdu_image_id_flag:
glance_client = glance_utils.GlanceClient(vim_info)
image = glance_client.get_image(image).name
else:
volume_ids = [volume.get('id') for volume in vdu_info.get(
'attributes').get('os-extended-volumes:volumes_attached')]
cinder_client = cinder_utils.CinderClient(vim_info)
if vdu_image_id_flag:
image = [cinder_client.get_volume(
volume_id).volume_image_metadata.get('image_id')
for volume_id in volume_ids if cinder_client.get_volume(
volume_id).volume_image_metadata][0]
else:
image = [cinder_client.get_volume(
volume_id).volume_image_metadata.get('image_name')
for volume_id in volume_ids if cinder_client.get_volume(
volume_id).volume_image_metadata][0]
flavor_name = vdu_info.get('attributes').get('flavor').get(
'original_name')
return image, flavor_name
def _get_new_res_info(self, parent_resource_name, vdu_infos,
stack_id, vnfd_info, vdu_id, heat_client):
new_res_infos = {
"stack_id": stack_id,
"parent_resource_name": parent_resource_name
}
nested_reses = heat_client.get_resource_list(
stack_id.split('/')[1])['resources']
for nested_res in nested_reses:
if nested_res.get(
'resource_type') == 'OS::Nova::Server' and nested_res.get(
'resource_name') == vdu_id:
new_res_infos["vdu_id"] = nested_res.get(
'physical_resource_id')
elif nested_res.get(
'resource_type'
) == 'OS::Cinder::Volume' and nested_res.get(
'resource_name') == vnfd_info.get(
'volume_info').get('volume_name'):
new_res_infos['volume_info'] = {
"volume_name": nested_res.get('resource_name'),
"volume_id": nested_res.get('physical_resource_id')
}
vdu_infos.append(new_res_infos)
def _get_ssh_ip(self, nested_stack_id, vnfc_param, heat_client):
cp_name = vnfc_param.get('cp_name')
cp_info = heat_client.get_resource_info(nested_stack_id, cp_name)
if cp_info.get('attributes').get('floating_ip_address'):
ssh_ip = cp_info.get('attributes').get('floating_ip_address')
else:
ssh_ip = cp_info.get('attributes').get('fixed_ips')[0].get(
'ip_address')
return ssh_ip
def _update_vnf_instantiated_info(
self, req, new_res_ids, inst, vnfd, heat_client, stack_name,
operation='change_vnfpkg'):
instantiated_vnf_info = inst.instantiatedVnfInfo
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_reses = heat_client.get_resources(stack_name)
storage_reses = self._get_checked_reses(
vnfd.get_storage_nodes(inst.instantiatedVnfInfo.flavourId),
heat_utils.get_storage_reses(heat_reses))
# handle storage_info
def _res_to_handle(res):
return objects.ResourceHandle(
resourceId=res['physical_resource_id'],
vimLevelResourceType=res['resource_type'],
vimConnectionId=vim_info.vimId)
storage_infos = [
objects.VirtualStorageResourceInfoV2(
id=res_id,
virtualStorageDescId=res['resource_name'],
storageResource=_res_to_handle(res)
)
for res_id, res in storage_reses.items()
]
# handle vnfc_resource_info
for vnfc_res in instantiated_vnf_info.vnfcResourceInfo:
if new_res_ids.get(vnfc_res.vduId):
new_res_info = [vnfc_info for vnfc_info
in new_res_ids.get(vnfc_res.vduId)
if vnfc_info['stack_id']
== vnfc_res.metadata['stack_id']]
if not new_res_info:
continue
new_res_info = new_res_info[0]
current_vnfc = []
if instantiated_vnf_info.obj_attr_is_set('vnfcInfo'):
current_vnfc = [
vnfc for vnfc in instantiated_vnf_info.vnfcInfo
if vnfc.id == _make_combination_id(
vnfc_res.vduId, vnfc_res.id)][0]
vnfc_res.id = new_res_info.get('vdu_id')
vnfc_res.computeResource.resourceId = new_res_info.get(
'vdu_id')
if current_vnfc:
current_vnfc.id = _make_combination_id(
vnfc_res.vduId, vnfc_res.id)
current_vnfc.vnfcResourceInfoId = vnfc_res.id
if operation == 'change_vnfpkg':
vnfc_res.metadata['current_vnfd_id'] = req.vnfdId
else:
vnfc_res.metadata['current_vnfd_id'] = inst.vnfdId
storage_ids = [
storage_id for storage_id, storage_res in
storage_reses.items()
if (vnfc_res.vduId in storage_res.get(
'required_by', []))]
if vnfc_res.metadata.get('parent_resource_name') != stack_name:
storage_ids = [
storage_id for storage_id, storage_res in
storage_reses.items()
if (vnfc_res.vduId in storage_res.get(
'required_by', []) and vnfc_res.metadata.get(
'parent_resource_name') == storage_res.get(
'parent_resource'))]
if storage_ids:
vnfc_res.storageResourceIds = storage_ids
else:
if vnfc_res.obj_attr_is_set('storageResourceIds'):
del vnfc_res.storageResourceIds
if storage_infos:
instantiated_vnf_info.virtualStorageResourceInfo = storage_infos
else:
if instantiated_vnf_info.obj_attr_is_set(
'virtualStorageResourceInfo'):
del instantiated_vnf_info.virtualStorageResourceInfo
def _update_vnf_template_and_parameter(
self, stack_id, vdu_infos, vdu_id, heat_client, new_template):
vdu_info = vdu_infos[vdu_id]
volume_info = vdu_info.get('volume_info', {})
base_templates = heat_client.get_template(stack_id)
old_parameter = heat_client.get_parameters(stack_id)['nfv']
new_parameter = json.loads(copy.deepcopy(old_parameter))
# old VM(created by volume) -> new VM(created by volume)
if volume_info and 'image' not in base_templates[
'resources'][vdu_id]['properties']:
new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get(
'flavor')
new_parameter['VDU'][volume_info.get('volume_name')][
'vcImageId'] = vdu_info.get('image')
# old VM(created by volume) -> new VM(created by image)
elif vdu_info.get(
'volume_info') is None and 'image' not in base_templates[
'resources'][vdu_id]['properties']:
# delete vdu's volume definition info
if len(base_templates['resources'][vdu_id]['properties'][
'block_device_mapping_v2']) > 1:
old_volumes = [name for name, value in
base_templates['resources'].items()
if value['type'] == 'OS::Cinder::Volume'
and value['properties']['image']]
for volume in base_templates['resources'][
vdu_id]['properties']['block_device_mapping_v2']:
if volume['volume_id']['get_resource'] in old_volumes:
target_volume_name = volume['volume_id'][
'get_resource']
else:
target_volume_name = base_templates['resources'][
vdu_id]['properties']['block_device_mapping_v2'][0][
'volume_id']['get_resource']
del new_parameter['VDU'][target_volume_name]
new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get(
'flavor')
new_parameter['VDU'][vdu_id]['vcImageId'] = vdu_info.get('image')
# old VM(created by image) -> new VM(created by volume)
elif volume_info and 'image' in base_templates[
'resources'][vdu_id]['properties']:
del new_parameter['VDU'][vdu_id]['vcImageId']
new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get(
'flavor')
new_parameter['VDU'][volume_info.get('volume_name')] = {
"vcImageId": vdu_infos[vdu_id].get('image')
}
# old VM(created by image) -> new VM(created by image)
else:
new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get(
'flavor')
new_parameter['VDU'][vdu_id]['vcImageId'] = vdu_info.get('image')
heat_parameter = {
"templates": new_template,
"nfv": new_parameter
}
return heat_parameter
def _make_ext_vl_info_from_req(self, req, grant, ext_cp_infos):
# make extVirtualLinkInfo
req_ext_vls = []
@ -947,3 +1539,47 @@ class Openstack(object):
inst_vnf_info.vnfcInfo = vnfc_infos
inst.instantiatedVnfInfo = inst_vnf_info
def _handle_exception(
self, res, new_res_ids, vdu_infos,
vdu_id, heat_client, req, inst, vnfd,
stack_name, ex=None, get_res_flag=False):
if get_res_flag:
if len(res.keys()) == 2:
par_stack_id = '{}/{}'.format(
res.get('resource_name'),
res.get('physical_resource_id'))
else:
par_stack_id = heat_utils.get_parent_nested_id(res)
self._get_new_res_info(
res.get('resource_name'),
new_res_ids[vdu_id],
par_stack_id,
vdu_infos[vdu_id], vdu_id,
heat_client)
self._update_vnf_instantiated_info(
req, new_res_ids, inst,
vnfd, heat_client, stack_name)
if ex:
raise ex
raise sol_ex.StackOperationFailed
def _get_entire_stack_fields(self, heat_client, stack_id,
group_vdu_ids, vdu_infos):
parameter = json.loads(heat_client.get_parameters(stack_id)['nfv'])
for group_vdu_id in group_vdu_ids:
if not parameter['VDU'][group_vdu_id].get('vcImageId'):
volume_info = vdu_infos[
group_vdu_id].get('volume_info')
parameter['VDU'][volume_info.get('volume_name')][
'vcImageId'] = vdu_infos[group_vdu_id].get(
'image')
else:
parameter['VDU'][group_vdu_id][
'vcImageId'] = vdu_infos[group_vdu_id].get(
'image')
fields = {
"stack_id": stack_id,
"parameters": {"nfv": parameter}
}
return fields

View File

@ -15,6 +15,7 @@
import yaml
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils
@ -23,39 +24,39 @@ class DefaultUserData(userdata_utils.AbstractUserData):
@staticmethod
def instantiate(req, inst, grant_req, grant, tmp_csar_dir):
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = req['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
userdata_utils.get_param_flavor(
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = userdata_utils.get_param_image(
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value['locationConstraints'] = (
userdata_utils.get_param_zone(
common_script_utils.get_param_zone(
vdu_name, grant_req, grant))
if 'desired_capacity' in vdu_value:
vdu_value['desired_capacity'] = (
userdata_utils.get_param_capacity(
common_script_utils.get_param_capacity(
vdu_name, inst, grant_req))
cps = nfv_dict.get('CP', {})
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
cp_value['network'] = userdata_utils.get_param_network(
cp_value['network'] = common_script_utils.get_param_network(
cp_name, grant, req)
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips(
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
@ -70,7 +71,7 @@ class DefaultUserData(userdata_utils.AbstractUserData):
fixed_ips.append(ips_i)
cp_value['fixed_ips'] = fixed_ips
userdata_utils.apply_ext_managed_vls(top_hot, req, grant)
common_script_utils.apply_ext_managed_vls(top_hot, req, grant)
if 'nfv' in req.get('additionalParams', {}):
nfv_dict = inst_utils.json_merge_patch(nfv_dict,
@ -98,19 +99,19 @@ class DefaultUserData(userdata_utils.AbstractUserData):
# NOTE: complete 'nfv' dict can not be made at the moment
# since InstantiateVnfRequest is necessary to make it.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
new_vdus = {}
for vdu_name, vdu_value in vdus.items():
if 'desired_capacity' in vdu_value:
capacity = userdata_utils.get_param_capacity(
capacity = common_script_utils.get_param_capacity(
vdu_name, inst, grant_req)
new_vdus[vdu_name] = {'desired_capacity': capacity}
@ -125,19 +126,19 @@ class DefaultUserData(userdata_utils.AbstractUserData):
# It is thought that it is suitable that this method defines
# here since it is very likely to scale method above.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
new_vdus = {}
for vdu_name, vdu_value in vdus.items():
if 'desired_capacity' in vdu_value:
capacity = userdata_utils.get_current_capacity(
capacity = common_script_utils.get_current_capacity(
vdu_name, inst)
new_vdus[vdu_name] = {'desired_capacity': capacity}
@ -155,25 +156,26 @@ class DefaultUserData(userdata_utils.AbstractUserData):
# NOTE: complete 'nfv' dict can not be made at the moment
# since InstantiateVnfRequest is necessary to make it.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
cps = nfv_dict.get('CP', {})
new_cps = {}
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
network = userdata_utils.get_param_network(cp_name, grant, req)
network = common_script_utils.get_param_network(
cp_name, grant, req)
if network is None:
continue
new_cps.setdefault(cp_name, {})
new_cps[cp_name]['network'] = network
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips(
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
@ -200,27 +202,28 @@ class DefaultUserData(userdata_utils.AbstractUserData):
# It is thought that it is suitable that this method defines
# here since it is very likely to scale method above.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
cps = nfv_dict.get('CP', {})
new_cps = {}
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
network = userdata_utils.get_param_network_from_inst(
network = common_script_utils.get_param_network_from_inst(
cp_name, inst)
if network is None:
continue
new_cps.setdefault(cp_name, {})
new_cps[cp_name]['network'] = network
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips_from_inst(
cp_name, inst)
ext_fixed_ips = (
common_script_utils.get_param_fixed_ips_from_inst(
cp_name, inst))
fixed_ips = []
for i in range(len(ext_fixed_ips)):
if i not in cp_value['fixed_ips']:

View File

@ -16,7 +16,6 @@
import abc
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnfd_utils
class AbstractUserData(metaclass=abc.ABCMeta):
@ -54,224 +53,3 @@ class AbstractUserData(metaclass=abc.ABCMeta):
@abc.abstractmethod
def heal(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
def get_vnfd(vnfd_id, csar_dir):
vnfd = vnfd_utils.Vnfd(vnfd_id)
vnfd.init_from_csar_dir(csar_dir)
return vnfd
def init_nfv_dict(hot_template):
get_params = []
def _get_get_param(prop):
if isinstance(prop, dict):
for key, value in prop.items():
if key == 'get_param':
get_params.append(value)
else:
_get_get_param(value)
elif isinstance(prop, list):
for value in prop:
_get_get_param(value)
for res in hot_template.get('resources', {}).values():
_get_get_param(res.get('properties', {}))
nfv = {}
for param in get_params:
if (not isinstance(param, list) or len(param) < 4 or
param[0] != 'nfv'):
continue
parent = nfv
for item in param[1:-1]:
parent.setdefault(item, {})
parent = parent[item]
parent[param[-1]] = None
# TODO(oda-g): enhance to handle list
# NOTE: List is not considered here and only 'fixed_ips' is treated as
# list in userdata_default.py at the moment.
# Note that if handling list is enhanced, userdata_default.py is
# necessary to modify.
return nfv
def get_param_flavor(vdu_name, flavour_id, vnfd, grant):
# try to get from grant
if 'vimAssets' in grant:
assets = grant['vimAssets']
if 'computeResourceFlavours' in assets:
flavours = assets['computeResourceFlavours']
for flavour in flavours:
if flavour['vnfdVirtualComputeDescId'] == vdu_name:
return flavour['vimFlavourId']
# if specified in VNFD, use it
# NOTE: if not found. parameter is set to None.
# may be error when stack create
return vnfd.get_compute_flavor(flavour_id, vdu_name)
def get_param_image(vdu_name, flavour_id, vnfd, grant):
# try to get from grant
if 'vimAssets' in grant:
assets = grant['vimAssets']
if 'softwareImages' in assets:
images = assets['softwareImages']
for image in images:
if image['vnfdSoftwareImageId'] == vdu_name:
return image['vimSoftwareImageId']
# if specified in VNFD, use it
# NOTE: if not found. parameter is set to None.
# may be error when stack create
sw_images = vnfd.get_sw_image(flavour_id)
for name, image in sw_images.items():
if name == vdu_name:
return image
def get_param_zone(vdu_name, grant_req, grant):
if 'zones' not in grant or 'addResources' not in grant:
return
for res in grant['addResources']:
if 'zoneId' not in res:
continue
for req_res in grant_req['addResources']:
if req_res['id'] == res['resourceDefinitionId']:
if req_res.get('resourceTemplateId') == vdu_name:
for zone in grant['zones']:
if zone['id'] == res['zoneId']: # must be found
return zone['zoneId']
def get_current_capacity(vdu_name, inst):
count = 0
inst_vnfcs = (inst.get('instantiatedVnfInfo', {})
.get('vnfcResourceInfo', []))
for inst_vnfc in inst_vnfcs:
if inst_vnfc['vduId'] == vdu_name:
count += 1
return count
def get_param_capacity(vdu_name, inst, grant_req):
# NOTE: refer grant_req here since interpretation of VNFD was done when
# making grant_req.
count = get_current_capacity(vdu_name, inst)
add_reses = grant_req.get('addResources', [])
for res_def in add_reses:
if (res_def['type'] == 'COMPUTE' and
res_def['resourceTemplateId'] == vdu_name):
count += 1
rm_reses = grant_req.get('removeResources', [])
for res_def in rm_reses:
if (res_def['type'] == 'COMPUTE' and
res_def['resourceTemplateId'] == vdu_name):
count -= 1
return count
def _get_fixed_ips_from_extcp(extcp):
fixed_ips = []
for cp_conf in extcp['cpConfig'].values():
if 'cpProtocolData' not in cp_conf:
continue
for prot_data in cp_conf['cpProtocolData']:
if 'ipOverEthernet' not in prot_data:
continue
if 'ipAddresses' not in prot_data['ipOverEthernet']:
continue
for ip in prot_data['ipOverEthernet']['ipAddresses']:
data = {}
if 'fixedAddresses' in ip:
# pick up only one ip address
data['ip_address'] = str(ip['fixedAddresses'][0])
if 'subnetId' in ip:
data['subnet'] = ip['subnetId']
if data:
fixed_ips.append(data)
return fixed_ips
def get_param_network(cp_name, grant, req):
# see grant first then instantiateVnfRequest
vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', [])
for vl in vls:
for extcp in vl['extCps']:
if extcp['cpdId'] == cp_name:
return vl['resourceId']
def get_param_fixed_ips(cp_name, grant, req):
# see grant first then instantiateVnfRequest
vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', [])
for vl in vls:
for extcp in vl['extCps']:
if extcp['cpdId'] == cp_name:
return _get_fixed_ips_from_extcp(extcp)
def get_param_network_from_inst(cp_name, inst):
for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []):
for extcp in vl.get('currentVnfExtCpData', []):
if extcp['cpdId'] == cp_name:
return vl['resourceHandle']['resourceId']
def get_param_fixed_ips_from_inst(cp_name, inst):
for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []):
for extcp in vl.get('currentVnfExtCpData', []):
if extcp['cpdId'] == cp_name:
return _get_fixed_ips_from_extcp(extcp)
def apply_ext_managed_vls(hot_dict, req, grant):
# see grant first then instantiateVnfRequest
mgd_vls = (grant.get('extManagedVirtualLinks', []) +
req.get('extManagedVirtualLinks', []))
# NOTE: refer HOT only here, not refer VNFD.
# HOT and VNFD must be consistent.
for mgd_vl in mgd_vls:
vl_name = mgd_vl['vnfVirtualLinkDescId']
network_id = mgd_vl['resourceId']
get_res = {'get_resource': vl_name}
def _change(item):
if not isinstance(item, dict):
return
for key, value in item.items():
if value == get_res:
item[key] = network_id
else:
_change(value)
del_reses = []
for res_name, res_data in hot_dict.get('resources', {}).items():
# delete network definition
if res_name == vl_name:
del_reses.append(res_name)
# delete subnet definition
if res_data['type'] == 'OS::Neutron::Subnet':
net = (res_data.get('properties', {})
.get('network', {})
.get('get_resource'))
if net == vl_name:
del_reses.append(res_name)
# change '{get_resource: vl_name}' to network_id
_change(res_data)
for res_name in del_reses:
hot_dict['resources'].pop(res_name)

View File

@ -210,6 +210,52 @@ class LocalNfvo(object):
softwareImages=vim_sw_images
)
def change_vnfpkg_grant(self, context, grant_req, grant_res):
attr_list = ['updateResources', 'addResources', 'removeResources']
for attr in attr_list:
if grant_req.obj_attr_is_set(attr):
res_list = []
for res_def in grant_req[attr]:
g_info = objects.GrantInfoV1(res_def.id)
res_list.append(g_info)
grant_res[attr] = res_list
vnfd = self.get_vnfd(context, grant_req.vnfdId)
sw_image_data = vnfd.get_sw_image_data(grant_req.flavourId)
target_vdu_ids = [res['resourceTemplateId'] for
res in grant_req['updateResources']]
vdu_nodes = {key: value for key, value in vnfd.get_vdu_nodes(
grant_req.flavourId).items() if key in target_vdu_ids}
target_storage_nodes = []
for key, value in vdu_nodes.items():
target_storage_nodes.extend(vnfd.get_vdu_storages(value))
vim_sw_images = []
for res_id, sw_data in sw_image_data.items():
if 'file' in sw_data and (res_id in target_storage_nodes or
res_id in target_vdu_ids):
vim_info = self._get_vim_info(context, grant_req)
if vim_info is None:
msg = "No VimConnectionInfo to create glance image"
LOG.exception(msg)
raise sol_ex.LocalNfvoGrantFailed(sol_detail=msg)
try:
image = self._glance_create_image(
vim_info, vnfd, sw_data, grant_req.vnfInstanceId)
except Exception:
msg = "glance image create failed"
LOG.exception(msg)
raise sol_ex.LocalNfvoGrantFailed(sol_detail=msg)
else:
image = sw_data['name']
vim_sw_image = objects.VimSoftwareImageV1(
vnfdSoftwareImageId=res_id,
vimSoftwareImageId=image)
vim_sw_images.append(vim_sw_image)
if vim_sw_images:
grant_res.vimAssets = objects.GrantV1_VimAssets(
softwareImages=vim_sw_images
)
def grant(self, context, grant_req):
grant_res = objects.GrantV1(
id=uuidutils.generate_uuid(),
@ -222,6 +268,9 @@ class LocalNfvo(object):
if grant_req.operation == v2_fields.LcmOperationType.INSTANTIATE:
self.instantiate_grant(context, grant_req, grant_res)
elif grant_req.operation == v2_fields.LcmOperationType.CHANGE_VNFPKG:
self.change_vnfpkg_grant(context, grant_req, grant_res)
endpoint = config.CONF.v2_vnfm.endpoint
grant_res._links = objects.GrantV1_Links(
vnfLcmOpOcc=objects.Link(
@ -312,8 +361,10 @@ class LocalNfvo(object):
if vim_info is None:
# never happen. just for code consistency.
return
self._glance_delete_images(vim_info, inst.id)
elif lcmocc.operation == v2_fields.LcmOperationType.MODIFY_INFO:
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
self._glance_delete_images(vim_info, inst.id)
elif lcmocc.operation == v2_fields.LcmOperationType.MODIFY_INFO or (
lcmocc.operation == v2_fields.LcmOperationType.CHANGE_VNFPKG):
if (lcmocc.operationState ==
v2_fields.LcmOperationStateType.PROCESSING):
# register vnfdId of vnf instance so that

View File

@ -28,13 +28,23 @@ class ChangeCurrentVnfPkgRequest(base.TackerObject,
fields = {
'vnfdId': fields.StringField(nullable=False),
# NOTE: 'extVirtualLinks' is not supported.
# It can be specified but make no effect at all.
'extVirtualLinks': fields.ListOfObjectsField(
'ExtVirtualLinkData', nullable=True),
# NOTE: 'extManagedVirtualLinks' is not supported.
# It can be specified but make no effect at all.
'extManagedVirtualLinks': fields.ListOfObjectsField(
'ExtManagedVirtualLinkData', nullable=True),
# NOTE: 'vimConnectionInfo' is not supported.
# It can be specified but make no effect at all.
'vimConnectionInfo': fields.DictOfObjectsField(
'VimConnectionInfo', nullable=True),
'additionalParams': fields.KeyValuePairsField(nullable=True),
# NOTE: 'extensions' is not supported.
# It can be specified but make no effect at all.
'extensions': fields.KeyValuePairsField(nullable=True),
# NOTE: 'vnfConfigurableProperties' is not supported.
# It can be specified but make no effect at all.
'vnfConfigurableProperties': fields.KeyValuePairsField(nullable=True),
}

View File

@ -112,6 +112,12 @@ class Client(object):
path, "POST", body=req_body, version="2.0.0")
self.print(resp, body)
def change_vnfpkg(self, id, req_body):
path = self.path + '/' + id + '/change_vnfpkg'
resp, body = self.client.do_request(
path, "POST", body=req_body, version="2.0.0")
self.print(resp, body)
def retry(self, id):
path = self.path + '/' + id + '/retry'
resp, body = self.client.do_request(path, "POST", version="2.0.0")
@ -140,6 +146,7 @@ def usage():
print(" inst scale {id} body(path of content)")
print(" inst heal {id} body(path of content)")
print(" inst chg_ext_conn {id} body(path of content)")
print(" inst change_vnfpkg {id} body(path of content)")
print(" subsc create body(path of content)")
print(" subsc list [body(path of content)]")
print(" subsc show {id}")
@ -166,7 +173,8 @@ if __name__ == '__main__':
if resource == "inst":
if action not in ["create", "list", "show", "delete", "update",
"inst", "term", "scale", "heal", "chg_ext_conn"]:
"inst", "term", "scale", "heal", "chg_ext_conn",
"change_vnfpkg"]:
usage()
client = Client("/vnflcm/v2/vnf_instances")
elif resource == "subsc":
@ -224,6 +232,10 @@ if __name__ == '__main__':
if len(sys.argv) != 5:
usage()
client.chg_ext_conn(sys.argv[3], get_body(sys.argv[4]))
elif action == "change_vnfpkg":
if len(sys.argv) != 5:
usage()
client.change_vnfpkg(sys.argv[3], get_body(sys.argv[4]))
elif action == "retry":
if len(sys.argv) != 4:
usage()

View File

@ -76,6 +76,9 @@ class BaseSolV2Test(base.BaseTestCase):
service_type='compute')
cls.heat_client = heat_utils.HeatClient(vim_info)
cls.cinder_client = http_client.HttpClient(
auth, service_type='block-storage')
@classmethod
def tearDownClass(cls):
super(BaseSolV2Test, cls).tearDownClass()
@ -328,11 +331,21 @@ class BaseSolV2Test(base.BaseTestCase):
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.0.0")
def change_vnfpkg(self, inst_id, req_body):
path = "/vnflcm/v2/vnf_instances/{}/change_vnfpkg".format(inst_id)
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.0.0")
def terminate_vnf_instance(self, inst_id, req_body):
path = "/vnflcm/v2/vnf_instances/{}/terminate".format(inst_id)
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.0.0")
def rollback(self, lcmocc_id):
path = "/vnflcm/v2/vnf_lcm_op_occs/{}/rollback".format(lcmocc_id)
return self.tacker_client.do_request(
path, "POST", version="2.0.0")
def wait_lcmocc_complete(self, lcmocc_id):
# NOTE: It is not necessary to set timeout because the operation
# itself set timeout and the state will become 'FAILED_TEMP'.
@ -476,3 +489,21 @@ class BaseSolV2Test(base.BaseTestCase):
callback_url)
self.assertEqual(1, len(notify_mock_responses))
self.assertEqual(204, notify_mock_responses[0].status_code)
def get_current_vdu_image(
self, stack_id, stack_name, resource_name):
vdu_info = self.heat_client.get_resource_info(
f"{stack_name}/{stack_id}", resource_name)
if vdu_info.get('attributes').get('image'):
image_id = vdu_info.get('attributes').get('image').get('id')
else:
volume_ids = [volume.get('id') for volume in vdu_info.get(
'attributes').get('os-extended-volumes:volumes_attached')]
for volume_id in volume_ids:
path = f"/volumes/{volume_id}"
resp, resp_body = self.cinder_client.do_request(path, "GET")
if resp_body['volume']['volume_image_metadata']:
image_id = resp_body['volume'][
'volume_image_metadata'].get('image_id')
return image_id

View File

@ -829,3 +829,109 @@ def change_ext_conn_min(net_ids, subnets):
ext_vl_1
]
}
def change_vnfpkg_create(vnfd_id):
return {
"vnfdId": vnfd_id,
"vnfInstanceName": "vnf_change_vnfpkg",
"vnfInstanceDescription": "test_change_vnfpkg_from_image_to_image",
"metadata": {"dummy-key": "dummy-val"}
}
def change_vnfpkg_instantiate(net_ids, subnet_ids, auth_url,
flavor_id='simple'):
ext_vl_1 = {
"id": uuidutils.generate_uuid(),
"resourceId": net_ids['net0'],
"extCps": [
{
"cpdId": "VDU1_CP1",
"cpConfig": {
"VDU1_CP1_1": {
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"numDynamicAddresses": 1}]}}]}
}
},
{
"cpdId": "VDU2_CP1",
"cpConfig": {
"VDU2_CP1_1": {
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"fixedAddresses": ["10.10.0.101"]}]}}]}
}
}
],
}
return {
"flavourId": flavor_id,
"instantiationLevelId": "instantiation_level_1",
"extVirtualLinks": [
ext_vl_1
],
"vimConnectionInfo": {
"vim1": {
"vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3",
"vimId": uuidutils.generate_uuid(),
"interfaceInfo": {"endpoint": auth_url},
"accessInfo": {
"username": "nfv_user",
"region": "RegionOne",
"password": "devstack",
"project": "nfv",
"projectDomain": "Default",
"userDomain": "Default"
}
}
},
}
def change_vnfpkg(vnfd_id):
return {
"vnfdId": vnfd_id,
"additionalParams": {
"upgrade_type": "RollingUpdate",
"lcm-operation-coordinate-old-vnf":
"./Scripts/coordinate_old_vnf.py",
"lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf",
"lcm-operation-coordinate-new-vnf":
"./Scripts/coordinate_new_vnf.py",
"lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf",
"vdu_params": [{
"vdu_id": "VDU1",
"old_vnfc_param": {
"cp_name": "VDU1_CP1",
"username": "ubuntu",
"password": "ubuntu"
},
"new_vnfc_param": {
"cp_name": "VDU1_CP1",
"username": "ubuntu",
"password": "ubuntu"
}
}, {
"vdu_id": "VDU2",
"old_vnfc_param": {
"cp_name": "VDU2_CP1",
"username": "ubuntu",
"password": "ubuntu"
},
"new_vnfc_param": {
"cp_name": "VDU2_CP1",
"username": "ubuntu",
"password": "ubuntu"
}
}]
}
}

View File

@ -15,6 +15,7 @@
import yaml
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils
@ -47,39 +48,39 @@ class UserData(userdata_utils.AbstractUserData):
link_port_ids.append(cp_conf['linkPortId'])
return link_port_ids
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = req['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
userdata_utils.get_param_flavor(
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = userdata_utils.get_param_image(
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value['locationConstraints'] = (
userdata_utils.get_param_zone(
common_script_utils.get_param_zone(
vdu_name, grant_req, grant))
if 'desired_capacity' in vdu_value:
vdu_value['desired_capacity'] = (
userdata_utils.get_param_capacity(
common_script_utils.get_param_capacity(
vdu_name, inst, grant_req))
cps = nfv_dict.get('CP', {})
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
cp_value['network'] = userdata_utils.get_param_network(
cp_value['network'] = common_script_utils.get_param_network(
cp_name, grant, req)
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips(
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
@ -104,7 +105,7 @@ class UserData(userdata_utils.AbstractUserData):
cp_value['port'] = _get_param_port(
cp_name, grant, req).pop()
userdata_utils.apply_ext_managed_vls(top_hot, req, grant)
common_script_utils.apply_ext_managed_vls(top_hot, req, grant)
if 'nfv' in req.get('additionalParams', {}):
nfv_dict = inst_utils.json_merge_patch(nfv_dict,

View File

@ -1,4 +1,4 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -15,6 +15,7 @@
import yaml
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils
@ -47,39 +48,39 @@ class UserData(userdata_utils.AbstractUserData):
link_port_ids.append(cp_conf['linkPortId'])
return link_port_ids
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = req['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
userdata_utils.get_param_flavor(
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = userdata_utils.get_param_image(
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value['locationConstraints'] = (
userdata_utils.get_param_zone(
common_script_utils.get_param_zone(
vdu_name, grant_req, grant))
if 'desired_capacity' in vdu_value:
vdu_value['desired_capacity'] = (
userdata_utils.get_param_capacity(
common_script_utils.get_param_capacity(
vdu_name, inst, grant_req))
cps = nfv_dict.get('CP', {})
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
cp_value['network'] = userdata_utils.get_param_network(
cp_value['network'] = common_script_utils.get_param_network(
cp_name, grant, req)
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips(
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
@ -104,7 +105,7 @@ class UserData(userdata_utils.AbstractUserData):
cp_value['port'] = _get_param_port(
cp_name, grant, req).pop()
userdata_utils.apply_ext_managed_vls(top_hot, req, grant)
common_script_utils.apply_ext_managed_vls(top_hot, req, grant)
if 'nfv' in req.get('additionalParams', {}):
nfv_dict = inst_utils.json_merge_patch(nfv_dict,

View File

@ -0,0 +1,54 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1, vcImageId] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]}
outputs: {}

View File

@ -0,0 +1,27 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image }
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }

View File

@ -0,0 +1,53 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]}
outputs: {}

View File

@ -0,0 +1,39 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}]
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
VDU1-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: image }
size: 4
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: { get_resource: VDU1_CP1 }
metadata: { multiattach: "<is> True" }

View File

@ -0,0 +1,219 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
sw_image_data:
name: VDU1-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: VDU2-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,224 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: volume
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
requirements:
- virtual_storage: VDU1-VirtualStorage
VDU1-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 4 GB
rdma_enabled: true
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: VDU2-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
- change_vnf_pkg_new_image_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
provider: Company
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ]
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'Sample VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple,volume ] ]
default: simple
flavour_description:
type: string
default: "flavour"
requirements:
- virtual_link_external1:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_external2:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,145 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 os
import pickle
import sys
import time
from oslo_log import log as logging
import paramiko
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnfd_utils
LOG = logging.getLogger(__name__)
CMD_TIMEOUT = 30
SERVER_WAIT_COMPLETE_TIME = 60
SSH_CONNECT_RETRY_COUNT = 4
class SampleNewCoordinateVNFScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.vdu_info = vdu_info
def coordinate_vnf(self):
# check ssh connect and os version
"""(YiFeng) Add comment to check ssh access
The next part of code is to check connect VM via ssh.
Since the zuul's network cannot check this content, so
we comment this part of code. If you want to check them
in your local environment, please uncomment.
# user = self.vdu_info.get('vdu_param').get(
# 'new_vnfc_param').get('username')
# password = self.vdu_info.get('vdu_param').get(
# 'new_vnfc_param').get('password')
# host = self.vdu_info.get("ssh_ip")
# commander = self._init_commander(
# user, password, host, retry=SSH_CONNECT_RETRY_COUNT)
# ssh_command = 'cat /etc/os-release | grep PRETTY_NAME'
# result = self._execute_command(commander, host, ssh_command)
# os_version = result[0].replace('\n', '').split('=')[1]
# LOG.info('The os version of this new VM is %s', os_version)
"""
# check image and flavour updated successfully
vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId'))
vnfd.init_from_csar_dir(self.csar_dir)
vdu_infos = common_script_utils.get_vdu_info(
self.grant, self.inst, vnfd)
vdu_id = self.vdu_info.get('vdu_param').get('vdu_id')
image = vdu_infos.get(vdu_id, {}).get('image')
flavor = vdu_infos.get(vdu_id, {}).get('flavor')
if self.vdu_info.get('new_image') != image or self.vdu_info.get(
'new_flavor') != flavor:
error = "The VM's image or flavour update failed'"
LOG.error(error)
raise sol_ex.VMRunningFailed(error)
def _init_commander(self, user, password, host, retry):
while retry > 0:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
host, username=user, password=password)
LOG.info("Connected to %s", host)
return ssh
except paramiko.AuthenticationException as e:
LOG.error("Authentication failed when connecting to %s",
host)
raise sol_ex.VMRunningFailed(e)
except (paramiko.SSHException,
paramiko.ssh_exception.NoValidConnectionsError) as e:
LOG.debug(e)
retry -= 1
if retry == 0:
LOG.error(e)
raise sol_ex.VMRunningFailed(e)
time.sleep(SERVER_WAIT_COMPLETE_TIME)
def _execute_command(self, commander, host, command):
try:
stdin, stdout, stderr = commander.exec_command(command)
cmd_out = stdout.readlines()
cmd_err = stderr.readlines()
return_code = stdout.channel.recv_exit_status()
except paramiko.SSHException as e:
LOG.error("Command execution failed at %s. Giving up", host)
raise e
finally:
commander.close()
if return_code != 0:
error = cmd_err
raise sol_ex.VMRunningFailed(error_info=error)
result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % (
command, cmd_out, cmd_err, return_code)
LOG.debug("Remote command execution result: %s", result)
return cmd_out
def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer)
req = script_dict['request']
inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
vdu_info = script_dict['vdu_info']
script = SampleNewCoordinateVNFScript(
req, inst, grant_req, grant,
csar_dir, vdu_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__":
try:
main()
os._exit(0)
except Exception as ex:
sys.stderr.write(str(ex))
sys.stderr.flush()
os._exit(1)

View File

@ -0,0 +1,4 @@
TOSCA-Meta-File-Version: 1.0
CSAR-Version: 1.1
Created-by: Onboarding portal
Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml

View File

@ -0,0 +1,51 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 json
import os
import shutil
import tempfile
from oslo_utils import uuidutils
from tacker.tests.functional.sol_v2 import paramgen
from tacker.tests.functional.sol_v2 import utils
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
tmp_dir = tempfile.mkdtemp()
vnfd_id = uuidutils.generate_uuid()
image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/"
image_file = "cirros-0.5.2-x86_64-disk.img"
image_path = os.path.abspath(image_dir + image_file)
utils.make_zip(".", tmp_dir, vnfd_id, image_path)
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir)
# if your sample is change VM from image to image
change_vnfpkg_req_from_image_to_image = paramgen.change_vnfpkg(vnfd_id)
with open("change_vnfpkg_req_from_image_to_image", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_req_from_image_to_image, indent=2))
# if your sample is change VM from volume to image
change_vnfpkg_req_from_volume_to_image = paramgen.change_vnfpkg(vnfd_id)
del change_vnfpkg_req_from_volume_to_image['additionalParams']['vdu_params'][0]
with open("change_vnfpkg_req_from_volume", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_req_from_volume_to_image, indent=2))

View File

@ -0,0 +1,65 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]}
VDU2-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] }
size: 4
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: VDU2-multi
metadata: { multiattach: "<is> True" }
outputs: {}

View File

@ -0,0 +1,27 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image }
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }

View File

@ -0,0 +1,65 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]}
VDU2-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] }
size: 4
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: VDU2-multi
metadata: { multiattach: "<is> True" }
outputs: {}

View File

@ -0,0 +1,39 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}]
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
VDU1-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: image }
size: 4
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: { get_resource: VDU1_CP1 }
metadata: { multiattach: "<is> True" }

View File

@ -0,0 +1,224 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: ds2G
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 2 GB
virtual_cpu:
num_virtual_cpu: 2
virtual_local_storage:
- size_of_storage: 20 GB
requirements:
- virtual_storage: VDU2-VirtualStorage
VDU2-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 1 GB
rdma_enabled: true
sw_image_data:
name: VDU2-VirtualStorage-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,237 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: volume
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
requirements:
- virtual_storage: VDU1-VirtualStorage
VDU1-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 1 GB
rdma_enabled: true
sw_image_data:
name: VDU1-VirtualStorage-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
requirements:
- virtual_storage: VDU2-VirtualStorage
VDU2-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 1 GB
rdma_enabled: true
sw_image_data:
name: VDU2-VirtualStorage-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
- change_vnf_pkg_new_volume_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
provider: Company
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ]
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'Sample VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple,volume ] ]
default: simple
flavour_description:
type: string
default: "flavour"
requirements:
- virtual_link_external1:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_external2:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,145 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 os
import pickle
import sys
import time
from oslo_log import log as logging
import paramiko
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnfd_utils
LOG = logging.getLogger(__name__)
CMD_TIMEOUT = 30
SERVER_WAIT_COMPLETE_TIME = 60
SSH_CONNECT_RETRY_COUNT = 4
class SampleNewCoordinateVNFScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.vdu_info = vdu_info
def coordinate_vnf(self):
# check ssh connect and os version
"""(YiFeng) Add comment to check ssh access
The next part of code is to check connect VM via ssh.
Since the zuul's network cannot check this content, so
we comment this part of code. If you want to check them
in your local environment, please uncomment.
user = self.vdu_info.get('vdu_param').get(
'new_vnfc_param').get('username')
password = self.vdu_info.get('vdu_param').get(
'new_vnfc_param').get('password')
host = self.vdu_info.get("ssh_ip")
commander = self._init_commander(
user, password, host, retry=SSH_CONNECT_RETRY_COUNT)
ssh_command = 'cat /etc/os-release | grep PRETTY_NAME'
result = self._execute_command(commander, host, ssh_command)
os_version = result[0].replace('\n', '').split('=')[1]
LOG.info('The os version of this new VM is %s', os_version)
"""
# check image and flavour updated successfully
vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId'))
vnfd.init_from_csar_dir(self.csar_dir)
vdu_infos = common_script_utils.get_vdu_info(
self.grant, self.inst, vnfd)
vdu_id = self.vdu_info.get('vdu_param').get('vdu_id')
image = vdu_infos.get(vdu_id, {}).get('image')
flavor = vdu_infos.get(vdu_id, {}).get('flavor')
if self.vdu_info.get('new_image') != image or self.vdu_info.get(
'new_flavor') != flavor:
error = "The VM's image or flavour update failed'"
LOG.error(error)
raise sol_ex.VMRunningFailed(error)
def _init_commander(self, user, password, host, retry):
while retry > 0:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
host, username=user, password=password)
LOG.info("Connected to %s", host)
return ssh
except paramiko.AuthenticationException as e:
LOG.error("Authentication failed when connecting to %s",
host)
raise sol_ex.VMRunningFailed(e)
except (paramiko.SSHException,
paramiko.ssh_exception.NoValidConnectionsError) as e:
LOG.debug(e)
retry -= 1
if retry == 0:
LOG.error(e)
raise sol_ex.VMRunningFailed(e)
time.sleep(SERVER_WAIT_COMPLETE_TIME)
def _execute_command(self, commander, host, command):
try:
stdin, stdout, stderr = commander.exec_command(command)
cmd_out = stdout.readlines()
cmd_err = stderr.readlines()
return_code = stdout.channel.recv_exit_status()
except paramiko.SSHException as e:
LOG.error("Command execution failed at %s. Giving up", host)
raise e
finally:
commander.close()
if return_code != 0:
error = cmd_err
raise sol_ex.VMRunningFailed(error_info=error)
result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % (
command, cmd_out, cmd_err, return_code)
LOG.debug("Remote command execution result: %s", result)
return cmd_out
def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer)
req = script_dict['request']
inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
vdu_info = script_dict['vdu_info']
script = SampleNewCoordinateVNFScript(
req, inst, grant_req, grant,
csar_dir, vdu_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__":
try:
main()
os._exit(0)
except Exception as ex:
sys.stderr.write(str(ex))
sys.stderr.flush()
os._exit(1)

View File

@ -0,0 +1,67 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 os
import pickle
import sys
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
CMD_TIMEOUT = 30
SERVER_WAIT_COMPLETE_TIME = 60
SSH_CONNECT_RETRY_COUNT = 4
class SampleErrorNewCoordinateVNFScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.vdu_info = vdu_info
def coordinate_vnf(self):
raise Exception("ErrorNewCoordinateVNFScript")
def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer)
req = script_dict['request']
inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
vdu_info = script_dict['vdu_info']
script = SampleErrorNewCoordinateVNFScript(
req, inst, grant_req, grant,
csar_dir, vdu_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__":
try:
main()
os._exit(0)
except Exception as ex:
sys.stderr.write(str(ex))
sys.stderr.flush()
os._exit(1)

View File

@ -0,0 +1,4 @@
TOSCA-Meta-File-Version: 1.0
CSAR-Version: 1.1
Created-by: Onboarding portal
Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml

View File

@ -0,0 +1,54 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 json
import os
import shutil
import tempfile
from oslo_utils import uuidutils
from tacker.tests.functional.sol_v2 import paramgen
from tacker.tests.functional.sol_v2 import utils
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
tmp_dir = tempfile.mkdtemp()
vnfd_id = uuidutils.generate_uuid()
image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/"
image_file = "cirros-0.5.2-x86_64-disk.img"
image_path = os.path.abspath(image_dir + image_file)
utils.make_zip(".", tmp_dir, vnfd_id, image_path)
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir)
# if your sample is change VM from image to volume
change_vnfpkg_req_from_image_to_volume = paramgen.change_vnfpkg(vnfd_id)
del change_vnfpkg_req_from_image_to_volume['additionalParams']['vdu_params'][0]
with open("change_vnfpkg_req_from_image_to_volume", "w",
encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_req_from_image_to_volume, indent=2))
# if your sample is change VM from volume to volume
change_vnfpkg_req_from_volume_to_volume = paramgen.change_vnfpkg(vnfd_id)
del change_vnfpkg_req_from_volume_to_volume[
'additionalParams']['vdu_params'][0]
with open("change_vnfpkg_req_from_volume", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_req_from_volume_to_volume, indent=2))

View File

@ -0,0 +1,53 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1, vcImageId] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]}
outputs: {}

View File

@ -0,0 +1,26 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image }
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }

View File

@ -0,0 +1,219 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
sw_image_data:
name: VDU1-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.error
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: VDU2-image
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
- change_vnf_pkg_error_image_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
provider: Company
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ]
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'Sample VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple ] ]
default: simple
flavour_description:
type: string
default: "flavour"
requirements:
- virtual_link_external1:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_external2:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,145 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 os
import pickle
import sys
import time
from oslo_log import log as logging
import paramiko
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnfd_utils
LOG = logging.getLogger(__name__)
CMD_TIMEOUT = 30
SERVER_WAIT_COMPLETE_TIME = 60
SSH_CONNECT_RETRY_COUNT = 4
class SampleNewCoordinateVNFScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.vdu_info = vdu_info
def coordinate_vnf(self):
# check ssh connect and os version
"""(YiFeng) Add comment to check ssh access
The next part of code is to check connect VM via ssh.
Since the zuul's network cannot check this content, so
we comment this part of code. If you want to check them
in your local environment, please uncomment.
# user = self.vdu_info.get('vdu_param').get(
# 'new_vnfc_param').get('username')
# password = self.vdu_info.get('vdu_param').get(
# 'new_vnfc_param').get('password')
# host = self.vdu_info.get("ssh_ip"),
# commander = self._init_commander(
# user, password, host, retry=SSH_CONNECT_RETRY_COUNT)
# ssh_command = 'cat /etc/os-release | grep PRETTY_NAME'
# result = self._execute_command(commander, host, ssh_command)
# os_version = result.get_stdout()[0].replace('\n', '').split('=')
# LOG.info('The os version of this new VM is %s', os_version)
"""
# check image and flavour updated successfully
vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId'))
vnfd.init_from_csar_dir(self.csar_dir)
vdu_infos = common_script_utils.get_vdu_info(
self.grant, self.inst, vnfd)
vdu_id = self.vdu_info.get('vdu_param').get('vdu_id')
image = vdu_infos.get(vdu_id, {}).get('image')
flavor = vdu_infos.get(vdu_id, {}).get('flavor')
if self.vdu_info.get('new_image') != image or self.vdu_info.get(
'new_flavor') != flavor:
error = "The VM's image or flavour update failed'"
LOG.error(error)
raise sol_ex.VMRunningFailed(error)
def _init_commander(self, user, password, host, retry):
while retry > 0:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
host, username=user, password=password)
LOG.info("Connected to %s", host)
return ssh
except paramiko.AuthenticationException as e:
LOG.error("Authentication failed when connecting to %s",
host)
raise sol_ex.VMRunningFailed(e)
except (paramiko.SSHException,
paramiko.ssh_exception.NoValidConnectionsError) as e:
LOG.debug(e)
retry -= 1
if retry == 0:
LOG.error(e)
raise sol_ex.VMRunningFailed(e)
time.sleep(SERVER_WAIT_COMPLETE_TIME)
def _execute_command(self, commander, host, command):
try:
stdin, stdout, stderr = commander.exec_command(command)
cmd_out = stdout.readlines()
cmd_err = stderr.readlines()
return_code = stdout.channel.recv_exit_status()
except paramiko.SSHException as e:
LOG.error("Command execution failed at %s. Giving up", host)
raise e
finally:
commander.close()
if return_code != 0:
error = cmd_err
raise sol_ex.VMRunningFailed(error_info=error)
result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % (
command, cmd_out, cmd_err, return_code)
LOG.debug("Remote command execution result: %s", result)
return cmd_out
def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer)
req = script_dict['request']
inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
vdu_info = script_dict['vdu_info']
script = SampleNewCoordinateVNFScript(
req, inst, grant_req, grant,
csar_dir, vdu_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__":
try:
main()
os._exit(0)
except Exception as ex:
sys.stderr.write(str(ex))
sys.stderr.flush()
os._exit(1)

View File

@ -0,0 +1,4 @@
TOSCA-Meta-File-Version: 1.0
CSAR-Version: 1.1
Created-by: Onboarding portal
Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml

View File

@ -0,0 +1,46 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 json
import os
import shutil
import tempfile
from oslo_utils import uuidutils
from tacker.tests.functional.sol_v2 import paramgen
from tacker.tests.functional.sol_v2 import utils
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
tmp_dir = tempfile.mkdtemp()
vnfd_id = uuidutils.generate_uuid()
image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/"
image_file = "cirros-0.5.2-x86_64-disk.img"
image_path = os.path.abspath(image_dir + image_file)
utils.make_zip(".", tmp_dir, vnfd_id, image_path)
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir)
# if your sample is change VM from image to image update failed
change_vnfpkg_req_update_failed = paramgen.change_vnfpkg(vnfd_id)
del change_vnfpkg_req_update_failed['additionalParams']['vdu_params'][1]
with open("change_vnfpkg_req_update_failed", "w",
encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_req_update_failed, indent=2))

View File

@ -0,0 +1,53 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1, vcImageId] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]}
outputs: {}

View File

@ -0,0 +1,27 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image }
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }

View File

@ -0,0 +1,65 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1_scale:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
resource:
type: base_hot_nested_VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1_scale
adjustment_type: change_in_capacity
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
networks:
- port:
get_resource: VDU2_CP1
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] }
fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]}
VDU2-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] }
size: 4
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: VDU2-multi
metadata: { multiattach: "<is> True" }
outputs: {}

View File

@ -0,0 +1,39 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
net1:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}]
networks:
- port:
get_resource: VDU1_CP1
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
VDU1-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: image }
size: 4
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: { get_resource: VDU1_CP1 }
metadata: { multiattach: "<is> True" }

View File

@ -0,0 +1,211 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,229 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: volume
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
requirements:
- virtual_storage: VDU1-VirtualStorage
VDU1-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 4 GB
rdma_enabled: true
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
requirements:
- virtual_storage: VDU2-VirtualStorage
VDU2-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 4 GB
rdma_enabled: true
sw_image_data:
name: cirros-0.5.2-x86_64-disk
version: '0.5.2'
checksum:
algorithm: sha-256
hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 2 GB
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
VDU1_scale:
name: VDU1_scale
description: VDU1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: VDU1_scale
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
VDU1_scale:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
VDU1_scale:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]

View File

@ -0,0 +1,32 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- change_vnf_pkg_types.yaml
- change_vnf_pkg_old_image_df_simple.yaml
- change_vnf_pkg_old_volume_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
provider: Company
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ]
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'Sample VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple, volume ] ]
default: simple
flavour_description:
type: string
default: "flavour"
requirements:
- virtual_link_external1:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_external2:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,145 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 os
import pickle
import sys
import time
from oslo_log import log as logging
import paramiko
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnfd_utils
LOG = logging.getLogger(__name__)
CMD_TIMEOUT = 30
SERVER_WAIT_COMPLETE_TIME = 60
SSH_CONNECT_RETRY_COUNT = 4
class SampleOldCoordinateVNFScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.vdu_info = vdu_info
def coordinate_vnf(self):
# check ssh connect and os version
"""(YiFeng) Add comment to check ssh access
The next part of code is to check connect VM via ssh.
Since the zuul's network cannot check this content, so
we comment this part of code. If you want to check them
in your local environment, please uncomment.
# user = self.vdu_info.get('vdu_param').get(
# 'old_vnfc_param').get('username')
# password = self.vdu_info.get('vdu_param').get(
# 'old_vnfc_param').get('password')
# host = self.vdu_info.get("ssh_ip"),
# commander = self._init_commander(
# user, password, host, retry=SSH_CONNECT_RETRY_COUNT)
# ssh_command = 'cat /etc/os-release | grep PRETTY_NAME'
# result = self._execute_command(commander, host, ssh_command)
# os_version = result.get_stdout()[0].replace('\n', '').split('=')
# LOG.info('The os version of this new VM is %s', os_version)
"""
# check image and flavour updated successfully
vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId'))
vnfd.init_from_csar_dir(self.csar_dir)
vdu_infos = common_script_utils.get_vdu_info(
self.grant, self.inst, vnfd)
vdu_id = self.vdu_info.get('vdu_param').get('vdu_id')
image = vdu_infos.get(vdu_id, {}).get('image')
flavor = vdu_infos.get(vdu_id, {}).get('flavor')
if self.vdu_info.get('new_image') != image or self.vdu_info.get(
'new_flavor') != flavor:
error = "The VM's image or flavour update failed'"
LOG.error(error)
raise sol_ex.VMRunningFailed(error)
def _init_commander(self, user, password, host, retry):
while retry > 0:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
host, username=user, password=password)
LOG.info("Connected to %s", host)
return ssh
except paramiko.AuthenticationException as e:
LOG.error("Authentication failed when connecting to %s",
host)
raise sol_ex.VMRunningFailed(e)
except (paramiko.SSHException,
paramiko.ssh_exception.NoValidConnectionsError) as e:
LOG.debug(e)
retry -= 1
if retry == 0:
LOG.error(e)
raise sol_ex.VMRunningFailed(e)
time.sleep(SERVER_WAIT_COMPLETE_TIME)
def _execute_command(self, commander, host, command):
try:
stdin, stdout, stderr = commander.exec_command(command)
cmd_out = stdout.readlines()
cmd_err = stderr.readlines()
return_code = stdout.channel.recv_exit_status()
except paramiko.SSHException as e:
LOG.error("Command execution failed at %s. Giving up", host)
raise e
finally:
commander.close()
if return_code != 0:
error = cmd_err
raise sol_ex.VMRunningFailed(error_info=error)
result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % (
command, cmd_out, cmd_err, return_code)
LOG.debug("Remote command execution result: %s", result)
return cmd_out
def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer)
req = script_dict['request']
inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
vdu_info = script_dict['vdu_info']
script = SampleOldCoordinateVNFScript(
req, inst, grant_req, grant,
csar_dir, vdu_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__":
try:
main()
os._exit(0)
except Exception as ex:
sys.stderr.write(str(ex))
sys.stderr.flush()
os._exit(1)

View File

@ -0,0 +1,4 @@
TOSCA-Meta-File-Version: 1.0
CSAR-Version: 1.1
Created-by: Onboarding portal
Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml

View File

@ -0,0 +1,85 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 json
import os
import shutil
import tempfile
from oslo_utils import uuidutils
from tacker.tests.functional.sol_v2 import paramgen
from tacker.tests.functional.sol_v2 import utils
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
tmp_dir = tempfile.mkdtemp()
vnfd_id = uuidutils.generate_uuid()
image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/"
image_file = "cirros-0.5.2-x86_64-disk.img"
image_path = os.path.abspath(image_dir + image_file)
utils.make_zip(".", tmp_dir, vnfd_id, image_path)
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir)
create_req = paramgen.change_vnfpkg_create(vnfd_id)
terminate_req = paramgen.terminate_vnf_min()
net_ids = utils.get_network_ids(['net0'])
subnet_ids = utils.get_subnet_ids(['subnet0'])
# if your sample is change VM from image to image
instantiate_req_from_image_to_image = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, "http://localhost/identity/v3")
# if your sample is change VM from volume to image
instantiate_req_from_volume_to_image = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, "http://localhost/identity/v3", flavor_id='volume')
# if your sample is change VM from image to volume
instantiate_req_from_image_to_volume = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, "http://localhost/identity/v3")
# if your sample is change VM from volume to volume
instantiate_req_from_volume_to_volume = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, "http://localhost/identity/v3", flavor_id='volume')
# if your sample is change VM from image to image update failed
instantiate_req_update_failed = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, "http://localhost/identity/v3")
with open("create_req", "w") as f:
f.write(json.dumps(create_req, indent=2))
with open("terminate_req", "w") as f:
f.write(json.dumps(terminate_req, indent=2))
with open("instantiate_req_from_image_to_image", "w") as f:
f.write(json.dumps(instantiate_req_from_image_to_image, indent=2))
with open("instantiate_req_from_volume_to_image", "w") as f:
f.write(json.dumps(instantiate_req_from_volume_to_image, indent=2))
with open("instantiate_req_from_image_to_volume", "w") as f:
f.write(json.dumps(instantiate_req_from_image_to_volume, indent=2))
with open("instantiate_req_from_volume_to_volume", "w") as f:
f.write(json.dumps(instantiate_req_from_volume_to_volume, indent=2))
with open("instantiate_req_update_failed", "w") as f:
f.write(json.dumps(instantiate_req_update_failed, indent=2))

View File

@ -0,0 +1,644 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# 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 ddt
import os
import time
from tacker.tests.functional.sol_v2 import base_v2
from tacker.tests.functional.sol_v2 import paramgen
@ddt.ddt
class ChangeVnfPkgVnfLcmTest(base_v2.BaseSolV2Test):
@classmethod
def setUpClass(cls):
super(ChangeVnfPkgVnfLcmTest, cls).setUpClass()
cur_dir = os.path.dirname(__file__)
# tacker/tests/etc...
# /functional/sol_v2
image_dir = os.path.join(
cur_dir, "../../etc/samples/etsi/nfv/common/Files/images")
image_file = "cirros-0.5.2-x86_64-disk.img"
image_path = os.path.abspath(os.path.join(image_dir, image_file))
change_vnfpkg_from_image_to_image_path = os.path.join(
cur_dir, "samples/test_instantiate_vnf_with_old_image_or_volume")
cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package(
change_vnfpkg_from_image_to_image_path)
change_vnfpkg_from_image_to_image_path_2 = os.path.join(
cur_dir, "samples/test_change_vnf_pkg_with_new_image")
cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package(
change_vnfpkg_from_image_to_image_path_2, image_path=image_path)
change_vnfpkg_from_image_to_volume_path = os.path.join(
cur_dir, "samples/test_change_vnf_pkg_with_new_volume")
cls.vnf_pkg_3, cls.vnfd_id_3 = cls.create_vnf_package(
change_vnfpkg_from_image_to_volume_path, image_path=image_path)
change_vnfpkg_failed_in_update_path = os.path.join(
cur_dir, "samples/test_change_vnf_pkg_with_update_failed")
cls.vnf_pkg_4, cls.vnfd_id_4 = cls.create_vnf_package(
change_vnfpkg_failed_in_update_path, image_path=image_path)
@classmethod
def tearDownClass(cls):
super(ChangeVnfPkgVnfLcmTest, cls).tearDownClass()
cls.delete_vnf_package(cls.vnf_pkg_1)
cls.delete_vnf_package(cls.vnf_pkg_2)
cls.delete_vnf_package(cls.vnf_pkg_3)
cls.delete_vnf_package(cls.vnf_pkg_4)
def setUp(self):
super(ChangeVnfPkgVnfLcmTest, self).setUp()
def test_change_vnfpkg_from_image_to_image(self):
create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, self.auth_url)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp_1, body_1 = self.show_vnf_instance(inst_id)
stack_name = "vnf-{}".format(inst_id)
stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
old_vnfd_id = body_1['vnfdId']
self.assertEqual(200, resp_1.status_code)
self.check_resp_headers_in_get(resp_1)
self.check_resp_body(body_1, expected_inst_attrs)
change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp_2, body_2 = self.show_vnf_instance(inst_id)
image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
self.assertNotEqual(image_id_1, image_id_2)
self.assertEqual(200, resp_2.status_code)
self.check_resp_headers_in_get(resp_2)
self.check_resp_body(body_2, expected_inst_attrs)
new_vnfd_id = [obj['metadata']['current_vnfd_id'] for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo']][0]
self.assertNotEqual(old_vnfd_id, new_vnfd_id)
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
def test_change_vnfpkg_from_image_to_volume(self):
create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, self.auth_url)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp_1, body_1 = self.show_vnf_instance(inst_id)
stack_name = "vnf-{}".format(inst_id)
stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
self.assertEqual(200, resp_1.status_code)
self.check_resp_headers_in_get(resp_1)
self.check_resp_body(body_1, expected_inst_attrs)
resource_ids_1 = [obj['id'] for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_3)
del change_vnfpkg_req['additionalParams']['vdu_params'][0]
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp_2, body_2 = self.show_vnf_instance(inst_id)
image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
storageResourceIds = [obj.get('storageResourceIds') for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2']
self.assertIsNotNone(storageResourceIds)
resource_ids_2 = [obj['id'] for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertNotEqual(resource_ids_1, resource_ids_2)
self.assertNotEqual(image_id_1, image_id_2)
self.assertEqual(200, resp_2.status_code)
self.check_resp_headers_in_get(resp_2)
self.check_resp_body(body_2, expected_inst_attrs)
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
def test_change_vnfpkg_from_volume_to_image(self):
create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, self.auth_url, flavor_id='volume')
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp_1, body_1 = self.show_vnf_instance(inst_id)
stack_name = "vnf-{}".format(inst_id)
stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
storageResourceIds_1 = [
obj.get('storageResourceIds') for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['vduId'] == 'VDU2']
resource_ids_1 = [obj['id'] for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertIsNotNone(storageResourceIds_1)
self.assertEqual(200, resp_1.status_code)
self.check_resp_headers_in_get(resp_1)
self.check_resp_body(body_1, expected_inst_attrs)
change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_2)
del change_vnfpkg_req['additionalParams']['vdu_params'][0]
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp_2, body_2 = self.show_vnf_instance(inst_id)
image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
storageResourceIds_2 = [
obj.get('storageResourceIds') for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['vduId'] == 'VDU2']
resource_ids_2 = [obj['id'] for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertIsNone(storageResourceIds_2[0])
self.assertNotEqual(image_id_1, image_id_2)
self.assertNotEqual(resource_ids_1, resource_ids_2)
self.assertEqual(200, resp_2.status_code)
self.check_resp_headers_in_get(resp_2)
self.check_resp_body(body_2, expected_inst_attrs)
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
def test_change_vnfpkg_from_volume_to_volume(self):
create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, self.auth_url, flavor_id='volume')
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp_1, body_1 = self.show_vnf_instance(inst_id)
stack_name = "vnf-{}".format(inst_id)
stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
storageResourceId_1 = [
obj.get('storageResourceIds') for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['vduId'] == 'VDU2']
resource_ids_1 = [obj['id'] for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertEqual(200, resp_1.status_code)
self.check_resp_headers_in_get(resp_1)
self.check_resp_body(body_1, expected_inst_attrs)
change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_3)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp_2, body_2 = self.show_vnf_instance(inst_id)
image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2')
storageResourceId_2 = [
obj.get('storageResourceIds') for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['vduId'] == 'VDU2']
resource_ids_2 = [obj['id'] for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertNotEqual(image_id_1, image_id_2)
self.assertNotEqual(storageResourceId_1, storageResourceId_2)
self.assertNotEqual(resource_ids_1, resource_ids_2)
self.assertEqual(200, resp_2.status_code)
self.check_resp_headers_in_get(resp_2)
self.check_resp_body(body_2, expected_inst_attrs)
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
def test_change_vnfpkg_failed_in_update(self):
create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, self.auth_url)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp_1, body_1 = self.show_vnf_instance(inst_id)
old_vnfd_id = body_1['vnfdId']
self.assertEqual(200, resp_1.status_code)
self.check_resp_headers_in_get(resp_1)
self.check_resp_body(body_1, expected_inst_attrs)
change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_4)
del change_vnfpkg_req['additionalParams']['vdu_params'][1]
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
resp, body = self.rollback(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
resp_2, body_2 = self.show_vnf_instance(inst_id)
new_vnfd_id = [
obj['metadata'].get('current_vnfd_id') for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['metadata'].get('current_vnfd_id')][0]
self.assertEqual(old_vnfd_id, new_vnfd_id)
self.assertEqual(200, resp_2.status_code)
self.check_resp_headers_in_get(resp_2)
self.check_resp_body(body_2, expected_inst_attrs)
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
def test_change_vnfpkg_failed_with_error_coordinate_vnf(self):
create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.change_vnfpkg_instantiate(
net_ids, subnet_ids, self.auth_url, flavor_id='volume')
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp_1, body_1 = self.show_vnf_instance(inst_id)
storageResourceId_1 = [
obj.get('storageResourceIds') for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['vduId'] == 'VDU2']
resource_ids_1 = [obj['id'] for obj in body_1[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertEqual(200, resp_1.status_code)
self.check_resp_headers_in_get(resp_1)
self.check_resp_body(body_1, expected_inst_attrs)
change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_3)
change_vnfpkg_req['additionalParams'][
'lcm-operation-coordinate-new-vnf'
] = "./Scripts/error_coordinate_new_vnf.py"
change_vnfpkg_req['additionalParams'][
'lcm-operation-coordinate-new-vnf-class'
] = "ErrorCoordinateNewVnf"
del change_vnfpkg_req['additionalParams']['vdu_params'][0]
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
resp, body = self.rollback(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
resp_2, body_2 = self.show_vnf_instance(inst_id)
storageResourceId_2 = [
obj.get('storageResourceIds') for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo']
if obj['vduId'] == 'VDU2']
resource_ids_2 = [obj['id'] for obj in body_2[
'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[
'vduId'] == 'VDU2'][0]
self.assertEqual(200, resp_2.status_code)
self.check_resp_headers_in_get(resp_2)
self.check_resp_body(body_2, expected_inst_attrs)
self.assertNotEqual(storageResourceId_1, storageResourceId_2)
self.assertNotEqual(resource_ids_1, resource_ids_2)
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)

View File

@ -762,15 +762,15 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
stack_name_1 = href.split("/")[7]
break
_, port_info = self.heat_client.get_resource_info(
stack_name_1, stack_id_1, 'VDU1_CP1')
port_info = self.heat_client.get_resource_info(
f"{stack_name_1}/{stack_id_1}", 'VDU1_CP1')
before_physical_resource_id_1 = port_info['physical_resource_id']
before_fixed_ips_1 = port_info['attributes']['fixed_ips']
stack_id_2 = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
_, port_info = self.heat_client.get_resource_info(
stack_name, stack_id_2, 'VDU2_CP2')
port_info = self.heat_client.get_resource_info(
f"{stack_name}/{stack_id_2}", 'VDU2_CP2')
before_physical_resource_id_2 = port_info['physical_resource_id']
before_fixed_ips_2 = port_info['attributes']['fixed_ips']
@ -782,15 +782,15 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
_, port_info = self.heat_client.get_resource_info(
stack_name_1, stack_id_1, 'VDU1_CP1')
port_info = self.heat_client.get_resource_info(
f"{stack_name_1}/{stack_id_1}", 'VDU1_CP1')
after_physical_resource_id_1 = port_info['physical_resource_id']
after_fixed_ips_1 = port_info['attributes']['fixed_ips']
stack_id_2 = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
_, port_info = self.heat_client.get_resource_info(
stack_name, stack_id_2, 'VDU2_CP2')
port_info = self.heat_client.get_resource_info(
f"{stack_name}/{stack_id_2}", 'VDU2_CP2')
after_physical_resource_id_2 = port_info['physical_resource_id']
after_fixed_ips_2 = port_info['attributes']['fixed_ips']
@ -2859,8 +2859,8 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
# 14. Change external connectivity
stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][
'id']
_, port_info = self.heat_client.get_resource_info(
stack_name, stack_id, 'VDU2_CP2')
port_info = self.heat_client.get_resource_info(
f"{stack_name}/{stack_id}", 'VDU2_CP2')
before_physical_resource_id = port_info['physical_resource_id']
before_fixed_ips = port_info['attributes']['fixed_ips']
@ -2871,8 +2871,8 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
_, port_info = self.heat_client.get_resource_info(
stack_name, stack_id, 'VDU2_CP2')
port_info = self.heat_client.get_resource_info(
f"{stack_name}/{stack_id}", 'VDU2_CP2')
after_physical_resource_id = port_info['physical_resource_id']
after_fixed_ips = port_info['attributes']['fixed_ips']

View File

@ -15,7 +15,7 @@
import os
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils
from tacker.sol_refactored.common import common_script_utils
from tacker.tests import base
@ -23,14 +23,14 @@ SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000"
SAMPLE_FLAVOUR_ID = "simple"
class TestUserDataUtils(base.BaseTestCase):
class TestCommontScriptUtils(base.BaseTestCase):
def setUp(self):
super(TestUserDataUtils, self).setUp()
super(TestCommontScriptUtils, self).setUp()
cur_dir = os.path.dirname(__file__)
sample_dir = os.path.join(cur_dir, "../..", "samples")
sample_dir = os.path.join(cur_dir, "..", "samples")
self.vnfd_1 = userdata_utils.get_vnfd(SAMPLE_VNFD_ID,
self.vnfd_1 = common_script_utils.get_vnfd(SAMPLE_VNFD_ID,
os.path.join(sample_dir, "sample1"))
def test_init_nfv_dict(self):
@ -54,7 +54,7 @@ class TestUserDataUtils(base.BaseTestCase):
'subnet': None}}}
}
}
result = userdata_utils.init_nfv_dict(top_hot)
result = common_script_utils.init_nfv_dict(top_hot)
self.assertEqual(expected_result, result)
def test_get_param_flavor(self):
@ -68,12 +68,14 @@ class TestUserDataUtils(base.BaseTestCase):
}
}
result = userdata_utils.get_param_flavor('VDU1', SAMPLE_FLAVOUR_ID,
result = common_script_utils.get_param_flavor(
'VDU1', SAMPLE_FLAVOUR_ID,
self.vnfd_1, grant)
self.assertEqual(flavor, result)
# if not exist in grant, get from VNFD
result = userdata_utils.get_param_flavor('VDU2', SAMPLE_FLAVOUR_ID,
result = common_script_utils.get_param_flavor(
'VDU2', SAMPLE_FLAVOUR_ID,
self.vnfd_1, grant)
self.assertEqual('m1.tiny', result)
@ -90,7 +92,7 @@ class TestUserDataUtils(base.BaseTestCase):
}
}
result = userdata_utils.get_param_image('VDU2', SAMPLE_FLAVOUR_ID,
result = common_script_utils.get_param_image('VDU2', SAMPLE_FLAVOUR_ID,
self.vnfd_1, grant)
self.assertEqual(image_id, result)
@ -113,7 +115,7 @@ class TestUserDataUtils(base.BaseTestCase):
]
}
result = userdata_utils.get_param_zone('VDU1', grant_req, grant)
result = common_script_utils.get_param_zone('VDU1', grant_req, grant)
self.assertEqual('nova', result)
def test_get_param_capacity(self):
@ -145,9 +147,11 @@ class TestUserDataUtils(base.BaseTestCase):
}
}
result = userdata_utils.get_param_capacity('VDU1', inst, grant_req)
result = common_script_utils.get_param_capacity(
'VDU1', inst, grant_req)
self.assertEqual(2, result)
result = userdata_utils.get_param_capacity('VDU2', inst, grant_req)
result = common_script_utils.get_param_capacity(
'VDU2', inst, grant_req)
self.assertEqual(1, result)
def test_get_parama_network(self):
@ -167,7 +171,7 @@ class TestUserDataUtils(base.BaseTestCase):
]
}
result = userdata_utils.get_param_network('VDU1_CP1', {}, req)
result = common_script_utils.get_param_network('VDU1_CP1', {}, req)
self.assertEqual(res_id, result)
def test_get_param_fixed_ips(self):
@ -207,7 +211,7 @@ class TestUserDataUtils(base.BaseTestCase):
}
expected_result = [{'ip_address': ip_address, 'subnet': subnet_id}]
result = userdata_utils.get_param_fixed_ips('VDU2_CP2', {}, req)
result = common_script_utils.get_param_fixed_ips('VDU2_CP2', {}, req)
self.assertEqual(expected_result, result)
def _inst_example_get_network_fixed_ips_from_inst(self):
@ -252,7 +256,8 @@ class TestUserDataUtils(base.BaseTestCase):
def test_get_parama_network_from_inst(self):
inst = self._inst_example_get_network_fixed_ips_from_inst()
result = userdata_utils.get_param_network_from_inst('VDU2_CP2', inst)
result = common_script_utils.get_param_network_from_inst(
'VDU2_CP2', inst)
self.assertEqual("ext_vl_res_id", result)
def test_get_param_fixed_ips_from_inst(self):
@ -260,7 +265,8 @@ class TestUserDataUtils(base.BaseTestCase):
expected_result = [{'ip_address': 'ip_address', 'subnet': 'subnet_id'}]
result = userdata_utils.get_param_fixed_ips_from_inst('VDU2_CP2', inst)
result = common_script_utils.get_param_fixed_ips_from_inst(
'VDU2_CP2', inst)
self.assertEqual(expected_result, result)
def test_apply_ext_managed_vls(self):
@ -289,7 +295,7 @@ class TestUserDataUtils(base.BaseTestCase):
self.assertIn(vl, top_hot['resources'])
self.assertIn(vl_subnet, top_hot['resources'])
userdata_utils.apply_ext_managed_vls(top_hot, req, {})
common_script_utils.apply_ext_managed_vls(top_hot, req, {})
# check after
# replaced to resource id

View File

@ -914,6 +914,7 @@ _inst_info_example_4 = {
"extVirtualLinkInfo": _inst_info_example_2["extVirtualLinkInfo"],
# network resource is not changed but ports are re-receated.
# this is for check of SOL003 all=True case.
"extManagedVirtualLinkInfo": [
{
"id": "res_id_internalVL1",
@ -1099,6 +1100,257 @@ _inst_info_example_4 = {
# "vnfcInfo": omitted
}
# example_5 is update VDU1 and VDU2's info from example_1.
_inst_info_example_5 = {
# "flavourId", "vnfState", "scaleStatus", "maxScaleLevels" are omitted
# "extCpInfo": omitted
"extVirtualLinkInfo": [
{
"id": "bbf0932a-6142-4ea8-93cd-8059dba594a1",
"resourceHandle": {
"resourceId": "3529d333-dbcc-4d93-9b64-210647712569"
},
"extLinkPorts": [
{
"id": "res_id_VDU2_CP1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU2_CP1"
},
{
"id": "res_id_VDU1_CP1_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP1_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP1_1"
}
],
# "currentVnfExtCpData": omitted
},
{
"id": "790949df-c7b3-4926-a559-3895412f1dfe",
"resourceHandle": {
"resourceId": "367e5b3b-34dc-47f2-85b8-c39e3272893a"
},
"extLinkPorts": [
{
"id": "res_id_VDU2_CP2",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP2",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU2_CP2"
},
{
"id": "res_id_VDU1_CP2_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP2_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP2_1"
}
],
# "currentVnfExtCpData": omitted
}
],
"extManagedVirtualLinkInfo": [
{
"id": "res_id_internalVL1",
"vnfVirtualLinkDescId": "internalVL1",
"networkResource": {
"resourceId": "res_id_internalVL1"
},
"vnfLinkPorts": [
{
"id": "res_id_VDU2_CP3",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP3",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU2_CP3-res_id_VDU2",
"cpInstanceType": "VNFC_CP"
},
{
"id": "res_id_VDU1_CP3_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP3_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU1_CP3-res_id_VDU1_1",
"cpInstanceType": "VNFC_CP"
}
]
}
],
"vnfcResourceInfo": [
{
"id": "res_id_VDU1_1_update",
"vduId": "VDU1",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_1_update",
"vimLevelResourceType": "OS::Nova::Server"
},
"metadata": {
"current_vnfd_id": 'new_vnfd_id'
},
"storageResourceIds": [
"new_res_id_VirtualStorage_1"
],
"vnfcCpInfo": [
{
"id": "VDU1_CP1-res_id_VDU1_1",
"cpdId": "VDU1_CP1",
"vnfExtCpId": "cp-res_id_VDU1_CP1_1"
},
{
"id": "VDU1_CP2-res_id_VDU1_1",
"cpdId": "VDU1_CP2",
"vnfExtCpId": "cp-res_id_VDU1_CP2_1"
},
{
"id": "VDU1_CP3-res_id_VDU1_1",
"cpdId": "VDU1_CP3",
"vnfLinkPortId": "res_id_VDU1_CP3_1"
},
{
"id": "VDU1_CP4-res_id_VDU1_1",
"cpdId": "VDU1_CP4",
"vnfLinkPortId": "res_id_VDU1_CP4_1"
},
{
"id": "VDU1_CP5-res_id_VDU1_1",
"cpdId": "VDU1_CP5",
"vnfLinkPortId": "res_id_VDU1_CP5_1"
}
],
},
{
"id": "res_id_VDU2",
"vduId": "VDU2",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2",
"vimLevelResourceType": "OS::Nova::Server"
},
"metadata": {
"current_vnfd_id": 'new_vnfd_id'
},
"vnfcCpInfo": [
{
"id": "VDU2_CP1-res_id_VDU2",
"cpdId": "VDU2_CP1",
"vnfExtCpId": "cp-res_id_VDU2_CP1"
},
{
"id": "VDU2_CP2-res_id_VDU2",
"cpdId": "VDU2_CP2",
"vnfExtCpId": "cp-res_id_VDU2_CP2"
},
{
"id": "VDU2_CP3-res_id_VDU2",
"cpdId": "VDU2_CP3",
"vnfLinkPortId": "res_id_VDU2_CP3"
},
{
"id": "VDU2_CP4-res_id_VDU2",
"cpdId": "VDU2_CP4",
"vnfLinkPortId": "res_id_VDU2_CP4"
},
{
"id": "VDU2_CP5-res_id_VDU2",
"cpdId": "VDU2_CP5",
"vnfLinkPortId": "res_id_VDU2_CP5"
}
],
}
],
"vnfVirtualLinkResourceInfo": [
{
"id": "res_id_internalVL3",
"vnfVirtualLinkDescId": "internalVL3",
"networkResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_internalVL3",
"vimLevelResourceType": "OS::Neutron::Net"
},
"vnfLinkPorts": [
{
"id": "res_id_VDU2_CP5",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP5",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU2_CP5-res_id_VDU2",
"cpInstanceType": "VNFC_CP"
},
{
"id": "res_id_VDU1_CP5_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP5_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU1_CP5-res_id_VDU1_1",
"cpInstanceType": "VNFC_CP"
}
]
},
{
"id": "res_id_internalVL2",
"vnfVirtualLinkDescId": "internalVL2",
"networkResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_internalVL2",
"vimLevelResourceType": "OS::Neutron::Net"
},
"vnfLinkPorts": [
{
"id": "res_id_VDU2_CP4",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP4",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU2_CP4-res_id_VDU2",
"cpInstanceType": "VNFC_CP"
},
{
"id": "res_id_VDU1_CP4_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP4_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU1_CP4-res_id_VDU1_1",
"cpInstanceType": "VNFC_CP"
}
]
}
],
"virtualStorageResourceInfo": [
{
"id": "new_res_id_VirtualStorage_1",
"virtualStorageDescId": "VirtualStorage",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "new_res_id_VirtualStorage_1",
"vimLevelResourceType": "OS::Cinder::Volume"
}
}
],
}
# expected results
_expected_resource_changes_instantiate = {
"affectedVnfcs": [
@ -1878,6 +2130,95 @@ _expected_resource_changes_heal = {
}
]
}
_expected_resource_changes_change_vnfpkg = {
"affectedVnfcs": [
{
"id": "res_id_VDU1_1",
"vduId": "VDU1",
"changeType": "REMOVED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_1",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU1_CP1-res_id_VDU1_1",
"VDU1_CP2-res_id_VDU1_1",
"VDU1_CP3-res_id_VDU1_1",
"VDU1_CP4-res_id_VDU1_1",
"VDU1_CP5-res_id_VDU1_1"
],
"removedStorageResourceIds": [
"res_id_VirtualStorage_1"
]
},
{
"id": "res_id_VDU1_1_update",
"vduId": "VDU1",
"changeType": "ADDED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_1_update",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU1_CP1-res_id_VDU1_1",
"VDU1_CP2-res_id_VDU1_1",
"VDU1_CP3-res_id_VDU1_1",
"VDU1_CP4-res_id_VDU1_1",
"VDU1_CP5-res_id_VDU1_1"
],
"addedStorageResourceIds": [
"new_res_id_VirtualStorage_1"
],
'metadata': {
'current_vnfd_id': 'new_vnfd_id'
}
},
{
"id": "res_id_VDU2",
"vduId": "VDU2",
"changeType": "MODIFIED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU2_CP1-res_id_VDU2",
"VDU2_CP2-res_id_VDU2",
"VDU2_CP3-res_id_VDU2",
"VDU2_CP4-res_id_VDU2",
"VDU2_CP5-res_id_VDU2"
],
'metadata': {
'current_vnfd_id': 'new_vnfd_id'
}
}
],
"affectedVirtualStorages": [
{
"id": "new_res_id_VirtualStorage_1",
"virtualStorageDescId": "VirtualStorage",
"changeType": "ADDED",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "new_res_id_VirtualStorage_1",
"vimLevelResourceType": "OS::Cinder::Volume"
}
},
{
"id": "res_id_VirtualStorage_1",
"virtualStorageDescId": "VirtualStorage",
"changeType": "REMOVED",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VirtualStorage_1",
"vimLevelResourceType": "OS::Cinder::Volume"
}
}
]
}
class TestLcmOpOccUtils(base.BaseTestCase):
@ -2061,3 +2402,25 @@ class TestLcmOpOccUtils(base.BaseTestCase):
self.assertEqual(
_expected_resource_changes_heal,
self._sort_resource_changes(lcmocc['resourceChanges']))
def test_update_lcmocc_change_vnfpkg(self):
# prepare
inst_saved = objects.VnfInstanceV2()
inst_saved.instantiatedVnfInfo = (
objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example_1))
inst_saved.vnfdId = 'old_vnfd_id'
inst = objects.VnfInstanceV2()
inst.instantiatedVnfInfo = (
objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example_5))
lcmocc = objects.VnfLcmOpOccV2(operation='CHANGE_VNFPKG')
# execute update_lcmocc
lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst)
# check resourceChanges
lcmocc = lcmocc.to_dict()
self.assertEqual(
_expected_resource_changes_change_vnfpkg,
self._sort_resource_changes(lcmocc['resourceChanges']))

View File

@ -70,6 +70,36 @@ class TestConductorV2(db_base.SqlTestCase):
return lcmocc
def _change_vnfpkg_lcmocc(
self, op_state=fields.LcmOperationStateType.STARTING):
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
vnfdId=uuidutils.generate_uuid(),
vnfProvider='provider',
vnfProductName='product name',
vnfSoftwareVersion='software version',
vnfdVersion='vnfd version',
instantiationState='INSTANTIATED'
)
req = {"vnfdId": uuidutils.generate_uuid()}
lcmocc = objects.VnfLcmOpOccV2(
# required fields
id=uuidutils.generate_uuid(),
operationState=op_state,
stateEnteredTime=datetime.utcnow(),
startTime=datetime.utcnow(),
vnfInstanceId=inst.id,
operation=fields.LcmOperationType.CHANGE_VNFPKG,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req)
inst.create(self.context)
lcmocc.create(self.context)
return lcmocc
def _make_grant_req_and_grant(self, lcmocc):
grant_req = objects.GrantRequestV1(
# required fields
@ -129,6 +159,34 @@ class TestConductorV2(db_base.SqlTestCase):
self.assertRaises(sol_ex.GrantRequestOrGrantNotFound,
lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc)
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'process')
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant')
def test_start_lcm_op_change_vnfpkg_completed(
self, mocked_grant, mocked_get_vnfd,
mocked_send_lcmocc_notification, mocked_process):
# prepare
lcmocc = self._change_vnfpkg_lcmocc()
mocked_get_vnfd.return_value = mock.Mock()
mocked_grant.return_value = self._make_grant_req_and_grant(lcmocc)
mocked_process.return_value = mock.Mock()
op_state = []
def _store_state(context, lcmocc, inst, endpoint):
op_state.append(lcmocc.operationState)
mocked_send_lcmocc_notification.side_effect = _store_state
# run start_lcm_op
self.conductor.start_lcm_op(self.context, lcmocc.id)
# check operationState transition
self.assertEqual(3, mocked_send_lcmocc_notification.call_count)
self.assertEqual(fields.LcmOperationStateType.STARTING, op_state[0])
self.assertEqual(fields.LcmOperationStateType.PROCESSING, op_state[1])
self.assertEqual(fields.LcmOperationStateType.COMPLETED, op_state[2])
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant')

View File

@ -627,6 +627,29 @@ _modify_vnfc_info_example = {
]
}
# change_vnfpkg example
_change_vnfpkg_example = {
"vnfdId": '61723406-6634-2fc0-060a-0b11104d2667',
"additionalParams": {
"upgrade_type": "RollingUpdate",
"lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_old_vnf.py",
"lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf",
"lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_new_vnf.py",
"lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf",
"vdu_params": [{
"vdu_id": "VDU1",
"old_vnfc_param": {
"cp_name": "CP1",
"username": "ubuntu",
"password": "ubuntu"},
"new_vnfc_param": {
"cp_name": "CP1",
"username": "ubuntu",
"password": "ubuntu"},
}]
}
}
class TestVnfLcmDriverV2(base.BaseTestCase):
@ -1688,3 +1711,193 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
for key, value in check_reses.items():
for name, ids in value.items():
self.assertEqual(len(expected_res_ids[key][name]), len(ids))
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_change_vnfpkg_grant_update_reses(self, mocked_grant):
# prepare
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
vnfdId=SAMPLE_VNFD_ID,
vnfProvider='provider',
vnfProductName='product name',
vnfSoftwareVersion='software version',
vnfdVersion='vnfd version',
instantiationState='INSTANTIATED'
)
inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example)
inst.instantiatedVnfInfo = inst_info
req = objects.ChangeCurrentVnfPkgRequest.from_dict(
_change_vnfpkg_example)
lcmocc = objects.VnfLcmOpOccV2(
# required fields
id=uuidutils.generate_uuid(),
operationState=fields.LcmOperationStateType.PROCESSING,
stateEnteredTime=datetime.utcnow(),
startTime=datetime.utcnow(),
vnfInstanceId=inst.id,
operation=fields.LcmOperationType.CHANGE_VNFPKG,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req)
mocked_grant.return_value = objects.GrantV1()
# run change_vnfpkg_grant
grant_req, _ = self.driver.grant(
self.context, lcmocc, inst, self.vnfd_1)
# check grant_req is constructed according to intention
grant_req = grant_req.to_dict()
expected_fixed_items = {
'vnfInstanceId': inst.id,
'vnfLcmOpOccId': lcmocc.id,
'vnfdId': '61723406-6634-2fc0-060a-0b11104d2667',
'operation': 'CHANGE_VNFPKG',
'isAutomaticInvocation': False
}
for key, value in expected_fixed_items.items():
self.assertEqual(value, grant_req[key])
update_reses = grant_req['updateResources']
target_vdu_list = [
vdu_param.get(
'vdu_id') for vdu_param in req.additionalParams.get(
'vdu_params')]
for i in range(len(update_reses)):
self.assertEqual('COMPUTE', update_reses[i]['type'])
for target_vdu in target_vdu_list:
self.assertEqual(target_vdu,
update_reses[i]['resourceTemplateId'])
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_change_vnfpkg_grant_add_reses(self, mocked_grant):
# prepare
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
vnfdId=SAMPLE_VNFD_ID,
vnfProvider='provider',
vnfProductName='product name',
vnfSoftwareVersion='software version',
vnfdVersion='vnfd version',
instantiationState='INSTANTIATED'
)
inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example)
inst.instantiatedVnfInfo = inst_info
req = objects.ChangeCurrentVnfPkgRequest.from_dict(
_change_vnfpkg_example)
lcmocc = objects.VnfLcmOpOccV2(
# required fields
id=uuidutils.generate_uuid(),
operationState=fields.LcmOperationStateType.PROCESSING,
stateEnteredTime=datetime.utcnow(),
startTime=datetime.utcnow(),
vnfInstanceId=inst.id,
operation=fields.LcmOperationType.CHANGE_VNFPKG,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req)
mocked_grant.return_value = objects.GrantV1()
# run change_vnfpkg_grant
grant_req, _ = self.driver.grant(
self.context, lcmocc, inst, self.vnfd_1)
# check grant_req is constructed according to intention
grant_req = grant_req.to_dict()
expected_fixed_items = {
'vnfInstanceId': inst.id,
'vnfLcmOpOccId': lcmocc.id,
'vnfdId': '61723406-6634-2fc0-060a-0b11104d2667',
'operation': 'CHANGE_VNFPKG',
'isAutomaticInvocation': False
}
for key, value in expected_fixed_items.items():
self.assertEqual(value, grant_req[key])
add_reses = grant_req['addResources']
for inst_vnc in inst_info.vnfcResourceInfo:
nodes = self.vnfd_1.get_vdu_nodes(inst_info.flavourId)
vdu_storage_names = self.vnfd_1.get_vdu_storages(
nodes[inst_vnc.vduId])
for i in range(len(add_reses)):
self.assertEqual('STORAGE', add_reses[i]['type'])
for vdu_storage_name in vdu_storage_names:
self.assertEqual(vdu_storage_name,
add_reses[i]['resourceTemplateId'])
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_change_vnfpkg_grant_remove_reses(self, mocked_grant):
# prepare
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
vnfdId=SAMPLE_VNFD_ID,
vnfProvider='provider',
vnfProductName='product name',
vnfSoftwareVersion='software version',
vnfdVersion='vnfd version',
instantiationState='INSTANTIATED'
)
inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example)
inst.instantiatedVnfInfo = inst_info
req = objects.ChangeCurrentVnfPkgRequest.from_dict(
_change_vnfpkg_example)
lcmocc = objects.VnfLcmOpOccV2(
# required fields
id=uuidutils.generate_uuid(),
operationState=fields.LcmOperationStateType.PROCESSING,
stateEnteredTime=datetime.utcnow(),
startTime=datetime.utcnow(),
vnfInstanceId=inst.id,
operation=fields.LcmOperationType.CHANGE_VNFPKG,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req)
mocked_grant.return_value = objects.GrantV1()
# run change_vnfpkg_grant
grant_req, _ = self.driver.grant(
self.context, lcmocc, inst, self.vnfd_1)
# check grant_req is constructed according to intention
grant_req = grant_req.to_dict()
expected_fixed_items = {
'vnfInstanceId': inst.id,
'vnfLcmOpOccId': lcmocc.id,
'vnfdId': '61723406-6634-2fc0-060a-0b11104d2667',
'operation': 'CHANGE_VNFPKG',
'isAutomaticInvocation': False
}
for key, value in expected_fixed_items.items():
self.assertEqual(value, grant_req[key])
remove_reses = grant_req['removeResources']
inst_stor_info = inst_info.virtualStorageResourceInfo
check_reses = []
for str_info in inst_stor_info:
check_res = {
'resourceId': str_info.storageResource.resourceId,
'vimLevelResourceType':
str_info.storageResource.vimLevelResourceType
}
check_reses.append(check_res)
for i in range(len(remove_reses)):
self.assertEqual('STORAGE', remove_reses[i]['type'])
self.assertEqual(str_info.virtualStorageDescId,
remove_reses[i]['resourceTemplateId'])
for j in range(len(check_reses)):
for k in range(len(remove_reses)):
if j == k:
self.assertEqual(
check_reses[j]['resourceId'],
remove_reses[j]['resource']['resourceId'])
self.assertEqual(
check_reses[j]['vimLevelResourceType'],
remove_reses[j]['resource']['vimLevelResourceType'])

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
import yaml
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils
@ -23,39 +24,39 @@ class DefaultUserData(userdata_utils.AbstractUserData):
@staticmethod
def instantiate(req, inst, grant_req, grant, tmp_csar_dir):
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = req['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
userdata_utils.get_param_flavor(
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = userdata_utils.get_param_image(
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value['locationConstraints'] = (
userdata_utils.get_param_zone(
common_script_utils.get_param_zone(
vdu_name, grant_req, grant))
if 'desired_capacity' in vdu_value:
vdu_value['desired_capacity'] = (
userdata_utils.get_param_capacity(
common_script_utils.get_param_capacity(
vdu_name, inst, grant_req))
cps = nfv_dict.get('CP', {})
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
cp_value['network'] = userdata_utils.get_param_network(
cp_value['network'] = common_script_utils.get_param_network(
cp_name, grant, req)
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips(
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
@ -70,7 +71,7 @@ class DefaultUserData(userdata_utils.AbstractUserData):
fixed_ips.append(ips_i)
cp_value['fixed_ips'] = fixed_ips
userdata_utils.apply_ext_managed_vls(top_hot, req, grant)
common_script_utils.apply_ext_managed_vls(top_hot, req, grant)
if 'nfv' in req.get('additionalParams', {}):
nfv_dict = inst_utils.json_merge_patch(nfv_dict,
@ -98,19 +99,19 @@ class DefaultUserData(userdata_utils.AbstractUserData):
# NOTE: complete 'nfv' dict can not be made at the moment
# since InstantiateVnfRequest is necessary to make it.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
new_vdus = {}
for vdu_name, vdu_value in vdus.items():
if 'desired_capacity' in vdu_value:
capacity = userdata_utils.get_param_capacity(
capacity = common_script_utils.get_param_capacity(
vdu_name, inst, grant_req)
new_vdus[vdu_name] = {'desired_capacity': capacity}