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:
@@ -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.
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
@@ -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()
|
||||
|
433
tacker/tests/functional/sol_v2/test_individual_vnfc_mgmt.py
Normal file
433
tacker/tests/functional/sol_v2/test_individual_vnfc_mgmt.py
Normal 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)
|
@@ -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)
|
||||
|
@@ -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"
|
||||
}
|
||||
|
@@ -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 }
|
@@ -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 }
|
@@ -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: {}
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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)
|
@@ -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)
|
@@ -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
|
@@ -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))
|
@@ -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 }
|
@@ -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 }
|
@@ -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: {}
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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)
|
@@ -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)
|
@@ -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
|
@@ -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))
|
@@ -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)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user