Support individual VNFc management using HOT

Current Tacker's sample Base HOT uses OS::Heat::AutoScalingGroup
for managing VNFc. There are some inconvenient situations that
require rebooting all VMs at the same time.

This patch introduces new userdata class, StandardUserData which
enables individual VNFc management.

Most modifications are closed in userdata class but some fixes
are done in the main route which are originally necessary.

Implements: blueprint individual-vnfc-management
Change-Id: Ib14bcf0c00021f26878fc1960f48cb9f6aeaef43
This commit is contained in:
Itsuro Oda
2022-07-28 04:35:02 +00:00
parent c3d55fcf8f
commit 0403aa5839
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)