Merge "Support individual VNFc management using HOT"

This commit is contained in:
Zuul 2022-09-06 08:18:29 +00:00 committed by Gerrit Code Review
commit 213f5f59ba
30 changed files with 2961 additions and 86 deletions

View File

@ -0,0 +1,7 @@
---
features:
- |
Support individual VNFC management using OpenStack Heat.
New sample BaseHOT, corresponding UserData script,
and utility functions are added to UserData class
for v2 VNF Lifecycle management API.

View File

@ -94,7 +94,7 @@ class HeatClient(object):
LOG.info("%s %s done.", operation, stack_name)
raise loopingcall.LoopingCallDone()
elif status in failed_status:
LOG.error("% %s failed.", operation, stack_name)
LOG.error("%s %s failed.", operation, stack_name)
sol_title = "%s failed" % operation
raise sol_ex.StackOperationFailed(sol_title=sol_title,
sol_detail=status_reason)

View File

@ -18,6 +18,7 @@ import json
import os
import pickle
import subprocess
import yaml
from dateutil import parser
import eventlet
@ -67,6 +68,19 @@ def _make_combination_id(a, b):
return '{}-{}'.format(a, b)
def _get_vdu_idx(vdu_with_idx):
part = vdu_with_idx.rpartition('-')
if part[1] == '':
return None
return int(part[2])
def _vdu_with_idx(vdu, vdu_idx):
if vdu_idx is None:
return vdu
return f'{vdu}-{vdu_idx}'
class Openstack(object):
def __init__(self):
@ -75,7 +89,6 @@ class Openstack(object):
def instantiate(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
# create or update stack
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
@ -113,21 +126,27 @@ class Openstack(object):
stack_name = heat_utils.get_stack_name(inst)
heat_client.delete_stack(stack_name)
def _update_nfv_dict(self, heat_client, stack_name, fields):
parameters = heat_client.get_parameters(stack_name)
LOG.debug("ORIG parameters: %s", parameters)
# NOTE: parameters['nfv'] is string
orig_nfv_dict = json.loads(parameters.get('nfv', '{}'))
if 'nfv' in fields['parameters']:
def _update_fields(self, heat_client, stack_name, fields):
if 'nfv' in fields.get('parameters', {}):
parameters = heat_client.get_parameters(stack_name)
LOG.debug("ORIG parameters: %s", parameters)
# NOTE: parameters['nfv'] is string
orig_nfv_dict = json.loads(parameters.get('nfv', '{}'))
fields['parameters']['nfv'] = inst_utils.json_merge_patch(
orig_nfv_dict, fields['parameters']['nfv'])
LOG.debug("NEW parameters: %s", fields['parameters'])
if 'template' in fields:
orig_template = heat_client.get_template(stack_name)
template = inst_utils.json_merge_patch(
orig_template, yaml.safe_load(fields['template']))
fields['template'] = yaml.safe_dump(template)
LOG.debug("NEW fields: %s", fields)
return fields
def scale(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
@ -139,17 +158,19 @@ class Openstack(object):
if res_def.type == 'COMPUTE']
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo:
if vnfc.computeResource.resourceId in vnfc_res_ids:
if 'parent_stack_id' not in vnfc.metadata:
if 'parent_stack_id' in vnfc.metadata:
# AutoScalingGroup
heat_client.mark_unhealthy(
vnfc.metadata['parent_stack_id'],
vnfc.metadata['parent_resource_name'])
elif 'vdu_idx' not in vnfc.metadata:
# It means definition of VDU in the BaseHOT
# is inappropriate.
raise sol_ex.UnexpectedParentResourceDefinition()
heat_client.mark_unhealthy(
vnfc.metadata['parent_stack_id'],
vnfc.metadata['parent_resource_name'])
# update stack
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
fields = self._update_fields(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# make instantiated_vnf_info
@ -158,6 +179,10 @@ class Openstack(object):
def scale_rollback(self, req, inst, grant_req, grant, vnfd):
# NOTE: rollback is supported for scale out only
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd,
is_rollback=True)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
@ -169,19 +194,18 @@ class Openstack(object):
for res in heat_utils.get_server_reses(heat_reses):
if res['physical_resource_id'] not in vnfc_ids:
metadata = self._make_vnfc_metadata(res, heat_reses)
if 'parent_stack_id' not in metadata:
if 'parent_stack_id' in metadata:
# AutoScalingGroup
heat_client.mark_unhealthy(
metadata['parent_stack_id'],
metadata['parent_resource_name'])
elif 'vdu_idx' not in metadata:
# It means definition of VDU in the BaseHOT
# is inappropriate.
raise sol_ex.UnexpectedParentResourceDefinition()
heat_client.mark_unhealthy(
metadata['parent_stack_id'],
metadata['parent_resource_name'])
# update (put back) 'desired_capacity' parameter
fields = self._update_nfv_dict(heat_client, stack_name,
userdata_default.DefaultUserData.scale_rollback(
req, inst, grant_req, grant, vnfd.csar_dir))
fields = self._update_fields(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# NOTE: instantiatedVnfInfo is not necessary to update since it
@ -190,13 +214,12 @@ class Openstack(object):
def change_ext_conn(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
# update stack
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
fields = self._update_fields(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# make instantiated_vnf_info
@ -204,31 +227,33 @@ class Openstack(object):
heat_client)
def change_ext_conn_rollback(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd,
is_rollback=True)
# update stack
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name,
userdata_default.DefaultUserData.change_ext_conn_rollback(
req, inst, grant_req, grant, vnfd.csar_dir))
fields = self._update_fields(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# NOTE: it is necessary to re-create instantiatedVnfInfo because
# ports may be changed.
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_client)
heat_client, is_rollback=True)
def heal(self, req, inst, grant_req, grant, vnfd):
# make HOT
# NOTE: _make_hot() is called as other operations, but it returns
# empty 'nfv' dict by default. Therefore _update_nfv_dict() returns
# empty 'nfv' dict by default. Therefore _update_fields() returns
# current heat parameters as is.
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
fields = self._update_fields(heat_client, stack_name, fields)
# "re_create" is set to True only when SOL003 heal(without
# vnfcInstanceId) and "all=True" in additionalParams.
@ -284,7 +309,6 @@ class Openstack(object):
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
@ -298,13 +322,35 @@ class Openstack(object):
# update stack
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
fields = self._update_fields(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# make instantiated_vnf_info
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_client)
def _get_flavor_from_vdu_dict(self, vnfc, vdu_dict):
vdu_name = _vdu_with_idx(vnfc.vduId, vnfc.metadata.get('vdu_idx'))
return vdu_dict.get(vdu_name, {}).get('computeFlavourId')
def _get_images_from_vdu_dict(self, vnfc, storage_infos, vdu_dict):
vdu_idx = vnfc.metadata.get('vdu_idx')
vdu_name = _vdu_with_idx(vnfc.vduId, vdu_idx)
vdu_names = [vdu_name]
if vnfc.obj_attr_is_set('storageResourceIds'):
vdu_names += [
_vdu_with_idx(storage_info.virtualStorageDescId, vdu_idx)
for storage_info in storage_infos
if storage_info.id in vnfc.storageResourceIds
]
images = {}
for vdu_name in vdu_names:
image = vdu_dict.get(vdu_name, {}).get('vcImageId')
if image:
images[vdu_name] = image
return images
def _change_vnfpkg_rolling_update(self, req, inst, grant_req, grant,
vnfd, fields, heat_client, is_rollback):
if not grant_req.obj_attr_is_set('removeResources'):
@ -348,30 +394,25 @@ class Openstack(object):
LOG.debug("stack fields: %s", vdu_fields)
heat_client.update_stack(parent_stack_id, vdu_fields)
else:
# handle single VM
# pickup 'vcImageId' and 'computeFlavourId'
# pickup 'vcImageId' and 'computeFlavourId' from vdu_dict
vdu_name = _vdu_with_idx(vnfc.vduId,
vnfc.metadata.get('vdu_idx'))
params = {}
if vdu_dict[vnfc.vduId].get('computeFlavourId'):
params[vnfc.vduId] = {'computeFlavourId':
vdu_dict[vnfc.vduId]['computeFlavourId']}
vdu_names = [vnfc.vduId]
if vnfc.obj_attr_is_set('storageResourceIds'):
storage_infos = (
inst.instantiatedVnfInfo.virtualStorageResourceInfo)
vdu_names += [
storage_info.virtualStorageDescId
for storage_info in storage_infos
if storage_info.id in vnfc.storageResourceIds
]
for vdu_name in vdu_names:
image = vdu_dict.get(vdu_name, {}).get('vcImageId')
if image:
params.setdefault(vdu_name, {})
params[vdu_name]['vcImageId'] = image
flavor = self._get_flavor_from_vdu_dict(vnfc, vdu_dict)
if flavor:
params[vdu_name] = {'computeFlavourId': flavor}
storage_infos = (
inst.instantiatedVnfInfo.virtualStorageResourceInfo
if vnfc.obj_attr_is_set('storageResourceIds') else [])
images = self._get_images_from_vdu_dict(vnfc,
storage_infos, vdu_dict)
for vdu_name, image in images.items():
params.setdefault(vdu_name, {})
params[vdu_name]['vcImageId'] = image
update_nfv_dict = {'nfv': {'VDU': params}}
update_fields = {'parameters': update_nfv_dict}
update_fields = self._update_nfv_dict(heat_client, stack_name,
update_fields = self._update_fields(heat_client, stack_name,
update_fields)
LOG.debug("stack fields: %s", update_fields)
heat_client.update_stack(stack_name, update_fields)
@ -429,8 +470,9 @@ class Openstack(object):
def change_vnfpkg_rollback(self, req, inst, grant_req, grant, vnfd,
lcmocc):
fields = userdata_default.DefaultUserData.change_vnfpkg_rollback(
req, inst, grant_req, grant, vnfd.csar_dir)
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd,
is_rollback=True)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
@ -444,15 +486,15 @@ class Openstack(object):
# stack update
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
fields = self._update_fields(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# NOTE: it is necessary to re-create instantiatedVnfInfo because
# some resources may be changed.
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_client)
heat_client, is_rollback=True)
def _make_hot(self, req, inst, grant_req, grant, vnfd):
def _make_hot(self, req, inst, grant_req, grant, vnfd, is_rollback=False):
if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE:
flavour_id = req.flavourId
else:
@ -470,10 +512,12 @@ class Openstack(object):
'lcm-operation-user-data-class')
if userdata is None and userdata_class is None:
LOG.debug("Processing default userdata %s", grant_req.operation)
operation = grant_req.operation.lower()
if is_rollback:
operation = operation + '_rollback'
LOG.debug("Processing default userdata %s", operation)
# NOTE: objects used here are dict compat.
method = getattr(userdata_default.DefaultUserData,
grant_req.operation.lower())
method = getattr(userdata_default.DefaultUserData, operation)
fields = method(req, inst, grant_req, grant, vnfd.csar_dir)
elif userdata is None or userdata_class is None:
# Both must be specified.
@ -488,7 +532,8 @@ class Openstack(object):
'vnf_instance': inst.to_dict(),
'grant_request': grant_req.to_dict(),
'grant_response': grant.to_dict(),
'tmp_csar_dir': tmp_csar_dir
'tmp_csar_dir': tmp_csar_dir,
'is_rollback': is_rollback
}
script_path = os.path.join(
os.path.dirname(__file__), "userdata_main.py")
@ -510,6 +555,8 @@ class Openstack(object):
fields['timeout_mins'] = (
CONF.v2_vnfm.openstack_vim_stack_create_timeout)
LOG.debug("stack fields: %s", fields)
return fields
def _get_checked_reses(self, nodes, reses):
@ -895,9 +942,16 @@ class Openstack(object):
}
parent_res = heat_utils.get_parent_resource(server_res, heat_reses)
if parent_res:
metadata['parent_stack_id'] = (
heat_utils.get_resource_stack_id(parent_res))
metadata['parent_resource_name'] = parent_res['resource_name']
parent_parent_res = heat_utils.get_parent_resource(parent_res,
heat_reses)
if (parent_parent_res and
parent_parent_res['resource_type'] ==
'OS::Heat::AutoScalingGroup'):
metadata['parent_stack_id'] = (
heat_utils.get_resource_stack_id(parent_res))
metadata['parent_resource_name'] = parent_res['resource_name']
else:
metadata['vdu_idx'] = _get_vdu_idx(parent_res['resource_name'])
return metadata
@ -917,22 +971,16 @@ class Openstack(object):
if k.startswith('image-'):
metadata[k] = v
else:
properties = nfv_dict['VDU'][vnfc_res_info.vduId]
metadata['flavor'] = properties['computeFlavourId']
vdu_names = [vnfc_res_info.vduId]
if vnfc_res_info.obj_attr_is_set('storageResourceIds'):
vdu_names += [
storage_info.virtualStorageDescId
for storage_info in storage_infos
if storage_info.id in vnfc_res_info.storageResourceIds
]
for vdu_name in vdu_names:
image = nfv_dict['VDU'].get(vdu_name, {}).get('vcImageId')
if image:
metadata[f'image-{vdu_name}'] = image
# assume it is found
metadata['flavor'] = self._get_flavor_from_vdu_dict(
vnfc_res_info, nfv_dict['VDU'])
images = self._get_images_from_vdu_dict(vnfc_res_info,
storage_infos, nfv_dict['VDU'])
for vdu_name, image in images.items():
metadata[f'image-{vdu_name}'] = image
def _make_instantiated_vnf_info(self, req, inst, grant_req, grant, vnfd,
heat_client):
heat_client, is_rollback=False):
# get heat resources
stack_name = heat_utils.get_stack_name(inst)
heat_reses = heat_client.get_resources(stack_name)
@ -1029,7 +1077,8 @@ class Openstack(object):
flavour_id, req, grant)
else:
old_inst_vnf_info = inst.instantiatedVnfInfo
if op == v2fields.LcmOperationType.CHANGE_EXT_CONN:
if (op == v2fields.LcmOperationType.CHANGE_EXT_CONN and
not is_rollback):
ext_vl_infos = self._make_ext_vl_info_from_req_and_inst(
req, grant, old_inst_vnf_info, ext_cp_infos)
else:
@ -1107,7 +1156,9 @@ class Openstack(object):
# grant request.
def _get_key(vnfc):
return parser.isoparse(vnfc.metadata['creation_time'])
vdu_idx = vnfc.metadata.get('vdu_idx', 0)
creation_time = parser.isoparse(vnfc.metadata['creation_time'])
return (vdu_idx, creation_time)
sorted_vnfc_res_infos = sorted(vnfc_res_infos, key=_get_key,
reverse=True)

View File

@ -39,7 +39,10 @@ def main():
module = importlib.import_module(class_module)
klass = getattr(module, userdata_class)
method = getattr(klass, grant_req['operation'].lower())
operation = grant_req['operation'].lower()
if script_dict['is_rollback']:
operation = operation + '_rollback'
method = getattr(klass, operation)
stack_dict = method(req, inst, grant_req, grant, tmp_csar_dir)
pickle.dump(stack_dict, sys.stdout.buffer)

View File

@ -0,0 +1,544 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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 copy
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
def add_idx(name, index):
return f'{name}-{index}'
def rm_idx(name_idx):
return name_idx.rpartition('-')[0]
def add_idx_to_vdu_template(vdu_template, vdu_idx):
"""Add index to the third element of get_param
ex. input VDU template:
---
VDU1:
type: VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network ] }
---
output VDU template:
---
VDU1:
type: VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1-1, computeFlavourId ] }
image-VDU1: { get_param: [ nfv, VDU, VDU1-1, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1-1, network ] }
---
"""
res = copy.deepcopy(vdu_template)
for prop_value in res.get('properties', {}).values():
get_param = prop_value.get('get_param')
if (get_param is not None and
isinstance(get_param, list) and len(get_param) >= 4):
get_param[2] = add_idx(get_param[2], vdu_idx)
return res
class StandardUserData(userdata_utils.AbstractUserData):
@staticmethod
def instantiate(req, inst, grant_req, grant, 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']
# first modify VDU resources
poped_vdu = {}
vdu_idxes = {}
for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
vdu_idxes[vdu_name] = 0
for res in grant_req['addResources']:
if res['type'] != 'COMPUTE':
continue
vdu_name = res['resourceTemplateId']
if vdu_name not in poped_vdu:
continue
vdu_idx = vdu_idxes[vdu_name]
vdu_idxes[vdu_name] += 1
res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx)
top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
vdu_name = rm_idx(vdu_name)
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value['locationConstraints'] = (
common_script_utils.get_param_zone(
vdu_name, grant_req, grant))
cps = nfv_dict.get('CP', {})
for cp_name, cp_value in cps.items():
cp_name = rm_idx(cp_name)
if 'network' in cp_value:
cp_value['network'] = common_script_utils.get_param_network(
cp_name, grant, req)
if 'fixed_ips' in cp_value:
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
if i not in cp_value['fixed_ips']:
break
ips_i = cp_value['fixed_ips'][i]
if 'subnet' in ips_i:
ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
if 'ip_address' in ips_i:
ips_i['ip_address'] = ext_fixed_ips[i].get(
'ip_address')
fixed_ips.append(ips_i)
cp_value['fixed_ips'] = fixed_ips
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,
req['additionalParams']['nfv'])
if 'nfv' in grant.get('additionalParams', {}):
nfv_dict = inst_utils.json_merge_patch(nfv_dict,
grant['additionalParams']['nfv'])
fields = {
'template': yaml.safe_dump(top_hot),
'parameters': {'nfv': nfv_dict},
'files': {}
}
for key, value in hot_dict.get('files', {}).items():
fields['files'][key] = yaml.safe_dump(value)
return fields
@staticmethod
def scale(req, inst, grant_req, grant, tmp_csar_dir):
if req['type'] == 'SCALE_OUT':
return StandardUserData._scale_out(req, inst, grant_req, grant,
tmp_csar_dir)
else:
return StandardUserData._scale_in(req, inst, grant_req, grant,
tmp_csar_dir)
@staticmethod
def _scale_out(req, inst, grant_req, grant, 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']
# first modify VDU resources
poped_vdu = {}
vdu_idxes = {}
for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
vdu_idxes[vdu_name] = common_script_utils.get_current_capacity(
vdu_name, inst)
for res in grant_req['addResources']:
if res['type'] != 'COMPUTE':
continue
vdu_name = res['resourceTemplateId']
if vdu_name not in poped_vdu:
continue
vdu_idx = vdu_idxes[vdu_name]
vdu_idxes[vdu_name] += 1
res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx)
top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
vdu_name = rm_idx(vdu_name)
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value['locationConstraints'] = (
common_script_utils.get_param_zone(
vdu_name, grant_req, grant))
cps = nfv_dict.get('CP', {})
for cp_name, cp_value in cps.items():
cp_name = rm_idx(cp_name)
if 'network' in cp_value:
cp_value['network'] = (
common_script_utils.get_param_network_from_inst(
cp_name, inst))
if 'fixed_ips' in cp_value:
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']:
break
ips_i = cp_value['fixed_ips'][i]
if 'subnet' in ips_i:
ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
if 'ip_address' in ips_i:
ips_i['ip_address'] = ext_fixed_ips[i].get(
'ip_address')
fixed_ips.append(ips_i)
cp_value['fixed_ips'] = fixed_ips
fields = {
'template': yaml.safe_dump(top_hot),
'parameters': {'nfv': nfv_dict}
}
return fields
@staticmethod
def _scale_in(req, inst, grant_req, grant, tmp_csar_dir):
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
template = {'resources': {}}
vdus = {}
cps = {}
for res in grant_req['removeResources']:
if res['type'] != 'COMPUTE':
continue
for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
if (inst_vnfc['computeResource']['resourceId'] ==
res['resource']['resourceId']):
# must be found
vdu_idx = inst_vnfc['metadata']['vdu_idx']
break
vdu_name = res['resourceTemplateId']
template['resources'][add_idx(vdu_name, vdu_idx)] = None
vdus[add_idx(vdu_name, vdu_idx)] = None
for str_name in vnfd.get_vdu_storages(vdu_nodes[vdu_name]):
# may overspec, but no problem
vdus[add_idx(str_name, vdu_idx)] = None
for cp_name in vnfd.get_vdu_cps(flavour_id, vdu_name):
# may overspec, but no problem
cps[add_idx(cp_name, vdu_idx)] = None
fields = {
'template': yaml.safe_dump(template),
'parameters': {'nfv': {'VDU': vdus, 'CP': cps}}
}
return fields
@staticmethod
def scale_rollback(req, inst, grant_req, grant, tmp_csar_dir):
vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
vdu_idxes = {}
for vdu_name in vdu_nodes.keys():
vdu_idxes[vdu_name] = common_script_utils.get_current_capacity(
vdu_name, inst)
template = {'resources': {}}
vdus = {}
cps = {}
for res in grant_req['addResources']:
if res['type'] != 'COMPUTE':
continue
vdu_name = res['resourceTemplateId']
vdu_idx = vdu_idxes[vdu_name]
vdu_idxes[vdu_name] += 1
template['resources'][add_idx(vdu_name, vdu_idx)] = None
vdus[add_idx(vdu_name, vdu_idx)] = None
for str_name in vnfd.get_vdu_storages(vdu_nodes[vdu_name]):
# may overspec, but no problem
vdus[add_idx(str_name, vdu_idx)] = None
for cp_name in vnfd.get_vdu_cps(flavour_id, vdu_name):
# may overspec, but no problem
cps[add_idx(cp_name, vdu_idx)] = None
fields = {
'template': yaml.safe_dump(template),
'parameters': {'nfv': {'VDU': vdus, 'CP': cps}}
}
return fields
@staticmethod
def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
# change_ext_conn is interested in 'CP' only.
# This method returns only 'CP' part in the 'nfv' dict from
# ChangeExtVnfConnectivityRequest.
# It is applied to json merge patch against the existing 'nfv'
# dict by the caller.
# NOTE: complete 'nfv' dict can not be made at the moment
# since InstantiateVnfRequest is necessary to make it.
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']
# first modify VDU resources
poped_vdu = {}
for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
for inst_vnfc in inst['instantiatedVnfInfo'].get(
'vnfcResourceInfo', []):
vdu_idx = inst_vnfc['metadata'].get('vdu_idx')
if vdu_idx is None:
continue
vdu_name = inst_vnfc['vduId']
res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx)
top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
cps = nfv_dict.get('CP', {})
new_cps = {}
for cp_name_idx, cp_value in cps.items():
cp_name = rm_idx(cp_name_idx)
if 'network' in cp_value:
network = common_script_utils.get_param_network(
cp_name, grant, req)
if network is None:
continue
new_cps.setdefault(cp_name_idx, {})
new_cps[cp_name_idx]['network'] = network
if 'fixed_ips' in cp_value:
ext_fixed_ips = common_script_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
if i not in cp_value['fixed_ips']:
break
ips_i = cp_value['fixed_ips'][i]
if 'subnet' in ips_i:
ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
if 'ip_address' in ips_i:
ips_i['ip_address'] = ext_fixed_ips[i].get(
'ip_address')
fixed_ips.append(ips_i)
new_cps.setdefault(cp_name_idx, {})
new_cps[cp_name_idx]['fixed_ips'] = fixed_ips
fields = {'parameters': {'nfv': {'CP': new_cps}}}
return fields
@staticmethod
def change_ext_conn_rollback(req, inst, grant_req, grant, 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']
# first modify VDU resources
poped_vdu = {}
for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
for inst_vnfc in inst['instantiatedVnfInfo'].get(
'vnfcResourceInfo', []):
vdu_idx = inst_vnfc['metadata'].get('vdu_idx')
if vdu_idx is None:
continue
vdu_name = inst_vnfc['vduId']
res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx)
top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
cps = nfv_dict.get('CP', {})
new_cps = {}
for cp_name_idx, cp_value in cps.items():
cp_name = rm_idx(cp_name_idx)
if 'network' in cp_value:
network = common_script_utils.get_param_network_from_inst(
cp_name, inst)
if network is None:
continue
new_cps.setdefault(cp_name_idx, {})
new_cps[cp_name_idx]['network'] = network
if 'fixed_ips' in cp_value:
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']:
break
ips_i = cp_value['fixed_ips'][i]
if 'subnet' in ips_i:
ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
if 'ip_address' in ips_i:
ips_i['ip_address'] = ext_fixed_ips[i].get(
'ip_address')
fixed_ips.append(ips_i)
new_cps.setdefault(cp_name_idx, {})
new_cps[cp_name_idx]['fixed_ips'] = fixed_ips
fields = {'parameters': {'nfv': {'CP': new_cps}}}
return fields
@staticmethod
def heal(req, inst, grant_req, grant, tmp_csar_dir):
# It is not necessary to change parameters at heal basically.
fields = {'parameters': {'nfv': {}}}
return fields
@staticmethod
def change_vnfpkg(req, inst, grant_req, grant, tmp_csar_dir):
vnfd = common_script_utils.get_vnfd(grant_req['dstVnfdId'],
tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
# first modify VDU resources
poped_vdu = {}
for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
for res in grant_req['removeResources']:
if res['type'] != 'COMPUTE':
continue
for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
if (inst_vnfc['computeResource']['resourceId'] ==
res['resource']['resourceId']):
# must be found
vdu_idx = inst_vnfc['metadata']['vdu_idx']
break
vdu_name = res['resourceTemplateId']
res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx)
top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
vdus = nfv_dict.get('VDU', {})
for vdu_name, vdu_value in vdus.items():
vdu_name = rm_idx(vdu_name)
if 'computeFlavourId' in vdu_value:
vdu_value['computeFlavourId'] = (
common_script_utils.get_param_flavor(
vdu_name, flavour_id, vnfd, grant))
if 'vcImageId' in vdu_value:
vdu_value['vcImageId'] = common_script_utils.get_param_image(
vdu_name, flavour_id, vnfd, grant)
if 'locationConstraints' in vdu_value:
vdu_value.pop('locationConstraints')
fields = {
'parameters': {'nfv': {'VDU': vdus}}
}
return fields
@staticmethod
def change_vnfpkg_rollback(req, inst, grant_req, grant, 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']
# first modify VDU resources
poped_vdu = {}
for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
for res in grant_req['removeResources']:
if res['type'] != 'COMPUTE':
continue
for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
if (inst_vnfc['computeResource']['resourceId'] ==
res['resource']['resourceId']):
# must be found
vdu_idx = inst_vnfc['metadata']['vdu_idx']
break
vdu_name = res['resourceTemplateId']
res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx)
top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res
nfv_dict = common_script_utils.init_nfv_dict(top_hot)
images = {}
flavors = {}
for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
vdu_idx = inst_vnfc['metadata'].get('vdu_idx')
if vdu_idx is None:
continue
vdu_name = inst_vnfc['vduId']
if vdu_name in flavors:
continue
for key, value in inst_vnfc['metadata'].items():
if key == 'flavor':
flavors[add_idx(vdu_name, vdu_idx)] = value
elif key.startswith('image-'):
image_vdu = key.replace('image-', '')
images[image_vdu] = value
vdus = nfv_dict.get('VDU', {})
new_vdus = {}
for vdu_name, vdu_value in vdus.items():
if 'computeFlavourId' in vdu_value:
new_vdus.setdefault(vdu_name, {})
new_vdus[vdu_name]['computeFlavourId'] = flavors.get(vdu_name)
if 'vcImageId' in vdu_value:
new_vdus.setdefault(vdu_name, {})
new_vdus[vdu_name]['vcImageId'] = images.get(vdu_name)
fields = {
'parameters': {'nfv': {'VDU': new_vdus}}
}
return fields

View File

@ -44,12 +44,32 @@ class AbstractUserData(metaclass=abc.ABCMeta):
def scale(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def scale_rollback(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def change_ext_conn_rollback(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def heal(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def change_vnfpkg(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def change_vnfpkg_rollback(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()

View File

@ -0,0 +1,433 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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 time
from tacker.tests.functional.sol_v2_common import paramgen
from tacker.tests.functional.sol_v2_common import test_vnflcm_basic_common
class IndividualVnfcMgmtTest(test_vnflcm_basic_common.CommonVnfLcmTest):
@classmethod
def setUpClass(cls):
super(IndividualVnfcMgmtTest, cls).setUpClass()
cur_dir = os.path.dirname(__file__)
# tacker/tests/functional/sol_v2(here)
# /etc
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))
# tacker/tests/functional/sol_v2(here)
# /sol_refactored
userdata_dir = os.path.join(
cur_dir, "../../../sol_refactored/infra_drivers/openstack")
userdata_file = "userdata_standard.py"
userdata_path = os.path.abspath(
os.path.join(userdata_dir, userdata_file))
# main vnf package for StandardUserData test
pkg_path_1 = os.path.join(cur_dir,
"../sol_v2_common/samples/userdata_standard")
cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package(
pkg_path_1, image_path=image_path, userdata_path=userdata_path)
# for change_vnfpkg test
pkg_path_2 = os.path.join(cur_dir,
"../sol_v2_common/samples/userdata_standard_change_vnfpkg")
cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package(
pkg_path_2, image_path=image_path, userdata_path=userdata_path)
@classmethod
def tearDownClass(cls):
super(IndividualVnfcMgmtTest, cls).tearDownClass()
cls.delete_vnf_package(cls.vnf_pkg_1)
cls.delete_vnf_package(cls.vnf_pkg_2)
def setUp(self):
super().setUp()
def _put_fail_file(self, operation):
with open(f'/tmp/{operation}', 'w'):
pass
def _rm_fail_file(self, operation):
os.remove(f'/tmp/{operation}')
def _get_vdu_indexes(self, inst, vdu):
return {
vnfc['metadata'].get('vdu_idx')
for vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']
if vnfc['vduId'] == vdu
}
def _get_vnfc_by_vdu_index(self, inst, vdu, index):
for vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
if (vnfc['vduId'] == vdu and
vnfc['metadata'].get('vdu_idx') == index):
return vnfc
def _get_vnfc_id(self, inst, vdu, index):
vnfc = self._get_vnfc_by_vdu_index(inst, vdu, index)
return vnfc['id']
def _get_vnfc_cp_net_id(self, inst, vdu, index, cp):
vnfc = self._get_vnfc_by_vdu_index(inst, vdu, index)
for cp_info in vnfc['vnfcCpInfo']:
if cp_info['cpdId'] == cp:
# must be found
ext_cp_id = cp_info['vnfExtCpId']
break
for ext_vl in inst['instantiatedVnfInfo']['extVirtualLinkInfo']:
for port in ext_vl['extLinkPorts']:
if port['cpInstanceId'] == ext_cp_id:
# must be found
return ext_vl['resourceHandle']['resourceId']
def _get_vnfc_image(self, inst, vdu, index):
vnfc = self._get_vnfc_by_vdu_index(inst, vdu, index)
for key, value in vnfc['metadata'].items():
if key.startswith('image-'):
# must be found
return value
def _delete_instance(self, inst_id):
for _ in range(3):
resp, body = self.delete_vnf_instance(inst_id)
if resp.status_code == 204: # OK
return
elif resp.status_code == 409:
# may happen. there is a bit time between lcmocc become
# COMPLETED and lock of terminate is freed.
time.sleep(3)
else:
break
self.assertTrue(False)
def test_basic_operations(self):
"""Test basic operations using StandardUserData
* Note:
This test focuses whether StandardUserData works well.
This test does not check overall items of APIs at all.
* About LCM operations:
This test includes the following operations.
- Create VNF instance
- 1. Instantiate VNF instance
- Show VNF instance / check
- 2. Scale out operation
- Show VNF instance / check
- 3. Heal operation
- Show VNF instance / check
- 4. Scale in operation
- Show VNF instance / check
- 5. Change_ext_conn operation
- Show VNF instance / check
- 6. Change_vnfpkg operation
- Show VNF instance / check
- Terminate VNF instance
- Delete VNF instance
"""
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
# Create VNF instance
create_req = paramgen.sample3_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# 1. Instantiate VNF instance
instantiate_req = paramgen.sample3_instantiate(
net_ids, subnet_ids, self.auth_url)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_1 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check number of VDUs and indexes
self.assertEqual({0}, self._get_vdu_indexes(inst_1, 'VDU1'))
self.assertEqual({0}, self._get_vdu_indexes(inst_1, 'VDU2'))
# 2. Scale out operation
scale_out_req = paramgen.sample3_scale_out()
resp, body = self.scale_vnf_instance(inst_id, scale_out_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_2 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check number of VDUs and indexes
self.assertEqual({0, 1, 2}, self._get_vdu_indexes(inst_2, 'VDU1'))
self.assertEqual({0}, self._get_vdu_indexes(inst_2, 'VDU2'))
# 3. Heal operation
heal_req = paramgen.sample3_heal()
# pick up VDU1-1 to heal
vnfc_id = self._get_vnfc_id(inst_2, 'VDU1', 1)
heal_req['vnfcInstanceId'] = [f'VDU1-{vnfc_id}']
resp, body = self.heal_vnf_instance(inst_id, heal_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_3 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check id of VDU1-1 is changed and other are not.
self.assertEqual(self._get_vnfc_id(inst_2, 'VDU1', 0),
self._get_vnfc_id(inst_3, 'VDU1', 0))
self.assertNotEqual(self._get_vnfc_id(inst_2, 'VDU1', 1),
self._get_vnfc_id(inst_3, 'VDU1', 1))
self.assertEqual(self._get_vnfc_id(inst_2, 'VDU1', 2),
self._get_vnfc_id(inst_3, 'VDU1', 2))
self.assertEqual(self._get_vnfc_id(inst_2, 'VDU2', 0),
self._get_vnfc_id(inst_3, 'VDU2', 0))
# 4. Scale in operation
scale_in_req = paramgen.sample3_scale_in()
resp, body = self.scale_vnf_instance(inst_id, scale_in_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_4 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check VDU1-2 is removed. other are not changed.
self.assertEqual({0, 1}, self._get_vdu_indexes(inst_4, 'VDU1'))
self.assertEqual({0}, self._get_vdu_indexes(inst_4, 'VDU2'))
self.assertEqual(self._get_vnfc_id(inst_3, 'VDU1', 0),
self._get_vnfc_id(inst_4, 'VDU1', 0))
self.assertEqual(self._get_vnfc_id(inst_3, 'VDU1', 1),
self._get_vnfc_id(inst_4, 'VDU1', 1))
self.assertEqual(self._get_vnfc_id(inst_3, 'VDU2', 0),
self._get_vnfc_id(inst_4, 'VDU2', 0))
# 5. Change_ext_conn operation
change_ext_conn_req = paramgen.sample3_change_ext_conn(net_ids)
resp, body = self.change_ext_conn(inst_id, change_ext_conn_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_5 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check VDU1_CP1 is changed to net0 from net1. other are not changed.
self.assertEqual(net_ids['net1'],
self._get_vnfc_cp_net_id(inst_4, 'VDU1', 0, 'VDU1_CP1'))
self.assertEqual(net_ids['net1'],
self._get_vnfc_cp_net_id(inst_4, 'VDU1', 1, 'VDU1_CP1'))
self.assertEqual(net_ids['net1'],
self._get_vnfc_cp_net_id(inst_4, 'VDU2', 0, 'VDU2_CP1'))
self.assertEqual(net_ids['net0'],
self._get_vnfc_cp_net_id(inst_5, 'VDU1', 0, 'VDU1_CP1'))
self.assertEqual(net_ids['net0'],
self._get_vnfc_cp_net_id(inst_5, 'VDU1', 1, 'VDU1_CP1'))
self.assertEqual(net_ids['net1'],
self._get_vnfc_cp_net_id(inst_5, 'VDU2', 0, 'VDU2_CP1'))
# 6. Change_vnfpkg operation
change_vnfpkg_req = paramgen.sample4_change_vnfpkg(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_6 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check vnfdId is changed
self.assertEqual(self.vnfd_id_2, inst_6['vnfdId'])
# check images are changed
self.assertNotEqual(self._get_vnfc_image(inst_5, 'VDU1', 0),
self._get_vnfc_image(inst_6, 'VDU1', 0))
self.assertNotEqual(self._get_vnfc_image(inst_5, 'VDU1', 1),
self._get_vnfc_image(inst_6, 'VDU1', 1))
self.assertNotEqual(self._get_vnfc_image(inst_5, 'VDU2', 0),
self._get_vnfc_image(inst_6, 'VDU2', 0))
# Terminate VNF instance
terminate_req = paramgen.sample4_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Delete VNF instance
self._delete_instance(inst_id)
def test_rollback_operations(self):
"""Test rollback operations using StandardUserData
* Note:
This test focuses whether StandardUserData works well.
This test does not check overall items of APIs at all.
* About LCM operations:
This test includes the following operations.
- Create VNF instance
- Instantiate VNF instance
- Show VNF instance
- 1. Scale out operation => FAILED_TEMP
- Rollback
- Show VNF instance / check
- 2. Change_ext_conn operation => FAILED_TEMP
- Rollback
- Show VNF instance / check
- 3. Change_vnfpkg operation => FAILED_TEMP
- Rollback
- Show VNF instance / check
- Terminate VNF instance
- Delete VNF instance
"""
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1'])
# Create VNF instance
create_req = paramgen.sample3_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# Instantiate VNF instance
instantiate_req = paramgen.sample3_instantiate(
net_ids, subnet_ids, self.auth_url)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Show VNF instance
resp, inst_0 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# 1. Scale out operation
self._put_fail_file('scale_end')
scale_out_req = paramgen.sample3_scale_out()
resp, body = self.scale_vnf_instance(inst_id, scale_out_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
self._rm_fail_file('scale_end')
# Rollback
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
# Show VNF instance
resp, inst_1 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check number of vnfc and its id are not changed.
self.assertEqual(self._get_vdu_indexes(inst_0, 'VDU1'),
self._get_vdu_indexes(inst_1, 'VDU1'))
self.assertEqual(self._get_vdu_indexes(inst_0, 'VDU2'),
self._get_vdu_indexes(inst_1, 'VDU2'))
self.assertEqual(self._get_vnfc_id(inst_0, 'VDU1', 0),
self._get_vnfc_id(inst_1, 'VDU1', 0))
self.assertEqual(self._get_vnfc_id(inst_0, 'VDU2', 0),
self._get_vnfc_id(inst_1, 'VDU2', 0))
# 2. Change_ext_conn operation
self._put_fail_file('change_external_connectivity_end')
change_ext_conn_req = paramgen.sample3_change_ext_conn(net_ids)
resp, body = self.change_ext_conn(inst_id, change_ext_conn_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
self._rm_fail_file('change_external_connectivity_end')
# Rollback
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
# Show VNF instance
resp, inst_2 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check network of extVL cps are not changed. (i.e. net1)
self.assertEqual(net_ids['net1'],
self._get_vnfc_cp_net_id(inst_2, 'VDU1', 0, 'VDU1_CP1'))
self.assertEqual(net_ids['net1'],
self._get_vnfc_cp_net_id(inst_2, 'VDU2', 0, 'VDU2_CP1'))
# 3. Change_vnfpkg operation
self._put_fail_file('change_vnfpkg')
change_vnfpkg_req = paramgen.sample4_change_vnfpkg(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
self._rm_fail_file('change_vnfpkg')
# Rollback
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
# Show VNF instance
resp, inst_3 = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# check vnfdId is not changed
self.assertEqual(self.vnfd_id_1, inst_3['vnfdId'])
# check images are not changed
self.assertEqual(self._get_vnfc_image(inst_2, 'VDU1', 0),
self._get_vnfc_image(inst_3, 'VDU1', 0))
self.assertEqual(self._get_vnfc_image(inst_2, 'VDU2', 0),
self._get_vnfc_image(inst_3, 'VDU2', 0))
# Terminate VNF instance
terminate_req = paramgen.sample3_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# Delete VNF instance
self._delete_instance(inst_id)

View File

@ -107,11 +107,12 @@ class BaseSolV2Test(base.BaseTestCase):
@classmethod
def create_vnf_package(cls, sample_path, user_data={},
image_path=None, nfvo=False):
image_path=None, nfvo=False, userdata_path=None):
vnfd_id = uuidutils.generate_uuid()
tmp_dir = tempfile.mkdtemp()
utils.make_zip(sample_path, tmp_dir, vnfd_id, image_path)
utils.make_zip(sample_path, tmp_dir, vnfd_id, image_path,
userdata_path)
zip_file_name = os.path.basename(os.path.abspath(sample_path)) + ".zip"
zip_file_path = os.path.join(tmp_dir, zip_file_name)

View File

@ -933,3 +933,197 @@ def change_vnfpkg(vnfd_id):
}]
}
}
# sample3 is used for tests of StandardUserData
#
def sample3_create(vnfd_id):
return {
"vnfdId": vnfd_id,
"vnfInstanceName": "sample3",
"vnfInstanceDescription": "test for StandardUserData"
}
def sample3_terminate():
return {
"terminationType": "FORCEFUL"
}
def sample3_instantiate(net_ids, subnet_ids, auth_url):
ext_vl_1 = {
"id": uuidutils.generate_uuid(),
"resourceId": net_ids['net1'],
"extCps": [
{
"cpdId": "VDU1_CP1",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"numDynamicAddresses": 1}]}}]}
}
},
{
"cpdId": "VDU2_CP1",
"cpConfig": {
"VDU2_CP2_1": {
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"numDynamicAddresses": 1,
"subnetId": subnet_ids['subnet1']}]}}]}
}
}
]
}
return {
"flavourId": "simple",
"instantiationLevelId": "instantiation_level_1",
"extVirtualLinks": [ext_vl_1],
"extManagedVirtualLinks": [
{
"id": uuidutils.generate_uuid(),
"vnfVirtualLinkDescId": "internalVL1",
"resourceId": net_ids['net_mgmt']
},
],
"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"
}
}
},
"additionalParams": {
"lcm-operation-user-data": "./UserData/userdata_standard.py",
"lcm-operation-user-data-class": "StandardUserData"
}
}
def sample3_scale_out():
return {
"type": "SCALE_OUT",
"aspectId": "VDU1_scale",
"numberOfSteps": 2,
"additionalParams": {
"lcm-operation-user-data": "./UserData/userdata_standard.py",
"lcm-operation-user-data-class": "StandardUserData"
}
}
def sample3_scale_in():
return {
"type": "SCALE_IN",
"aspectId": "VDU1_scale",
"numberOfSteps": 1,
"additionalParams": {
"lcm-operation-user-data": "./UserData/userdata_standard.py",
"lcm-operation-user-data-class": "StandardUserData"
}
}
def sample3_heal():
return {
"vnfcInstanceId": [], # should be filled
"additionalParams": {
"lcm-operation-user-data": "./UserData/userdata_standard.py",
"lcm-operation-user-data-class": "StandardUserData"
}
}
def sample3_change_ext_conn(net_ids):
return {
"extVirtualLinks": [
{
"id": uuidutils.generate_uuid(),
"resourceId": net_ids['net0'],
"extCps": [
{
"cpdId": "VDU1_CP1",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"numDynamicAddresses": 1}]}}]}
}
}
]
}
],
"additionalParams": {
"lcm-operation-user-data": "./UserData/userdata_standard.py",
"lcm-operation-user-data-class": "StandardUserData"
}
}
# sample4 is for change_vnfpkg test of StandardUserData
#
def sample4_change_vnfpkg(vnfd_id):
return {
"vnfdId": vnfd_id,
"additionalParams": {
"upgrade_type": "RollingUpdate",
"lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_vnf.py",
"lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_vnf.py",
"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"
}
}
],
"lcm-operation-user-data": "./UserData/userdata_standard.py",
"lcm-operation-user-data-class": "StandardUserData"
}
}
def sample4_terminate():
return {
"terminationType": "FORCEFUL"
}

View File

@ -0,0 +1,53 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image-VDU1:
type: string
net1:
type: string
net2:
type: string
net3:
type: string
affinity:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image-VDU1 }
networks:
- port:
get_resource: VDU1_CP1
# replace the following line to Port ID when extmanagedVLs' Ports are
# specified in instantiatevnfrequest
- port:
get_resource: VDU1_CP2
- port:
get_resource: VDU1_CP3
scheduler_hints:
group: { get_param: affinity }
# extVL without FixedIP or with numDynamicAddresses
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
# CPs of internal VLs are deleted when extmanagedVLs and port are
# specified in instantiatevnfrequest
VDU1_CP2:
type: OS::Neutron::Port
properties:
network: { get_param: net2 }
VDU1_CP3:
type: OS::Neutron::Port
properties:
network: { get_param: net3 }

View File

@ -0,0 +1,69 @@
heat_template_version: 2013-05-23
description: 'VDU2 HOT for Sample VNF'
parameters:
flavor:
type: string
image-VDU2-VirtualStorage:
type: string
net1:
type: string
net2:
type: string
net3:
type: string
subnet1:
type: string
affinity:
type: string
resources:
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU2
block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
networks:
- port:
get_resource: VDU2_CP1
# replace the following line to Port ID when extmanagedVLs' Ports are
# specified in instantiatevnfrequest
- port:
get_resource: VDU2_CP2
- port:
get_resource: VDU2_CP3
scheduler_hints:
group: {get_param: affinity }
VDU2-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: image-VDU2-VirtualStorage }
size: 1
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: VDU2-multi
metadata: { multiattach: "<is> True" }
# extVL with numDynamicAddresses and subnet
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
fixed_ips:
- subnet: { get_param: subnet1 }
# CPs of internal VLs are deleted when extmanagedVLs and port are
# specified in instantiatevnfrequest
VDU2_CP2:
type: OS::Neutron::Port
properties:
network: { get_param: net2 }
VDU2_CP3:
type: OS::Neutron::Port
properties:
network: { get_param: net3 }

View File

@ -0,0 +1,57 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1:
type: VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network ] }
net2: { get_resource: internalVL1 }
net3: { get_resource: internalVL2 }
affinity: { get_resource: nfvi_node_affinity }
VDU2:
type: VDU2.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image-VDU2-VirtualStorage: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU2_CP1, network ] }
subnet1: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, subnet ]}
net2: { get_resource: internalVL1 }
net3: { get_resource: internalVL2 }
affinity: { get_resource: nfvi_node_affinity }
# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest
internalVL1:
type: OS::Neutron::Net
internalVL2:
type: OS::Neutron::Net
internalVL1_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: internalVL1
cidr: 192.168.3.0/24
internalVL2_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: internalVL2
cidr: 192.168.4.0/24
nfvi_node_affinity:
type: OS::Nova::ServerGroup
properties:
name: nfvi_node_affinity
policies: [ 'affinity' ]
outputs: {}

View File

@ -0,0 +1,357 @@
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
- v2_sample3_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_start:
implementation: sample-script
instantiate_end:
implementation: sample-script
terminate_start:
implementation: sample-script
terminate_end:
implementation: sample-script
scale_start:
implementation: sample-script
scale_end:
implementation: sample-script
heal_start:
implementation: sample-script
heal_end:
implementation: sample-script
change_external_connectivity_start:
implementation: sample-script
change_external_connectivity_end:
implementation: sample-script
modify_information_start:
implementation: sample-script
modify_information_end:
implementation: sample-script
artifacts:
sample-script:
description: Sample script
type: tosca.artifacts.Implementation.Python
file: ../Scripts/sample_script.py
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: 12 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: 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
VDU1_CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL1
VDU1_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL2
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
VDU2_CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL1
VDU2_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL2
internalVL1:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: External Managed Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 192.168.3.0/24
internalVL2:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: External Managed Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 192.168.4.0/24
groups:
affinityOrAntiAffinityGroup1:
type: tosca.groups.nfv.PlacementGroup
members: [ VDU1, 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: 1
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: 1
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: 2
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 ]
- internalVL1_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL1 ]
- internalVL2_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL2 ]
- policy_antiaffinity_group:
type: tosca.policies.nfv.AntiAffinityRule
targets: [ affinityOrAntiAffinityGroup1 ]
properties:
scope: nfvi_node

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
- v2_sample3_types.yaml
- v2_sample3_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,55 @@
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
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,46 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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
class FailScript(object):
def __init__(self, vnfc_param):
self.vnfc_param = vnfc_param
def run(self):
operation = 'change_vnfpkg'
if self.vnfc_param['is_rollback']:
operation += '_rollback'
if os.path.exists(f'/tmp/{operation}'):
raise Exception(f'test {operation} error')
def main():
vnfc_param = pickle.load(sys.stdin.buffer)
script = FailScript(vnfc_param)
script.run()
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,68 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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 functools
import os
import pickle
import sys
class FailScript(object):
"""Define error method for each operation
For example:
def instantiate_start(self):
if os.path.exists('/tmp/instantiate_start')
raise Exception('test instantiate_start error')
"""
def __init__(self, req, inst, grant_req, grant, csar_dir):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
def _fail(self, method):
if os.path.exists(f'/tmp/{method}'):
raise Exception(f'test {method} error')
def __getattr__(self, name):
return functools.partial(self._fail, name)
def main():
script_dict = pickle.load(sys.stdin.buffer)
operation = script_dict['operation']
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']
script = FailScript(req, inst, grant_req, grant, csar_dir)
getattr(script, operation)()
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/v2_sample3_top.vnfd.yaml

View File

@ -0,0 +1,83 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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_common import paramgen
from tacker.tests.functional.sol_v2_common import utils
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
tmp_dir = tempfile.mkdtemp()
vnfd_id = uuidutils.generate_uuid()
# tacker/tests/etc...
# /functional/sol_v2_common/samples/smapleX
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)
# tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py
# /tests/functional/sol_v2_common/samples/smapleX
userdata_dir = "../../../../../sol_refactored/infra_drivers/openstack/"
userdata_file = "userdata_standard.py"
userdata_path = os.path.abspath(userdata_dir + userdata_file)
utils.make_zip(".", tmp_dir, vnfd_id, image_path=image_path,
userdata_path=userdata_path)
shutil.copy(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir)
create_req = paramgen.sample3_create(vnfd_id)
terminate_req = paramgen.sample3_terminate()
net_ids = utils.get_network_ids(['net0', 'net1', 'net_mgmt'])
subnet_ids = utils.get_subnet_ids(['subnet0', 'subnet1'])
instantiate_req = paramgen.sample3_instantiate(
net_ids, subnet_ids, "http://localhost/identity/v3")
scale_out_req = paramgen.sample3_scale_out()
scale_in_req = paramgen.sample3_scale_in()
heal_req = paramgen.sample3_heal()
change_ext_conn_req = paramgen.sample3_change_ext_conn(net_ids)
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", "w") as f:
f.write(json.dumps(instantiate_req, indent=2))
with open("scale_out_req", "w") as f:
f.write(json.dumps(scale_out_req, indent=2))
with open("scale_in_req", "w") as f:
f.write(json.dumps(scale_in_req, indent=2))
# NOTE: vnfcInstanceId should be filled by hand
with open("heal_req", "w") as f:
f.write(json.dumps(heal_req, indent=2))
with open("change_ext_conn_req", "w") as f:
f.write(json.dumps(change_ext_conn_req, indent=2))

View File

@ -0,0 +1,53 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image-VDU1:
type: string
net1:
type: string
net2:
type: string
net3:
type: string
affinity:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image-VDU1 }
networks:
- port:
get_resource: VDU1_CP1
# replace the following line to Port ID when extmanagedVLs' Ports are
# specified in instantiatevnfrequest
- port:
get_resource: VDU1_CP2
- port:
get_resource: VDU1_CP3
scheduler_hints:
group: { get_param: affinity }
# extVL without FixedIP or with numDynamicAddresses
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
# CPs of internal VLs are deleted when extmanagedVLs and port are
# specified in instantiatevnfrequest
VDU1_CP2:
type: OS::Neutron::Port
properties:
network: { get_param: net2 }
VDU1_CP3:
type: OS::Neutron::Port
properties:
network: { get_param: net3 }

View File

@ -0,0 +1,69 @@
heat_template_version: 2013-05-23
description: 'VDU2 HOT for Sample VNF'
parameters:
flavor:
type: string
image-VDU2-VirtualStorage:
type: string
net1:
type: string
net2:
type: string
net3:
type: string
subnet1:
type: string
affinity:
type: string
resources:
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU2
block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
networks:
- port:
get_resource: VDU2_CP1
# replace the following line to Port ID when extmanagedVLs' Ports are
# specified in instantiatevnfrequest
- port:
get_resource: VDU2_CP2
- port:
get_resource: VDU2_CP3
scheduler_hints:
group: {get_param: affinity }
VDU2-VirtualStorage:
type: OS::Cinder::Volume
properties:
image: { get_param: image-VDU2-VirtualStorage }
size: 1
volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: VDU2-multi
metadata: { multiattach: "<is> True" }
# extVL with numDynamicAddresses and subnet
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
fixed_ips:
- subnet: { get_param: subnet1 }
# CPs of internal VLs are deleted when extmanagedVLs and port are
# specified in instantiatevnfrequest
VDU2_CP2:
type: OS::Neutron::Port
properties:
network: { get_param: net2 }
VDU2_CP3:
type: OS::Neutron::Port
properties:
network: { get_param: net3 }

View File

@ -0,0 +1,57 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1:
type: VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network ] }
net2: { get_resource: internalVL1 }
net3: { get_resource: internalVL2 }
affinity: { get_resource: nfvi_node_affinity }
VDU2:
type: VDU2.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image-VDU2-VirtualStorage: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] }
net1: { get_param: [ nfv, CP, VDU2_CP1, network ] }
subnet1: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, subnet ]}
net2: { get_resource: internalVL1 }
net3: { get_resource: internalVL2 }
affinity: { get_resource: nfvi_node_affinity }
# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest
internalVL1:
type: OS::Neutron::Net
internalVL2:
type: OS::Neutron::Net
internalVL1_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: internalVL1
cidr: 192.168.3.0/24
internalVL2_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: internalVL2
cidr: 192.168.4.0/24
nfvi_node_affinity:
type: OS::Nova::ServerGroup
properties:
name: nfvi_node_affinity
policies: [ 'affinity' ]
outputs: {}

View File

@ -0,0 +1,357 @@
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
- v2_sample4_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_start:
implementation: sample-script
instantiate_end:
implementation: sample-script
terminate_start:
implementation: sample-script
terminate_end:
implementation: sample-script
scale_start:
implementation: sample-script
scale_end:
implementation: sample-script
heal_start:
implementation: sample-script
heal_end:
implementation: sample-script
change_external_connectivity_start:
implementation: sample-script
change_external_connectivity_end:
implementation: sample-script
modify_information_start:
implementation: sample-script
modify_information_end:
implementation: sample-script
artifacts:
sample-script:
description: Sample script
type: tosca.artifacts.Implementation.Python
file: ../Scripts/sample_script.py
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: 12 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
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: 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: 12 GB
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU1_CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL1
VDU1_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL2
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
VDU2_CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL1
VDU2_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL2
internalVL1:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: External Managed Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 192.168.3.0/24
internalVL2:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: External Managed Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 192.168.4.0/24
groups:
affinityOrAntiAffinityGroup1:
type: tosca.groups.nfv.PlacementGroup
members: [ VDU1, 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: 1
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: 1
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: 2
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 ]
- internalVL1_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL1 ]
- internalVL2_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL2 ]
- policy_antiaffinity_group:
type: tosca.policies.nfv.AntiAffinityRule
targets: [ affinityOrAntiAffinityGroup1 ]
properties:
scope: nfvi_node

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
- v2_sample4_types.yaml
- v2_sample4_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,55 @@
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
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,46 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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
class FailScript(object):
def __init__(self, vnfc_param):
self.vnfc_param = vnfc_param
def run(self):
operation = 'change_vnfpkg'
if self.vnfc_param['is_rollback']:
operation += '_rollback'
if os.path.exists(f'/tmp/{operation}'):
raise Exception(f'test {operation} error')
def main():
vnfc_param = pickle.load(sys.stdin.buffer)
script = FailScript(vnfc_param)
script.run()
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,68 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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 functools
import os
import pickle
import sys
class FailScript(object):
"""Define error method for each operation
For example:
def instantiate_start(self):
if os.path.exists('/tmp/instantiate_start')
raise Exception('test instantiate_start error')
"""
def __init__(self, req, inst, grant_req, grant, csar_dir):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
def _fail(self, method):
if os.path.exists(f'/tmp/{method}'):
raise Exception(f'test {method} error')
def __getattr__(self, name):
return functools.partial(self._fail, name)
def main():
script_dict = pickle.load(sys.stdin.buffer)
operation = script_dict['operation']
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']
script = FailScript(req, inst, grant_req, grant, csar_dir)
getattr(script, operation)()
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/v2_sample4_top.vnfd.yaml

View File

@ -0,0 +1,52 @@
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# 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_common import paramgen
from tacker.tests.functional.sol_v2_common import utils
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
tmp_dir = tempfile.mkdtemp()
vnfd_id = uuidutils.generate_uuid()
# tacker/tests/etc...
# /functional/sol_v2_common/samples/sampleX
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)
# tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py
# /tests/functional/sol_v2_common/samples/sampleX
userdata_dir = "../../../../../sol_refactored/infra_drivers/openstack/"
userdata_file = "userdata_standard.py"
userdata_path = os.path.abspath(userdata_dir + userdata_file)
utils.make_zip(".", tmp_dir, vnfd_id, image_path=image_path,
userdata_path=userdata_path)
shutil.copy(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir)
change_vnfpkg_req = paramgen.sample4_change_vnfpkg(vnfd_id)
with open("change_vnfpkg_req", "w") as f:
f.write(json.dumps(change_vnfpkg_req, indent=2))

View File

@ -23,7 +23,8 @@ import subprocess
SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000"
def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None):
def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None,
userdata_path=None):
# NOTE: '.zip' will be added by shutil.make_archive
zip_file_name = os.path.basename(os.path.abspath(sample_dir))
zip_file_path = os.path.join(tmp_dir, zip_file_name)
@ -53,6 +54,12 @@ def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None):
os.makedirs(file_path)
shutil.copy(image_path, file_path)
if userdata_path is not None:
# mkdir UserData/ and copy userdata_path into it
file_path = os.path.join(tmp_contents, "UserData")
os.makedirs(file_path)
shutil.copy(userdata_path, file_path)
shutil.make_archive(zip_file_path, "zip", tmp_contents)