CNF v2 API enhance and refactor

This patch enables the following CNF v2 APIs.

 * scale VNF task
 * heal VNF task
 * Rollback operation task for the following APIs.
   - instantiate VNF task
   - scale (SCALE_OUT) VNF task

This patch also refactors k8s infra_driver to make this enhance,
future enhance and maintanace easy.

There are changes about the specification of additionalParams in
ChangeCurrentVnfPkgRequest, although function of change_vnfpkg
API is not changed basically.

Documentation about this enhance will be provided by anther patch.
It will include about specification change of change_vnfpkg too.

Implement: blueprint support-nfv-solv3-scale-vnf
Implement: blueprint support-nfv-solv3-heal-vnf
Implement: blueprint support-nfv-solv3-error-handling
Change-Id: Ifb0fc16c3fb67299763bceb4cc69e36e163531c0
This commit is contained in:
Itsuro Oda
2022-08-30 04:13:57 +00:00
parent 3bdb71c812
commit 736b457dfe
23 changed files with 1754 additions and 3188 deletions

View File

@@ -0,0 +1,11 @@
---
features:
- |
Support CNF Heal/Scale/Rollback operation in v2 LCM API.
This feature provides the following CNF operations in
v2 LCM API based on ETSI NFV specifications and
instantiatiationLevel parameter to determine
the initial number of Pods.
* Scale VNF task
* Heal VNF task
* Rollback operation task (instantiate and scale-out)

View File

@@ -338,3 +338,21 @@ class MalformedRequestBody(SolHttpError400):
class InvalidPagingMarker(SolHttpError400): class InvalidPagingMarker(SolHttpError400):
message = _("Paging marker value %(marker)s is invalid.") message = _("Paging marker value %(marker)s is invalid.")
class K8sOperationFailed(SolHttpError422):
# title and detail are set in the code from kubernetes operation
pass
class K8sOperaitionTimeout(SolHttpError422):
message = _("Kubernetes operation did not complete within"
" the timeout period.")
class K8sResourceNotFound(SolHttpError404):
message = _("Kubernetes resource %(rsc_name)s is not found.")
class K8sInvalidManifestFound(SolHttpError400):
message = _("Invalid manifest found.")

View File

@@ -16,12 +16,9 @@
import os import os
import pickle import pickle
import subprocess import subprocess
from urllib.parse import urlparse
import urllib.request as urllib2
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import uuidutils from oslo_utils import uuidutils
import yaml
from tacker.sol_refactored.common import config from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import exceptions as sol_ex
@@ -396,6 +393,17 @@ class VnfLcmDriverV2(object):
inst.vimConnectionInfo = vim_infos inst.vimConnectionInfo = vim_infos
# vimType dependent parameter check
# NOTE: controller cannot check because vim_info is not identified
# to here, although it is better to check in controller.
if lcmocc.operationState == v2fields.LcmOperationStateType.STARTING:
vim_info = inst_utils.select_vim_info(vim_infos)
if (vim_info.vimType == "kubernetes" and
not req.get('additionalParams', {}).get(
'lcm-kubernetes-def-files')):
raise sol_ex.SolValidationError(
detail="'lcm-kubernetes-def-files' must be specified")
def instantiate_process(self, context, lcmocc, inst, grant_req, def instantiate_process(self, context, lcmocc, inst, grant_req,
grant, vnfd): grant, vnfd):
req = lcmocc.operationParams req = lcmocc.operationParams
@@ -406,7 +414,7 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.instantiate(req, inst, grant_req, grant, vnfd) driver.instantiate(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes': # k8s elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes() driver = kubernetes.Kubernetes()
driver.instantiate(req, inst, grant_req, grant, vnfd) driver.instantiate(req, inst, grant_req, grant, vnfd)
else: else:
@@ -422,8 +430,11 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd) driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes()
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
else: else:
# only support openstack at the moment # should not occur
raise sol_ex.SolException(sol_detail='not support vim type') raise sol_ex.SolException(sol_detail='not support vim type')
def _make_res_def_for_remove_vnfcs(self, inst_info, inst_vnfcs, def _make_res_def_for_remove_vnfcs(self, inst_info, inst_vnfcs,
@@ -561,7 +572,7 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.terminate(req, inst, grant_req, grant, vnfd) driver.terminate(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes': # k8s elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes() driver = kubernetes.Kubernetes()
driver.terminate(req, inst, grant_req, grant, vnfd) driver.terminate(req, inst, grant_req, grant, vnfd)
else: else:
@@ -674,8 +685,11 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.scale(req, inst, grant_req, grant, vnfd) driver.scale(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes()
driver.scale(req, inst, grant_req, grant, vnfd)
else: else:
# only support openstack at the moment # should not occur
raise sol_ex.SolException(sol_detail='not support vim type') raise sol_ex.SolException(sol_detail='not support vim type')
def scale_rollback(self, context, lcmocc, inst, grant_req, def scale_rollback(self, context, lcmocc, inst, grant_req,
@@ -688,8 +702,11 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.scale_rollback(req, inst, grant_req, grant, vnfd) driver.scale_rollback(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes()
driver.scale_rollback(req, inst, grant_req, grant, vnfd)
else: else:
# only support openstack at the moment # should not occur
raise sol_ex.SolException(sol_detail='not support vim type') raise sol_ex.SolException(sol_detail='not support vim type')
def _modify_from_vnfd_prop(self, inst, vnfd_prop, attr): def _modify_from_vnfd_prop(self, inst, vnfd_prop, attr):
@@ -867,8 +884,11 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.heal(req, inst, grant_req, grant, vnfd) driver.heal(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes()
driver.heal(req, inst, grant_req, grant, vnfd)
else: else:
# only support openstack at the moment # should not occur
raise sol_ex.SolException(sol_detail='not support vim type') raise sol_ex.SolException(sol_detail='not support vim type')
def change_ext_conn_grant(self, grant_req, req, inst, vnfd): def change_ext_conn_grant(self, grant_req, req, inst, vnfd):
@@ -992,14 +1012,11 @@ class VnfLcmDriverV2(object):
if not inst_info.obj_attr_is_set('vnfcResourceInfo'): if not inst_info.obj_attr_is_set('vnfcResourceInfo'):
return return
if req.additionalParams.get('vdu_params'): vdu_ids = [vdu_param['vdu_id']
vdu_ids = [vdu_param['vdu_id'] for vdu_param in req.additionalParams['vdu_params']]
for vdu_param in req.additionalParams['vdu_params']] inst_vnfcs = [inst_vnfc
inst_vnfcs = [inst_vnfc for inst_vnfc in inst_info.vnfcResourceInfo
for inst_vnfc in inst_info.vnfcResourceInfo if inst_vnfc.vduId in vdu_ids]
if inst_vnfc.vduId in vdu_ids]
else:
inst_vnfcs = inst_info.vnfcResourceInfo
add_reses = [] add_reses = []
rm_reses = [] rm_reses = []
@@ -1025,70 +1042,6 @@ class VnfLcmDriverV2(object):
if rm_reses: if rm_reses:
grant_req.removeResources = rm_reses grant_req.removeResources = rm_reses
def _pre_check_for_change_vnfpkg(self, context, req, inst, vnfd):
def _get_file_content(file_path):
if ((urlparse(file_path).scheme == 'file') or
(bool(urlparse(file_path).scheme) and
bool(urlparse(file_path).netloc))):
with urllib2.urlopen(file_path) as file_object:
file_content = file_object.read()
else:
with open(file_path, 'rb') as file_object:
file_content = file_object.read()
return file_content
vnf_artifact_files = vnfd.get_vnf_artifact_files()
if req.additionalParams.get('lcm-kubernetes-def-files') is None:
target_k8s_files = inst.metadata.get('lcm-kubernetes-def-files')
else:
target_k8s_files = []
new_file_paths = req.additionalParams.get(
'lcm-kubernetes-def-files')
old_vnfd = self.nfvo_client.get_vnfd(
context=context, vnfd_id=inst.vnfdId, all_contents=False)
old_file_paths = inst.metadata.get('lcm-kubernetes-def-files')
for new_file_path in new_file_paths:
new_file_infos = [
{"kind": content.get('kind'),
"name": content.get('metadata', {}).get('name', '')}
for content in list(yaml.safe_load_all(
_get_file_content(os.path.join(
vnfd.csar_dir, new_file_path))))]
for old_file_path in old_file_paths:
find_flag = False
old_file_infos = [
{"kind": content.get('kind'),
"name": content.get('metadata', {}).get('name', '')}
for content in list(yaml.safe_load_all(
_get_file_content(os.path.join(
old_vnfd.csar_dir, old_file_path))))]
resources = [info for info in old_file_infos
if info in new_file_infos]
if len(resources) != 0:
if len(resources) != len(old_file_infos):
raise sol_ex.UnmatchedFileException(
new_file_path=new_file_path)
if 'Deployment' not in [res.get(
'kind') for res in resources]:
raise sol_ex.UnSupportedKindException(
new_file_path=new_file_path)
old_file_paths.remove(old_file_path)
target_k8s_files.append(new_file_path)
find_flag = True
break
continue
if not find_flag:
raise sol_ex.NotFoundUpdateFileException(
new_file_path=new_file_path)
target_k8s_files.extend(old_file_paths)
if set(target_k8s_files).difference(set(vnf_artifact_files)):
diff_files = ','.join(list(set(
target_k8s_files).difference(set(vnf_artifact_files))))
raise sol_ex.CnfDefinitionNotFound(diff_files=diff_files)
return target_k8s_files
def change_vnfpkg_process( def change_vnfpkg_process(
self, context, lcmocc, inst, grant_req, grant, vnfd): self, context, lcmocc, inst, grant_req, grant, vnfd):
req = lcmocc.operationParams req = lcmocc.operationParams
@@ -1099,14 +1052,9 @@ class VnfLcmDriverV2(object):
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack() driver = openstack.Openstack()
driver.change_vnfpkg(req, inst, grant_req, grant, vnfd) driver.change_vnfpkg(req, inst, grant_req, grant, vnfd)
elif vim_info.vimType == 'kubernetes': # k8s elif vim_info.vimType == 'kubernetes':
target_k8s_files = self._pre_check_for_change_vnfpkg(
context, req, inst, vnfd)
update_req = req.obj_clone()
update_req.additionalParams[
'lcm-kubernetes-def-files'] = target_k8s_files
driver = kubernetes.Kubernetes() driver = kubernetes.Kubernetes()
driver.change_vnfpkg(update_req, inst, grant_req, grant, vnfd) driver.change_vnfpkg(req, inst, grant_req, grant, vnfd)
else: else:
# should not occur # should not occur
raise sol_ex.SolException(sol_detail='not support vim type') raise sol_ex.SolException(sol_detail='not support vim type')
@@ -1121,10 +1069,10 @@ class VnfLcmDriverV2(object):
driver = openstack.Openstack() driver = openstack.Openstack()
driver.change_vnfpkg_rollback( driver.change_vnfpkg_rollback(
req, inst, grant_req, grant, vnfd, lcmocc) req, inst, grant_req, grant, vnfd, lcmocc)
elif vim_info.vimType == 'kubernetes': # k8s elif vim_info.vimType == 'kubernetes':
driver = kubernetes.Kubernetes() driver = kubernetes.Kubernetes()
driver.change_vnfpkg_rollback( driver.change_vnfpkg_rollback(
req, inst, grant_req, grant, vnfd, lcmocc) req, inst, grant_req, grant, vnfd)
else: else:
# should not occur # should not occur
raise sol_ex.SolException(sol_detail='not support vim type') raise sol_ex.SolException(sol_detail='not support vim type')

View File

@@ -447,14 +447,16 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
raise sol_ex.NotSupportUpgradeType(upgrade_type=upgrade_type) raise sol_ex.NotSupportUpgradeType(upgrade_type=upgrade_type)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
vdu_params = additional_params.get('vdu_params') vdu_params = additional_params.get('vdu_params')
if (vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3' and if vdu_params is None:
vdu_params is None):
raise sol_ex.SolValidationError( raise sol_ex.SolValidationError(
detail="'vdu_params' must exist in additionalParams") detail="'vdu_params' must exist in additionalParams")
if vdu_params: self._check_vdu_params(inst, vdu_params, vim_info.vimType,
self._check_vdu_params(inst, vdu_params, vim_info.vimType, additional_params.get('lcm-operation-coordinate-new-vnf'),
additional_params.get('lcm-operation-coordinate-new-vnf'), additional_params.get('lcm-operation-coordinate-old-vnf'))
additional_params.get('lcm-operation-coordinate-old-vnf')) if (vim_info.vimType == "kubernetes" and
not additional_params.get('lcm-kubernetes-def-files')):
raise sol_ex.SolValidationError(
detail="'lcm-kubernetes-def-files' must be specified")
lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.CHANGE_VNFPKG, lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.CHANGE_VNFPKG,
body) body)
@@ -609,9 +611,6 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
vim_infos = inst.vimConnectionInfo vim_infos = inst.vimConnectionInfo
vim_info = inst_utils.select_vim_info(vim_infos) vim_info = inst_utils.select_vim_info(vim_infos)
if lcmocc.operation != 'CHANGE_VNFPKG' and (
vim_info.vimType == 'kubernetes'):
raise sol_ex.NotSupportOperationType
self.conductor_rpc.retry_lcm_op(context, lcmocc.id) self.conductor_rpc.retry_lcm_op(context, lcmocc.id)
@@ -641,9 +640,6 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
vim_infos = inst.vimConnectionInfo vim_infos = inst.vimConnectionInfo
vim_info = inst_utils.select_vim_info(vim_infos) vim_info = inst_utils.select_vim_info(vim_infos)
if lcmocc.operation != 'CHANGE_VNFPKG' and (
vim_info.vimType == 'kubernetes'):
raise sol_ex.NotSupportOperationType
self.conductor_rpc.rollback_lcm_op(context, lcmocc.id) self.conductor_rpc.rollback_lcm_op(context, lcmocc.id)

View File

@@ -13,17 +13,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from kubernetes import client
import os
import pickle
import subprocess
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import uuidutils from oslo_service import loopingcall
from tacker.sol_refactored.common import config from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_resource
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_utils from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_utils
from tacker.sol_refactored import objects from tacker.sol_refactored import objects
@@ -31,6 +28,10 @@ from tacker.sol_refactored import objects
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = config.CONF CONF = config.CONF
CHECK_INTERVAL = 10
TARGET_KIND = {"Pod", "Deployment", "DaemonSet", "StatefulSet", "ReplicaSet"}
SCALABLE_KIND = {"Deployment", "ReplicaSet", "StatefulSet"}
class Kubernetes(object): class Kubernetes(object):
@@ -39,351 +40,435 @@ class Kubernetes(object):
pass pass
def instantiate(self, req, inst, grant_req, grant, vnfd): def instantiate(self, req, inst, grant_req, grant, vnfd):
# pre instantiate cnf vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
target_k8s_files = req.additionalParams.get( with kubernetes_utils.AuthContextManager(vim_info) as acm:
'lcm-kubernetes-def-files') k8s_api_client = acm.init_k8s_api_client()
vnf_artifact_files = vnfd.get_vnf_artifact_files() self._instantiate(req, inst, grant_req, grant, vnfd,
k8s_api_client)
if vnf_artifact_files is None or set(target_k8s_files).difference( def _instantiate(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
set(vnf_artifact_files)): target_k8s_files = req.additionalParams['lcm-kubernetes-def-files']
if vnf_artifact_files:
diff_files = ','.join(list(set( k8s_reses, namespace = self._setup_k8s_reses(
target_k8s_files).difference(set(vnf_artifact_files)))) vnfd, target_k8s_files, k8s_api_client,
else: req.additionalParams.get('namespace'))
diff_files = ','.join(target_k8s_files)
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
vdu_reses = self._select_vdu_reses(vnfd, req.flavourId, k8s_reses)
for vdu_name, vdu_res in vdu_reses.items():
if vdu_name not in vdus_num:
LOG.debug(f'resource name {vdu_res.name} in the kubernetes'
f' manifest does not match the VNFD.')
continue
if vdu_res.kind in SCALABLE_KIND:
vdu_res.body['spec']['replicas'] = vdus_num[vdu_name]
# deploy k8s resources
for k8s_res in k8s_reses:
if not k8s_res.is_exists():
k8s_res.create()
# wait k8s resource create complete
self._wait_k8s_reses_ready(k8s_reses)
# make instantiated info
self._init_instantiated_vnf_info(
inst, req.flavourId, target_k8s_files, vdu_reses, namespace)
self._update_vnfc_info(inst, k8s_api_client)
def _setup_k8s_reses(self, vnfd, target_k8s_files, k8s_api_client,
namespace):
# NOTE: this check should be done in STARTING phase.
vnf_artifact_files = vnfd.get_vnf_artifact_files()
diff_files = set(target_k8s_files) - set(vnf_artifact_files)
if diff_files:
diff_files = ','.join(list(diff_files))
raise sol_ex.CnfDefinitionNotFound(diff_files=diff_files) raise sol_ex.CnfDefinitionNotFound(diff_files=diff_files)
# get k8s content from yaml file # get k8s content from yaml file
k8s_resources, namespace = kubernetes_utils.get_k8s_json_file( return kubernetes_utils.get_k8s_reses_from_json_files(
req, inst, target_k8s_files, vnfd, 'INSTANTIATE') target_k8s_files, vnfd, k8s_api_client, namespace)
# sort k8s resource def instantiate_rollback(self, req, inst, grant_req, grant, vnfd):
sorted_k8s_reses = kubernetes_utils.sort_k8s_resource(
k8s_resources, 'INSTANTIATE')
# deploy k8s resources with sorted resources
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
# This is Context Manager for creation and deletion with kubernetes_utils.AuthContextManager(vim_info) as acm:
# of CA certificate temp file k8s_api_client = acm.init_k8s_api_client()
with kubernetes_utils.CaCertFileContextManager( self._instantiate_rollback(req, inst, grant_req, grant, vnfd,
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm: k8s_api_client)
# add an item ca_cert_file:file_path into vim_info.interfaceInfo, def _instantiate_rollback(self, req, inst, grant_req, grant, vnfd,
# and will be deleted in KubernetesClient k8s_api_client):
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path target_k8s_files = req.additionalParams['lcm-kubernetes-def-files']
k8s_client = kubernetes_utils.KubernetesClient(vim_info) try:
created_k8s_reses = k8s_client.create_k8s_resource( k8s_reses, _ = self._setup_k8s_reses(
sorted_k8s_reses, namespace) vnfd, target_k8s_files, k8s_api_client,
req.additionalParams.get('namespace'))
except sol_ex.SolException:
# it means it failed in a basic check and it failes always.
# nothing to do since instantiate failed in it too.
return
k8s_reses.reverse()
# wait k8s resource create complete # delete k8s resources
k8s_client.wait_k8s_res_create(created_k8s_reses) body = client.V1DeleteOptions(propagation_policy='Foreground')
self._delete_k8s_resource(k8s_reses, body)
# make instantiated info # wait k8s resource delete complete
all_pods = k8s_client.list_namespaced_pods(namespace) self._wait_k8s_reses_deleted(k8s_reses)
self._make_cnf_instantiated_info(
req, inst, vnfd, namespace, created_k8s_reses, all_pods) def _delete_k8s_resource(self, k8s_reses, body):
for k8s_res in k8s_reses:
if k8s_res.is_exists():
k8s_res.delete(body)
def terminate(self, req, inst, grant_req, grant, vnfd): def terminate(self, req, inst, grant_req, grant, vnfd):
target_k8s_files = inst.metadata.get('lcm-kubernetes-def-files') vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._terminate(req, inst, grant_req, grant, vnfd,
k8s_api_client)
def _terminate(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
target_k8s_files = inst.instantiatedVnfInfo.metadata[
'lcm-kubernetes-def-files']
# get k8s content from yaml file # get k8s content from yaml file
k8s_resources, namespace = kubernetes_utils.get_k8s_json_file( namespace = inst.instantiatedVnfInfo.metadata['namespace']
req, inst, target_k8s_files, vnfd, 'TERMINATE') k8s_reses, _ = kubernetes_utils.get_k8s_reses_from_json_files(
target_k8s_files, vnfd, k8s_api_client, namespace)
k8s_reses.reverse()
# sort k8s resource # delete k8s resources
sorted_k8s_reses = kubernetes_utils.sort_k8s_resource( timeout = 0
k8s_resources, 'TERMINATE') if req.terminationType == 'GRACEFUL':
timeout = CONF.v2_vnfm.default_graceful_termination_timeout
if req.obj_attr_is_set('gracefulTerminationTimeout'):
timeout = req.gracefulTerminationTimeout
# delete k8s resources with sorted resources body = client.V1DeleteOptions(propagation_policy='Foreground',
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) grace_period_seconds=timeout)
# This is Context Manager for creation and deletion self._delete_k8s_resource(k8s_reses, body)
# of CA certificate temp file
with kubernetes_utils.CaCertFileContextManager(
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm:
# add an item ca_cert_file:file_path into vim_info.interfaceInfo, # wait k8s resource delete complete
# and will be deleted in KubernetesClient self._wait_k8s_reses_deleted(k8s_reses)
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path
k8s_client = kubernetes_utils.KubernetesClient(vim_info) def _change_vnfpkg_rolling_update(
k8s_client.delete_k8s_resource(req, sorted_k8s_reses, namespace) self, inst, grant_req, grant, vnfd, k8s_api_client,
namespace, old_pods_names):
# wait k8s resource delete complete vdus_num = self._get_vdus_num_from_grant_req_res_defs(
k8s_client.wait_k8s_res_delete(sorted_k8s_reses, namespace) grant_req.addResources)
vdu_reses = []
for vdu_name, vdu_num in vdus_num.items():
vdu_res = self._get_vdu_res(inst, k8s_api_client, vdu_name)
vdu_res.body['spec']['replicas'] = vdu_num
vdu_reses.append(vdu_res)
# apply new deployment
for vdu_res in vdu_reses:
vdu_res.patch()
# wait k8s resource update complete
self._wait_k8s_reses_updated(
vdu_reses, k8s_api_client, namespace, old_pods_names)
# update cnf instantiated info
self._update_vnfc_info(inst, k8s_api_client)
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd): def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
if req.additionalParams.get('upgrade_type') == 'RollingUpdate': vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
# get deployment name from vnfd with kubernetes_utils.AuthContextManager(vim_info) as acm:
deployment_names, namespace = ( k8s_api_client = acm.init_k8s_api_client()
self._get_update_deployment_names_and_namespace( self._change_vnfpkg(req, inst, grant_req, grant, vnfd,
vnfd, req, inst)) k8s_api_client)
# check deployment exists in kubernetes def _change_vnfpkg(self, req, inst, grant_req, grant, vnfd,
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) k8s_api_client):
# This is Context Manager for creation and deletion if req.additionalParams['upgrade_type'] == 'RollingUpdate':
# of CA certificate temp file target_k8s_files = req.additionalParams[
with kubernetes_utils.CaCertFileContextManager( 'lcm-kubernetes-def-files']
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm: namespace = inst.instantiatedVnfInfo.metadata['namespace']
# add an item ca_cert_file:file_path target_vdus = {res_def.resourceTemplateId
# into vim_info.interfaceInfo, for res_def in grant_req.addResources
# and will be deleted in KubernetesClient if res_def.type == 'COMPUTE'}
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path old_pods_names = {vnfc.computeResource.resourceId
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo
if vnfc.vduId in target_vdus}
k8s_client = kubernetes_utils.KubernetesClient(vim_info) k8s_reses, _ = self._setup_k8s_reses(
k8s_client.check_deployment_exist(deployment_names, namespace) vnfd, target_k8s_files, k8s_api_client, namespace)
# get new deployment body vdu_reses = self._select_vdu_reses(
new_deploy_reses = kubernetes_utils.get_new_deployment_body( vnfd, inst.instantiatedVnfInfo.flavourId, k8s_reses)
req, inst, vnfd, deployment_names,
operation='CHANGE_VNFPKG')
# apply new deployment self._init_instantiated_vnf_info(
k8s_client.update_k8s_resource(new_deploy_reses, namespace) inst, inst.instantiatedVnfInfo.flavourId, target_k8s_files,
vdu_reses, namespace)
# wait k8s resource update complete
old_pods_names = [vnfc.computeResource.resourceId for vnfc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
try:
k8s_client.wait_k8s_res_update(
new_deploy_reses, namespace, old_pods_names)
except sol_ex.UpdateK8SResourceFailed as ex:
self._update_cnf_instantiated_info(
inst, deployment_names,
k8s_client.list_namespaced_pods(
namespace=namespace))
raise ex
# execute coordinate vnf script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd, 'CHANGE_VNFPKG',
namespace, new_deploy_reses)
except sol_ex.CoordinateVNFExecutionFailed as ex:
self._update_cnf_instantiated_info(
inst, deployment_names,
k8s_client.list_namespaced_pods(
namespace=namespace))
raise ex
# update cnf instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._update_cnf_instantiated_info(
inst, deployment_names, all_pods)
self._change_vnfpkg_rolling_update(
inst, grant_req, grant, vnfd, k8s_api_client, namespace,
old_pods_names)
else: else:
# TODO(YiFeng): Blue-Green type will be supported in next version. # not reach here
raise sol_ex.SolException(sol_detail='not support update type') pass
inst.vnfdId = req.vnfdId inst.vnfdId = req.vnfdId
if set(req.additionalParams.get(
'lcm-kubernetes-def-files')).difference(set(
inst.metadata.get('lcm-kubernetes-def-files'))):
inst.metadata['lcm-kubernetes-def-files'] = (
req.additionalParams.get('lcm-kubernetes-def-files'))
def change_vnfpkg_rollback( def change_vnfpkg_rollback(self, req, inst, grant_req, grant, vnfd):
self, req, inst, grant_req, grant, vnfd, lcmocc): vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
if not lcmocc.obj_attr_is_set('resourceChanges'): with kubernetes_utils.AuthContextManager(vim_info) as acm:
return k8s_api_client = acm.init_k8s_api_client()
if req.additionalParams.get('upgrade_type') == 'RollingUpdate': self._change_vnfpkg_rollback(req, inst, grant_req, grant, vnfd,
deployment_names = list({ k8s_api_client)
affected_vnfc.metadata['Deployment']['name'] for affected_vnfc
in lcmocc.resourceChanges.affectedVnfcs if
affected_vnfc.changeType == 'ADDED'})
namespace = inst.metadata.get('namespace')
old_deploy_reses = kubernetes_utils.get_new_deployment_body( def _change_vnfpkg_rollback(self, req, inst, grant_req, grant, vnfd,
req, inst, vnfd, deployment_names, k8s_api_client):
operation='CHANGE_VNFPKG_ROLLBACK') if req.additionalParams['upgrade_type'] == 'RollingUpdate':
namespace = inst.instantiatedVnfInfo.metadata['namespace']
# apply old deployment original_pods = {vnfc.computeResource.resourceId for vnfc in
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) inst.instantiatedVnfInfo.vnfcResourceInfo}
# This is Context Manager for creation and deletion all_pods = kubernetes_utils.list_namespaced_pods(
# of CA certificate temp file k8s_api_client, namespace)
with kubernetes_utils.CaCertFileContextManager( current_pods = {pod.metadata.name for pod in all_pods}
vim_info.interfaceInfo.get('ssl_ca_cert')) as ca_cert_cm: old_pods_names = current_pods - original_pods
# add an item ca_cert_file:file_path
# into vim_info.interfaceInfo,
# and will be deleted in KubernetesClient
vim_info.interfaceInfo['ca_cert_file'] = ca_cert_cm.file_path
k8s_client = kubernetes_utils.KubernetesClient(vim_info)
k8s_client.update_k8s_resource(old_deploy_reses, namespace)
# wait k8s resource update complete
old_pods_names = [vnfc.computeResource.resourceId for vnfc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
try:
k8s_client.wait_k8s_res_update(
old_deploy_reses, namespace, old_pods_names)
except sol_ex.UpdateK8SResourceFailed as ex:
raise ex
# execute coordinate vnf script
try:
self._execute_coordinate_vnf_script(
req, inst, grant_req, grant, vnfd,
'CHANGE_VNFPKG_ROLLBACK',
namespace, old_deploy_reses)
except sol_ex.CoordinateVNFExecutionFailed as ex:
raise ex
# update cnf instantiated info
all_pods = k8s_client.list_namespaced_pods(namespace)
self._update_cnf_instantiated_info(
inst, deployment_names, all_pods)
self._change_vnfpkg_rolling_update(
inst, grant_req, grant, vnfd, k8s_api_client, namespace,
old_pods_names)
else: else:
# TODO(YiFeng): Blue-Green type will be supported in next version. # not reach here
raise sol_ex.SolException(sol_detail='not support update type') pass
def _get_update_deployment_names_and_namespace(self, vnfd, req, inst): def heal(self, req, inst, grant_req, grant, vnfd):
vdu_nodes = vnfd.get_vdu_nodes( vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
flavour_id=inst.instantiatedVnfInfo.flavourId) with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._heal(req, inst, grant_req, grant, vnfd, k8s_api_client)
if req.additionalParams.get('vdu_params'): def _heal(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
target_vdus = [vdu_param.get('vdu_id') for vdu_param namespace = inst.instantiatedVnfInfo.metadata['namespace']
in req.additionalParams.get('vdu_params')]
if None in target_vdus:
raise sol_ex.MissingParameterException
else:
target_vdus = [inst_vnc.vduId for inst_vnc in
inst.instantiatedVnfInfo.vnfcResourceInfo]
deployment_names = [value.get('properties', {}).get('name') # get heal Pod name
for name, value in vdu_nodes.items() vnfc_res_ids = [res_def.resource.resourceId
if name in target_vdus] for res_def in grant_req.removeResources
namespace = inst.metadata.get('namespace') if res_def.type == 'COMPUTE']
return deployment_names, namespace target_vnfcs = [vnfc
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo
if vnfc.computeResource.resourceId in vnfc_res_ids]
def _make_cnf_instantiated_info( # check running Pod
self, req, inst, vnfd, namespace, created_k8s_reses, all_pods): all_pods = kubernetes_utils.list_namespaced_pods(
flavour_id = req.flavourId k8s_api_client, namespace)
target_kinds = {"Pod", "Deployment", "DaemonSet", current_pods_name = [pod.metadata.name for pod in all_pods]
"StatefulSet", "ReplicaSet"}
old_pods_names = set()
vdu_reses = {}
for vnfc in target_vnfcs:
if vnfc.id not in current_pods_name:
# may happen when retry or auto healing
msg = f'heal target pod {vnfc.id} is not in the running pod.'
LOG.error(msg)
continue
if vnfc.vduId in vdu_reses:
res = vdu_reses[vnfc.vduId]
else:
res = self._get_vdu_res(inst, k8s_api_client, vnfc.vduId)
vdu_reses[vnfc.vduId] = res
res.delete_pod(vnfc.id)
old_pods_names.add(vnfc.id)
# wait k8s resource create complete
if old_pods_names:
self._wait_k8s_reses_updated(list(vdu_reses.values()),
k8s_api_client, namespace, old_pods_names)
# make instantiated info
self._update_vnfc_info(inst, k8s_api_client)
def _scale_k8s_resource(self, inst, vdus_num, k8s_api_client):
namespace = inst.instantiatedVnfInfo.metadata['namespace']
vdu_reses = []
for vdu_name, vdu_num in vdus_num.items():
vdu_res = self._get_vdu_res(inst, k8s_api_client, vdu_name)
if vdu_res.kind not in SCALABLE_KIND:
LOG.error(f'scale vdu {vdu_name}'
f' is not scalable resource')
continue
vdu_res.scale(vdu_num)
vdu_reses.append(vdu_res)
# wait k8s resource create complete
self._wait_k8s_reses_updated(vdu_reses, k8s_api_client,
namespace, old_pods_names=set())
# make instantiated info
self._update_vnfc_info(inst, k8s_api_client)
def scale(self, req, inst, grant_req, grant, vnfd):
if req.type == 'SCALE_OUT':
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
for vdu_name, vdu_num in vdus_num.items():
vdus_num[vdu_name] = (self._get_current_vdu_num(inst, vdu_name)
+ vdu_num)
elif req.type == 'SCALE_IN':
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.removeResources)
for vdu_name, vdu_num in vdus_num.items():
vdus_num[vdu_name] = (self._get_current_vdu_num(inst, vdu_name)
- vdu_num)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._scale_k8s_resource(inst, vdus_num, k8s_api_client)
def scale_rollback(self, req, inst, grant_req, grant, vnfd):
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
for vdu_name, _ in vdus_num.items():
vdus_num[vdu_name] = self._get_current_vdu_num(inst, vdu_name)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._scale_k8s_resource(inst, vdus_num, k8s_api_client)
def _get_vdus_num_from_grant_req_res_defs(self, res_defs):
vdus_num = {}
for res_def in res_defs:
if res_def.type == 'COMPUTE':
vdus_num.setdefault(res_def.resourceTemplateId, 0)
vdus_num[res_def.resourceTemplateId] += 1
return vdus_num
def _get_current_vdu_num(self, inst, vdu):
num = 0
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo:
if vnfc.vduId == vdu:
num += 1
return num
def _select_vdu_reses(self, vnfd, flavour_id, k8s_reses):
vdu_nodes = vnfd.get_vdu_nodes(flavour_id) vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
vdu_ids = {value.get('properties').get('name'): key vdu_ids = {value.get('properties').get('name'): key
for key, value in vdu_nodes.items()} for key, value in vdu_nodes.items()}
return {vdu_ids[res.name]: res
for res in k8s_reses
if res.kind in TARGET_KIND and res.name in vdu_ids}
vnfc_resources = [] def _init_instantiated_vnf_info(self, inst, flavour_id, def_files,
for k8s_res in created_k8s_reses: vdu_reses, namespace):
if k8s_res.get('kind', '') not in target_kinds: metadata = {
continue 'namespace': namespace,
for pod in all_pods: 'lcm-kubernetes-def-files': def_files,
pod_name = pod.metadata.name 'vdu_reses': {vdu_name: vdu_res.body
match_result = kubernetes_utils.is_match_pod_naming_rule( for vdu_name, vdu_res in vdu_reses.items()}
k8s_res.get('kind', ''), k8s_res.get('name', ''), }
pod_name) inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo(
if match_result:
metadata = {}
metadata[k8s_res.get('kind')] = k8s_res.get('metadata')
if k8s_res.get('kind') != 'Pod':
metadata['Pod'] = pod.metadata.to_dict()
vnfc_resource = objects.VnfcResourceInfoV2(
id=uuidutils.generate_uuid(),
vduId=vdu_ids.get(k8s_res.get('name', '')),
computeResource=objects.ResourceHandle(
resourceId=pod_name,
vimLevelResourceType=k8s_res.get('kind')
),
metadata=metadata
)
vnfc_resources.append(vnfc_resource)
inst_vnf_info = objects.VnfInstanceV2_InstantiatedVnfInfo(
flavourId=flavour_id, flavourId=flavour_id,
vnfState='STARTED', vnfState='STARTED',
metadata=metadata
) )
if vnfc_resources: def _get_vdu_res(self, inst, k8s_api_client, vdu):
inst_vnf_info.vnfcResourceInfo = vnfc_resources # must be found
# make vnfcInfo res = inst.instantiatedVnfInfo.metadata['vdu_reses'][vdu]
# NOTE: vnfcInfo only exists in SOL002 cls = getattr(kubernetes_resource, res['kind'])
inst_vnf_info.vnfcInfo = [ return cls(k8s_api_client, res)
objects.VnfcInfoV2(
id=f'{vnfc_res_info.vduId}-{vnfc_res_info.id}',
vduId=vnfc_res_info.vduId,
vnfcResourceInfoId=vnfc_res_info.id,
vnfcState='STARTED'
)
for vnfc_res_info in vnfc_resources
]
inst.instantiatedVnfInfo = inst_vnf_info def _update_vnfc_info(self, inst, k8s_api_client):
inst.metadata = {"namespace": namespace if namespace else None} all_pods = kubernetes_utils.list_namespaced_pods(
inst.metadata['lcm-kubernetes-def-files'] = req.additionalParams.get( k8s_api_client, inst.instantiatedVnfInfo.metadata['namespace'])
'lcm-kubernetes-def-files') vnfc_resources = []
for pod in all_pods:
def _execute_coordinate_vnf_script( pod_name = pod.metadata.name
self, req, inst, grant_req, grant, vnfd, for vdu_name, vdu_res in (
operation, namespace, new_deploy_reses): inst.instantiatedVnfInfo.metadata['vdu_reses'].items()):
coordinate_vnf = None if kubernetes_utils.is_match_pod_naming_rule(
if req.obj_attr_is_set('additionalParams'): vdu_res['kind'], vdu_res['metadata']['name'],
if operation == 'CHANGE_VNFPKG': pod_name):
coordinate_vnf = req.additionalParams.get( vnfc_resources.append(objects.VnfcResourceInfoV2(
'lcm-operation-coordinate-new-vnf') id=pod_name,
else: vduId=vdu_name,
coordinate_vnf = req.additionalParams.get(
'lcm-operation-coordinate-old-vnf')
if coordinate_vnf:
tmp_csar_dir = vnfd.make_tmp_csar_dir()
script_dict = {
"request": req.to_dict(),
"vnf_instance": inst.to_dict(),
"grant_request": grant_req.to_dict(),
"grant_response": grant.to_dict(),
"tmp_csar_dir": tmp_csar_dir,
"k8s_info": {
"namespace": namespace,
"new_deploy_reses": new_deploy_reses
}
}
script_path = os.path.join(tmp_csar_dir, coordinate_vnf)
out = subprocess.run(["python3", script_path],
input=pickle.dumps(script_dict),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if out.returncode != 0:
LOG.error(out)
raise sol_ex.CoordinateVNFExecutionFailed
def _update_cnf_instantiated_info(self, inst, deployment_names, all_pods):
error_resource = None
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo:
if (vnfc.computeResource.vimLevelResourceType == 'Deployment'
) and (vnfc.metadata.get('Deployment').get(
'name') in deployment_names):
pods_info = [pod for pod in all_pods if
kubernetes_utils.is_match_pod_naming_rule(
'Deployment',
vnfc.metadata.get('Deployment').get('name'),
pod.metadata.name)]
if 'Pending' in [pod.status.phase for pod in pods_info] or (
'Unknown' in [pod.status.phase for pod in pods_info]):
pod_name = [pod.metadata.name for pod in pods_info
if pod.status.phase in [
'Pending', 'Unknown']][0]
error_resource = objects.VnfcResourceInfoV2(
id=uuidutils.generate_uuid(),
vduId=vnfc.vduId,
computeResource=objects.ResourceHandle( computeResource=objects.ResourceHandle(
resourceId=pod_name, resourceId=pod_name,
vimLevelResourceType='Deployment' vimLevelResourceType=vdu_res['kind']
), ),
metadata={'Deployment': vnfc.metadata.get( # lcmocc_utils.update_lcmocc assumes its existence
'Deployment')} metadata={}
) ))
continue
pod_info = pods_info.pop(-1)
vnfc.id = uuidutils.generate_uuid()
vnfc.computeResource.resourceId = pod_info.metadata.name
vnfc.metadata['Pod'] = pod_info.metadata.to_dict()
all_pods.remove(pod_info)
if error_resource: inst.instantiatedVnfInfo.vnfcResourceInfo = vnfc_resources
inst.instantiatedVnfInfo.vnfcResourceInfo.append(error_resource)
# make vnfcInfo
# NOTE: vnfcInfo only exists in SOL002
inst.instantiatedVnfInfo.vnfcInfo = [
objects.VnfcInfoV2(
id=f'{vnfc_res_info.vduId}-{vnfc_res_info.id}',
vduId=vnfc_res_info.vduId,
vnfcResourceInfoId=vnfc_res_info.id,
vnfcState='STARTED'
)
for vnfc_res_info in vnfc_resources
]
def _check_status(self, check_func, *args):
timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(
check_func, *args)
try:
timer.start(interval=CHECK_INTERVAL,
timeout=CONF.v2_vnfm.kubernetes_vim_rsc_wait_timeout).wait()
except loopingcall.LoopingCallTimeOut:
raise sol_ex.K8sOperaitionTimeout()
def _wait_k8s_reses_ready(self, k8s_reses):
def _check_ready(check_reses):
ok_reses = {res for res in check_reses if res.is_ready()}
check_reses -= ok_reses
if not check_reses:
raise loopingcall.LoopingCallDone()
check_reses = set(k8s_reses)
self._check_status(_check_ready, check_reses)
def _wait_k8s_reses_deleted(self, k8s_reses):
def _check_deleted(check_reses):
ok_reses = {res for res in check_reses if not res.is_exists()}
check_reses -= ok_reses
if not check_reses:
raise loopingcall.LoopingCallDone()
check_reses = set(k8s_reses)
self._check_status(_check_deleted, check_reses)
def _wait_k8s_reses_updated(self, k8s_reses, k8s_api_client, namespace,
old_pods_names):
def _check_update(check_reses, k8s_api_client, namespace,
old_pods_names):
ok_reses = set()
all_pods = kubernetes_utils.list_namespaced_pods(
k8s_api_client, namespace)
for res in check_reses:
pods_info = [pod for pod in all_pods
if kubernetes_utils.is_match_pod_naming_rule(
res.kind, res.name, pod.metadata.name)]
if res.is_update(pods_info, old_pods_names):
ok_reses.add(res)
check_reses -= ok_reses
if not check_reses:
raise loopingcall.LoopingCallDone()
check_reses = set(k8s_reses)
self._check_status(_check_update, check_reses, k8s_api_client,
namespace, old_pods_names)

View File

@@ -0,0 +1,586 @@
# 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 inspect
import ipaddress
import re
import time
from kubernetes import client
from oslo_log import log as logging
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
LOG = logging.getLogger(__name__)
CONF = config.CONF
CHECK_INTERVAL = 10
def convert(name):
return re.sub('([A-Z])', lambda x: '_' + x.group(1).lower(), name)
class CommonResource:
# Default API Class
api_class = client.CoreV1Api
def __init__(self, k8s_api_client, k8s_res):
self.k8s_api_client = k8s_api_client
self.k8s_client = self.api_class(api_client=self.k8s_api_client)
self.kind = k8s_res['kind']
self.namespace = k8s_res.get('metadata', {}).get('namespace')
self.name = k8s_res.get('metadata', {}).get('name')
self.metadata = k8s_res.get('metadata', {})
self.body = k8s_res
def create(self):
pass
def read(self):
pass
def delete(self, body):
pass
def is_exists(self):
try:
return self.read() is not None
except sol_ex.K8sResourceNotFound:
return False
def is_ready(self):
return True
class NamespacedResource(CommonResource):
def create(self):
method = getattr(self.k8s_client,
'create_namespaced' + convert(self.__class__.__name__))
try:
method(namespace=self.namespace, body=self.body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def read(self):
method = getattr(self.k8s_client,
'read_namespaced' + convert(self.__class__.__name__))
try:
return method(namespace=self.namespace, name=self.name)
except Exception as ex:
if isinstance(ex, client.ApiException) and ex.status == 404:
raise sol_ex.K8sResourceNotFound(rsc_name=self.name)
else:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def delete(self, body):
method = getattr(self.k8s_client,
'delete_namespaced' + convert(self.__class__.__name__))
try:
method(namespace=self.namespace, name=self.name, body=body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def patch(self):
method = getattr(self.k8s_client,
'patch_namespaced' + convert(self.__class__.__name__))
try:
method(namespace=self.namespace, name=self.name, body=self.body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def scale(self, scale_replicas):
body = {'spec': {'replicas': scale_replicas}}
method = getattr(self.k8s_client,
'patch_namespaced' + convert(self.__class__.__name__) + '_scale')
try:
method(namespace=self.namespace, name=self.name, body=body)
self.body['spec']['replicas'] = scale_replicas
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def delete_pod(self, pod_name):
body = client.V1DeleteOptions(propagation_policy='Foreground')
v1 = client.CoreV1Api(api_client=self.k8s_api_client)
try:
v1.delete_namespaced_pod(namespace=self.namespace,
name=pod_name, body=body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
class ClusterResource(CommonResource):
def create(self):
method = getattr(self.k8s_client,
'create' + convert(self.__class__.__name__))
try:
method(body=self.body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def read(self):
method = getattr(self.k8s_client,
'read' + convert(self.__class__.__name__))
try:
return method(name=self.name)
except Exception as ex:
if isinstance(ex, client.ApiException) and ex.status == 404:
raise sol_ex.K8sResourceNotFound(rsc_name=self.name)
else:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def delete(self, body):
method = getattr(self.k8s_client,
'delete' + convert(self.__class__.__name__))
try:
method(name=self.name, body=body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def patch(self):
method = getattr(self.k8s_client,
'patch' + convert(self.__class__.__name__))
try:
method(namespace=self.namespace, name=self.name, body=self.body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
class AuthenticationResource(CommonResource):
def create(self):
method = getattr(self.k8s_client,
'create' + convert(self.__class__.__name__))
try:
method(body=self.body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
class ComponentStatus(CommonResource):
pass
class ConfigMap(NamespacedResource):
pass
class Container(CommonResource):
pass
class LimitRange(NamespacedResource):
pass
class Namespace(ClusterResource):
def is_ready(self):
namespace_info = self.read()
return (namespace_info.status.phase and
namespace_info.status.phase == 'Active')
class Node(ClusterResource):
def is_ready(self):
node_info = self.read()
for condition in node_info.status.conditions:
if condition.type == 'Ready' and condition.status == 'True':
return True
return False
class PersistentVolume(ClusterResource):
def is_ready(self):
volume_info = self.read()
return (volume_info.status.phase and
volume_info.status.phase in ['Available', 'Bound'])
class PersistentVolumeClaim(NamespacedResource):
def is_ready(self):
claim_info = self.read()
return claim_info.status.phase and claim_info.status.phase == 'Bound'
class Pod(NamespacedResource):
def delete_pod(self, pod_name):
# Get Pod information before deletition
pod_info = self.read()
body = client.V1DeleteOptions(propagation_policy='Foreground')
self.delete(body=body)
timeout = CONF.v2_vnfm.kubernetes_vim_rsc_wait_timeout
max_check_count = (timeout / CHECK_INTERVAL)
check_count = 0
while (check_count < max_check_count):
if not self.is_exists():
break
check_count += 1
time.sleep(CHECK_INTERVAL)
else:
raise sol_ex.K8sOperaitionTimeout()
create_info = client.V1Pod(metadata=self.metadata, spec=pod_info.spec)
self.k8s_client.create_namespaced_pod(
namespace=self.namespace, body=create_info)
def is_ready(self):
pod_info = self.read()
return pod_info.status.phase and pod_info.status.phase == 'Running'
def is_update(self, pods_info, old_pods_names):
return self.is_ready()
class PodTemplate(NamespacedResource):
pass
class ResourceQuota(NamespacedResource):
pass
class Secret(NamespacedResource):
pass
class Service(NamespacedResource):
def is_ready(self):
def _check_is_ip(ip_addr):
try:
ipaddress.ip_address(ip_addr)
return True
except ValueError:
return False
service_info = self.read()
if service_info.spec.cluster_ip in ['', None] or _check_is_ip(
service_info.spec.cluster_ip):
try:
endpoint_info = self.k8s_client.read_namespaced_endpoints(
namespace=self.namespace, name=self.name)
if endpoint_info:
return True
except Exception as ex:
sol_title = "Read Endpoint failed"
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
class ServiceAccount(NamespacedResource):
pass
class Volume(CommonResource):
pass
class ControllerRevision(NamespacedResource):
api_class = client.AppsV1Api
def delete(self, body):
body = client.V1DeleteOptions(
propagation_policy='Background')
try:
self.k8s_client.delete_namespaced_controller_revision(
namespace=self.namespace, name=self.name, body=body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
class DaemonSet(NamespacedResource):
api_class = client.AppsV1Api
def is_ready(self):
daemonset_info = self.read()
return (daemonset_info.status.desired_number_scheduled and
(daemonset_info.status.desired_number_scheduled ==
daemonset_info.status.number_ready))
def is_update(self, pods_info, old_pods_names):
daemonset_info = self.read()
replicas = daemonset_info.status.desired_number_scheduled
for pod_info in pods_info:
if (pod_info.status.phase != 'Running' or
pod_info.metadata.name in old_pods_names):
return False
return len(pods_info) == replicas
class Deployment(NamespacedResource):
api_class = client.AppsV1Api
def is_ready(self):
deployment_info = self.read()
return (deployment_info.status.replicas and
(deployment_info.status.replicas ==
deployment_info.status.ready_replicas))
def is_update(self, pods_info, old_pods_names):
deployment_info = self.read()
replicas = deployment_info.spec.replicas
for pod_info in pods_info:
if (pod_info.status.phase != 'Running' or
pod_info.metadata.name in old_pods_names):
return False
return len(pods_info) == replicas
class ReplicaSet(NamespacedResource):
api_class = client.AppsV1Api
def is_ready(self):
replicaset_info = self.read()
return (replicaset_info.status.replicas and
(replicaset_info.status.replicas ==
replicaset_info.status.ready_replicas))
def is_update(self, pods_info, old_pods_names):
replicaset_info = self.read()
replicas = replicaset_info.spec.replicas
for pod_info in pods_info:
if (pod_info.status.phase != 'Running' or
pod_info.metadata.name in old_pods_names):
return False
return len(pods_info) == replicas
class StatefulSet(NamespacedResource):
api_class = client.AppsV1Api
def delete(self, body):
pvcs_for_delete = []
try:
resp_read_sfs = self.read()
sfs_spec = resp_read_sfs.spec
volume_claim_templates = sfs_spec.volume_claim_templates
v1 = client.CoreV1Api(api_client=self.k8s_api_client)
resps_pvc = v1.list_namespaced_persistent_volume_claim(
namespace=self.namespace)
pvcs = resps_pvc.items
for volume_claim_template in volume_claim_templates:
pvc_template_metadata = volume_claim_template.metadata
match_pattern = '-'.join(
[pvc_template_metadata.name, self.name, ""])
for pvc in pvcs:
pvc_metadata = pvc.metadata
pvc_name = pvc_metadata.name
match_result = re.match(
match_pattern + '[0-9]+$', pvc_name)
if match_result is not None:
pvcs_for_delete.append(pvc_name)
except Exception:
pass
try:
self.k8s_client.delete_namespaced_stateful_set(
namespace=self.namespace, name=self.name, body=body)
for delete_pvc_name in pvcs_for_delete:
try:
v1 = client.CoreV1Api(api_client=self.k8s_api_client)
v1.delete_namespaced_persistent_volume_claim(
name=delete_pvc_name, namespace=self.namespace,
body=body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
def is_ready(self):
statefulset_info = self.read()
replicas = statefulset_info.status.replicas
if replicas == statefulset_info.status.ready_replicas:
for i in range(0, statefulset_info.spec.replicas):
volume_claim_templates = (
statefulset_info.spec.volume_claim_templates)
for volume_claim_template in volume_claim_templates:
pvc_name = "-".join(
[volume_claim_template.metadata.name,
self.name, str(i)])
v1 = client.CoreV1Api(api_client=self.k8s_api_client)
persistent_volume_claim = (
v1.read_namespaced_persistent_volume_claim(
namespace=self.namespace, name=pvc_name))
if persistent_volume_claim.status.phase != 'Bound':
return False
return True
else:
return False
def is_update(self, pods_info, old_pods_names):
statefulset_info = self.read()
replicas = statefulset_info.spec.replicas
for pod_info in pods_info:
if pod_info.status.phase != 'Running':
return False
return len(pods_info) == replicas
class HorizontalPodAutoscaler(NamespacedResource):
api_class = client.AutoscalingV1Api
class Job(NamespacedResource):
api_class = client.BatchV1Api
def is_ready(self):
job_info = self.read()
return (job_info.spec.completions and
job_info.spec.completions == job_info.status.succeeded)
class APIService(ClusterResource):
api_class = client.ApiregistrationV1Api
def is_ready(self):
api_service_info = self.read()
for condition in api_service_info.status.conditions:
if condition.type == 'Available':
if condition.status != 'True':
return False
return True
class TokenReview(AuthenticationResource):
api_class = client.AuthenticationV1Api
class LocalSubjectAccessReview(AuthenticationResource):
api_class = client.AuthorizationV1Api
def create(self):
try:
self.k8s_client.create_namespaced_local_subject_access_review(
namespace=self.namespace, body=self.body)
except Exception as ex:
operation = inspect.currentframe().f_code.co_name
sol_title = "%s failed" % operation
raise sol_ex.K8sOperationFailed(sol_title=sol_title,
sol_detail=str(ex))
class SelfSubjectAccessReview(AuthenticationResource):
api_class = client.AuthorizationV1Api
class SelfSubjectRulesReview(AuthenticationResource):
api_class = client.AuthorizationV1Api
class SubjectAccessReview(AuthenticationResource):
api_class = client.AuthorizationV1Api
class Lease(NamespacedResource):
api_class = client.CoordinationV1Api
class NetworkPolicy(NamespacedResource):
api_class = client.NetworkingV1Api
class ClusterRole(ClusterResource):
api_class = client.RbacAuthorizationV1Api
class ClusterRoleBinding(ClusterResource):
api_class = client.RbacAuthorizationV1Api
class Role(NamespacedResource):
api_class = client.RbacAuthorizationV1Api
class RoleBinding(NamespacedResource):
api_class = client.RbacAuthorizationV1Api
class PriorityClass(ClusterResource):
api_class = client.SchedulingV1Api
class StorageClass(ClusterResource):
api_class = client.StorageV1Api
class VolumeAttachment(ClusterResource):
api_class = client.StorageV1Api
def is_ready(self):
volume_info = self.read()
return volume_info.status.attached

View File

@@ -13,28 +13,22 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import ipaddress
import os import os
import re import re
import tempfile import tempfile
import time
from urllib.parse import urlparse from urllib.parse import urlparse
import urllib.request as urllib2 import urllib.request as urllib2
from kubernetes import client from kubernetes import client
from oslo_log import log as logging from oslo_log import log as logging
from oslo_service import loopingcall
import yaml import yaml
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.objects.v2 import fields as v2fields from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_resource
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = config.CONF
CHECK_INTERVAL = 10
SUPPORTED_NAMESPACE_KINDS = [ SUPPORTED_NAMESPACE_KINDS = [
"Pod", "Pod",
"Binding", "Binding",
@@ -59,478 +53,6 @@ SUPPORTED_NAMESPACE_KINDS = [
"RoleBinding", "RoleBinding",
"Role" "Role"
] ]
RESOURCE_CREATION_ORDER = [
"StorageClass",
"PersistentVolume",
"PriorityClass",
"Namespace",
"LimitRange",
"ResourceQuota",
"HorizontalPodAutoscaler",
"NetworkPolicy",
"Service",
"Endpoints",
"PersistentVolumeClaim",
"ConfigMap",
"Secret",
"Pod",
"Binding",
"StatefulSet",
"Job",
"Deployment",
"DaemonSet",
]
STATUS_CHECK_RES = [
"Pod",
"Service",
"PersistentVolumeClaim",
"Namespace",
"Node",
"PersistentVolume",
"APIService",
"DaemonSet",
"Deployment",
"ReplicaSet",
"StatefulSet",
"Job",
"VolumeAttachment"
]
class KubernetesClient(object):
def __init__(self, vim_info):
self.k8s_api_client = init_k8s_api_client(vim_info)
self.k8s_clients = get_k8s_clients(self.k8s_api_client)
def create_k8s_resource(self, sorted_k8s_reses, namespace):
created_k8s_reses = []
for k8s_res in sorted_k8s_reses:
kind = k8s_res.get('kind', '')
api_version = k8s_res.get('apiVersion', '')
name = k8s_res.get('metadata', {}).get('name', '')
metadata = k8s_res.get('metadata', {})
body = k8s_res
k8s_client = self.k8s_clients[api_version]
try:
if kind in SUPPORTED_NAMESPACE_KINDS:
k8s_method = getattr(
k8s_client, f"create_namespaced_{convert(kind)}")
k8s_method(namespace=namespace, body=body)
create_k8s_res = {
"api_version": api_version,
"namespace": namespace,
"kind": kind,
"name": name,
"metadata": metadata,
"status": "CREATE_IN_PROCESS"
}
else:
k8s_method = getattr(
k8s_client, f"create_{convert(kind)}")
k8s_method(body=body)
create_k8s_res = {
"api_version": api_version,
"kind": kind,
"name": name,
"metadata": metadata,
"status": "CREATE_IN_PROCESS"
}
created_k8s_reses.append(create_k8s_res)
except Exception as ex:
LOG.error(ex)
raise sol_ex.ExecuteK8SResourceCreateApiFailed
return created_k8s_reses
def delete_k8s_resource(self, req, sorted_k8s_reses, namespace):
if req.terminationType:
if req.terminationType == 'GRACEFUL' and req.obj_attr_is_set(
'gracefulTerminationTimeout'):
body = client.V1DeleteOptions(
propagation_policy='Foreground',
grace_period_seconds=req.gracefulTerminationTimeout)
else:
body = client.V1DeleteOptions(
propagation_policy='Foreground',
grace_period_seconds=0)
for k8s_res in sorted_k8s_reses:
kind = k8s_res.get('kind', '')
api_version = k8s_res.get('apiVersion', '')
name = k8s_res.get('metadata', {}).get('name', '')
k8s_client = self.k8s_clients[api_version]
if kind == 'StatefulSet':
pvcs_for_delete = self._get_pvcs_for_delete(
sfs_name=name, namespace=namespace)
if kind == 'ControllerRevision':
body = client.V1DeleteOptions(
propagation_policy='Background')
try:
if kind in SUPPORTED_NAMESPACE_KINDS:
k8s_method = getattr(
k8s_client, f"delete_namespaced_{convert(kind)}")
k8s_method(name=name, namespace=namespace, body=body)
else:
k8s_method = getattr(
k8s_client, f"delete_{convert(kind)}")
k8s_method(name=name, body=body)
k8s_res.update(status='DELETE_IN_PROGRESS')
except Exception as ex:
k8s_res.update(status='DELETE_IN_PROGRESS')
LOG.debug(ex)
if kind == 'StatefulSet' and len(pvcs_for_delete) > 0:
for delete_pvc_name in pvcs_for_delete:
try:
self.k8s_clients[
'v1'].delete_namespaced_persistent_volume_claim(
name=delete_pvc_name, namespace=namespace,
body=body)
except Exception as ex:
LOG.debug(ex)
def update_k8s_resource(self, new_reses, namespace):
for k8s_res in new_reses:
kind = k8s_res.get('kind', '')
api_version = k8s_res.get('apiVersion', '')
name = k8s_res.get('metadata', {}).get('name', '')
k8s_client = self.k8s_clients[api_version]
k8s_method = getattr(
k8s_client, f"patch_namespaced_{convert(kind)}")
try:
k8s_method(name=name, namespace=namespace, body=k8s_res)
k8s_res.update(status='UPDATE_IN_PROCESS')
except Exception as e:
LOG.error(f'update resource failed. kind: {kind},'
f' name: {name}')
raise sol_ex.UpdateK8SResourceFailed from e
def list_namespaced_pods(self, namespace):
if namespace is None:
return None
return self.k8s_clients['v1'].list_namespaced_pod(
namespace=namespace).items
def check_deployment_exist(self, deployment_names, namespace):
for name in deployment_names:
try:
self.k8s_clients['apps/v1'].read_namespaced_deployment(
name=name, namespace=namespace)
except Exception as ex:
LOG.error(f'update deployment {name} does'
f' not exist in kubernetes cluster')
raise ex
def _get_pvcs_for_delete(self, sfs_name, namespace):
pvcs_for_delete = []
try:
resp_read_sfs = self.k8s_clients[
'apps/v1'].read_namespaced_stateful_set(sfs_name, namespace)
sfs_spec = resp_read_sfs.spec
volume_claim_templates = sfs_spec.volume_claim_templates
try:
resps_pvc = self.k8s_clients[
'v1'].list_namespaced_persistent_volume_claim(namespace)
pvcs = resps_pvc.items
for volume_claim_template in volume_claim_templates:
pvc_template_metadata = volume_claim_template.metadata
match_pattern = '-'.join(
[pvc_template_metadata.name, sfs_name, ""])
for pvc in pvcs:
pvc_metadata = pvc.metadata
pvc_name = pvc_metadata.name
match_result = re.match(
match_pattern + '[0-9]+$', pvc_name)
if match_result is not None:
pvcs_for_delete.append(pvc_name)
except Exception:
pass
except Exception:
pass
return pvcs_for_delete
def _wait_completion(self, k8s_reses, operation,
namespace=None, old_pods_names=None):
def _check_create_status():
for k8s_res in k8s_reses:
if k8s_res['status'] != 'CREATE_COMPLETE':
if k8s_res.get('kind') in STATUS_CHECK_RES:
res_check_method = getattr(
self, f"_check_status_"
f"{convert(k8s_res.get('kind'))}")
res_check_method(k8s_res)
else:
k8s_res.update(status='CREATE_COMPLETE')
statuses = {res['status'] for res in k8s_reses}
if len(statuses) == 1 and statuses.pop() == 'CREATE_COMPLETE':
raise loopingcall.LoopingCallDone()
if len(statuses) > 1 and (int(time.time()) - start_time > timeout):
raise sol_ex.CreateK8SResourceFailed
def _check_delete_status():
for k8s_res in k8s_reses:
kind = k8s_res.get('kind', '')
api_version = k8s_res.get('apiVersion', '')
name = k8s_res.get('metadata', {}).get('name', '')
k8s_client = self.k8s_clients[api_version]
if k8s_res['status'] != 'DELETE_COMPLETE':
try:
if kind in SUPPORTED_NAMESPACE_KINDS:
k8s_method = getattr(
k8s_client, f'read_namespaced_{convert(kind)}')
k8s_method(name=name, namespace=namespace)
else:
k8s_method = getattr(
k8s_client, f'read_{convert(kind)}')
k8s_method(name=name)
except Exception:
k8s_res.update(status='DELETE_COMPLETE')
statuses = {res['status'] for res in k8s_reses}
if len(statuses) == 1 and statuses.pop() == 'DELETE_COMPLETE':
raise loopingcall.LoopingCallDone()
if len(statuses) > 1 and (int(time.time()) - start_time > timeout):
raise sol_ex.DeleteK8SResourceFailed
def _check_update_status():
all_namespaced_pods = self.list_namespaced_pods(namespace)
for k8s_res in k8s_reses:
if k8s_res['status'] not in ['UPDATE_COMPLETE',
'UPDATE_FAILED']:
kind = k8s_res.get('kind', '')
api_version = k8s_res.get('apiVersion', '')
name = k8s_res.get('metadata', {}).get('name', '')
k8s_client = self.k8s_clients[api_version]
k8s_method = getattr(
k8s_client, f'read_namespaced_{convert(kind)}')
k8s_info = k8s_method(name=name, namespace=namespace)
replicas = k8s_info.spec.replicas
pods_info = [pod for pod in all_namespaced_pods if
is_match_pod_naming_rule(
kind, name, pod.metadata.name)]
pending_flag = False
unkown_flag = False
for pod_info in pods_info:
if pod_info.status.phase == 'Pending':
pending_flag = True
elif pod_info.status.phase == 'Unknown':
unkown_flag = True
if not pending_flag and not unkown_flag and len(
pods_info) == replicas and (
pods_info[0].metadata.name not in old_pods_names):
k8s_res.update(status='UPDATE_COMPLETE')
if unkown_flag:
k8s_res.update(status='UPDATE_FAILED')
statuses = {res['status'] for res in k8s_reses}
if len(statuses) == 1 and list(statuses)[0] == 'UPDATE_COMPLETE':
raise loopingcall.LoopingCallDone()
if (list(statuses)[0] == 'UPDATE_IN_PROCESS' and (int(
time.time()) - start_time > timeout)) or (
'UPDATE_FAILED' in statuses):
raise sol_ex.UpdateK8SResourceFailed
start_time = int(time.time())
timeout = CONF.v2_vnfm.kubernetes_vim_rsc_wait_timeout
if operation == v2fields.LcmOperationType.INSTANTIATE:
timer = loopingcall.FixedIntervalLoopingCall(_check_create_status)
elif operation == v2fields.LcmOperationType.TERMINATE:
timer = loopingcall.FixedIntervalLoopingCall(_check_delete_status)
else:
timer = loopingcall.FixedIntervalLoopingCall(_check_update_status)
timer.start(interval=CHECK_INTERVAL).wait()
def _check_status_pod(self, k8s_res):
pod = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_pod(
namespace=k8s_res.get('namespace'),
name=k8s_res.get('name'))
if pod.status.phase and pod.status.phase == 'Running':
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_stateful_set(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
stateful_set = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_stateful_set(
namespace=namespace, name=name)
pvc_statuses = []
replicas = stateful_set.status.replicas
if replicas and replicas == stateful_set.status.ready_replicas:
for i in range(0, stateful_set.spec.replicas):
volume_claim_templates = (
stateful_set.spec.volume_claim_templates)
for volume_claim_template in volume_claim_templates:
pvc_name = "-".join(
[volume_claim_template.metadata.name,
k8s_res.get('name'), str(i)])
persistent_volume_claim = (
self.k8s_clients[
'v1'].read_namespaced_persistent_volume_claim(
namespace=namespace, name=pvc_name))
pvc_statuses.append(persistent_volume_claim.status.phase)
if len(set(pvc_statuses)) == 1 and pvc_statuses[0] == 'Bound':
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_service(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
service = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_service(
namespace=namespace, name=name)
status_flag = False
if service.spec.cluster_ip in ['', None] or check_is_ip(
service.spec.cluster_ip):
try:
endpoint = self.k8s_clients['v1'].read_namespaced_endpoints(
namespace=namespace, name=name)
if endpoint:
status_flag = True
except Exception as e:
raise sol_ex.ReadEndpointsFalse(
kind=k8s_res.get('kind')) from e
if status_flag:
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_persistent_volume_claim(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
claim = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_persistent_volume_claim(
namespace=namespace, name=name)
if claim.status.phase and claim.status.phase == 'Bound':
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_namespace(self, k8s_res):
name = k8s_res.get('name')
name_space = self.k8s_clients[k8s_res.get(
'api_version')].read_namespace(name=name)
if name_space.status.phase and name_space.status.phase == 'Active':
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_node(self, k8s_res):
name = k8s_res.get('name')
node = self.k8s_clients[k8s_res.get(
'api_version')].read_node(name=name)
status_flag = False
for condition in node.status.conditions:
if condition.type == 'Ready':
if condition.status == 'True':
status_flag = True
break
else:
continue
if status_flag:
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_persistent_volume(self, k8s_res):
name = k8s_res.get('name')
volume = self.k8s_clients[k8s_res.get(
'api_version')].read_persistent_volume(name=name)
if volume.status.phase and volume.status.phase in [
'Available', 'Bound']:
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_api_service(self, k8s_res):
name = k8s_res.get('name')
api_service = self.k8s_clients[k8s_res.get(
'api_version')].read_api_service(name=name)
status_flag = False
for condition in api_service.status.conditions:
if condition.type == 'Available':
if condition.status == 'True':
status_flag = True
break
else:
continue
if status_flag:
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_daemon_set(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
daemon_set = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_daemon_set(
namespace=namespace, name=name)
if daemon_set.status.desired_number_scheduled and (
daemon_set.status.desired_number_scheduled ==
daemon_set.status.number_ready):
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_deployment(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
deployment = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_deployment(
namespace=namespace, name=name)
if deployment.status.replicas and (
deployment.status.replicas ==
deployment.status.ready_replicas):
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_replica_set(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
replica_set = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_replica_set(
namespace=namespace, name=name)
if replica_set.status.replicas and (
replica_set.status.replicas ==
replica_set.status.ready_replicas):
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_job(self, k8s_res):
namespace = k8s_res.get('namespace')
name = k8s_res.get('name')
job = self.k8s_clients[k8s_res.get(
'api_version')].read_namespaced_job(
namespace=namespace, name=name)
if job.spec.completions and (
job.spec.completions == job.status.succeeded):
k8s_res.update(status='CREATE_COMPLETE')
def _check_status_volume_attachment(self, k8s_res):
name = k8s_res.get('name')
volume = self.k8s_clients[k8s_res.get(
'api_version')].read_volume_attachment(name=name)
if volume.status.attached:
k8s_res.update(status='CREATE_COMPLETE')
def wait_k8s_res_create(self, created_k8s_reses):
self._wait_completion(created_k8s_reses, operation='INSTANTIATE')
def wait_k8s_res_delete(self, sorted_k8s_reses, namespace):
self._wait_completion(
sorted_k8s_reses, operation='TERMINATE', namespace=namespace)
def wait_k8s_res_update(self, new_k8s_reses, namespace,
old_pods_names=None):
self._wait_completion(
new_k8s_reses, operation='UPDATE', namespace=namespace,
old_pods_names=old_pods_names)
def is_match_pod_naming_rule(rsc_kind, rsc_name, pod_name): def is_match_pod_naming_rule(rsc_kind, rsc_name, pod_name):
@@ -538,7 +60,7 @@ def is_match_pod_naming_rule(rsc_kind, rsc_name, pod_name):
if rsc_kind == 'Pod': if rsc_kind == 'Pod':
# Expected example: name # Expected example: name
if rsc_name == pod_name: if rsc_name == pod_name:
match_result = True return True
elif rsc_kind == 'Deployment': elif rsc_kind == 'Deployment':
# Expected example: name-012789abef-019az # Expected example: name-012789abef-019az
# NOTE(horie): The naming rule of Pod in deployment is # NOTE(horie): The naming rule of Pod in deployment is
@@ -547,106 +69,21 @@ def is_match_pod_naming_rule(rsc_kind, rsc_name, pod_name):
# This may be from 1 to 10 caracters but not sure the lower limit # This may be from 1 to 10 caracters but not sure the lower limit
# from the source code of Kubernetes. # from the source code of Kubernetes.
match_result = re.match( match_result = re.match(
rsc_name + '-([0-9a-f]{1,10})-([0-9a-z]{5})+$', rsc_name + '-([0-9a-f]{1,10})-([0-9a-z]{5})+$', pod_name)
pod_name)
elif rsc_kind in ('ReplicaSet', 'DaemonSet'): elif rsc_kind in ('ReplicaSet', 'DaemonSet'):
# Expected example: name-019az # Expected example: name-019az
match_result = re.match( match_result = re.match(rsc_name + '-([0-9a-z]{5})+$', pod_name)
rsc_name + '-([0-9a-z]{5})+$',
pod_name)
elif rsc_kind == 'StatefulSet': elif rsc_kind == 'StatefulSet':
# Expected example: name-0 # Expected example: name-0
match_result = re.match( match_result = re.match(rsc_name + '-[0-9]+$', pod_name)
rsc_name + '-[0-9]+$',
pod_name)
if match_result: if match_result:
return True return True
return False return False
def check_is_ip(ip_addr): def get_k8s_reses_from_json_files(target_k8s_files, vnfd, k8s_api_client,
try: namespace):
ipaddress.ip_address(ip_addr)
return True
except ValueError:
return False
def convert(tmp_name):
name_with_underscores = re.sub(
'(.)([A-Z][a-z]+)', r'\1_\2', tmp_name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2',
name_with_underscores).lower()
def init_k8s_api_client(vim_info):
k8s_config = client.Configuration()
k8s_config.host = vim_info.interfaceInfo['endpoint']
ca_cert_file = (vim_info.interfaceInfo.pop('ca_cert_file')
if 'ca_cert_file' in vim_info.interfaceInfo else None)
if ('username' in vim_info.accessInfo and 'password'
in vim_info.accessInfo and vim_info.accessInfo.get(
'password') is not None):
k8s_config.username = vim_info.accessInfo['username']
k8s_config.password = vim_info.accessInfo['password']
basic_token = k8s_config.get_basic_auth_token()
k8s_config.api_key['authorization'] = basic_token
if 'bearer_token' in vim_info.accessInfo:
k8s_config.api_key_prefix['authorization'] = 'Bearer'
k8s_config.api_key['authorization'] = vim_info.accessInfo[
'bearer_token']
if 'ssl_ca_cert' in vim_info.interfaceInfo and ca_cert_file:
k8s_config.ssl_ca_cert = ca_cert_file
k8s_config.verify_ssl = True
else:
k8s_config.verify_ssl = False
return client.api_client.ApiClient(configuration=k8s_config)
def get_k8s_clients(k8s_api_client):
k8s_clients = {
"v1": client.CoreV1Api(api_client=k8s_api_client),
"apiregistration.k8s.io/v1":
client.ApiregistrationV1Api(api_client=k8s_api_client),
"apps/v1": client.AppsV1Api(api_client=k8s_api_client),
"authentication.k8s.io/v1":
client.AuthenticationV1Api(api_client=k8s_api_client),
"authorization.k8s.io/v1":
client.AuthorizationV1Api(api_client=k8s_api_client),
"autoscaling/v1": client.AutoscalingV1Api(
api_client=k8s_api_client),
"batch/v1": client.BatchV1Api(api_client=k8s_api_client),
"coordination.k8s.io/v1":
client.CoordinationV1Api(api_client=k8s_api_client),
"networking.k8s.io/v1":
client.NetworkingV1Api(api_client=k8s_api_client),
"rbac.authorization.k8s.io/v1":
client.RbacAuthorizationV1Api(api_client=k8s_api_client),
"scheduling.k8s.io/v1":
client.SchedulingV1Api(api_client=k8s_api_client),
"storage.k8s.io/v1":
client.StorageV1Api(api_client=k8s_api_client)
}
return k8s_clients
def get_k8s_json_file(req, inst, target_k8s_files, vnfd, operation):
def _update_k8s_resources(namespace):
for k8s_res in k8s_resources:
if (k8s_res.get('kind', '') in SUPPORTED_NAMESPACE_KINDS and
k8s_res.get('metadata') is None):
k8s_res.update(metadata={})
if k8s_res.get('kind', '') in SUPPORTED_NAMESPACE_KINDS:
k8s_res['metadata'].update(namespace=namespace)
k8s_resources = [] k8s_resources = []
for target_k8s_file in target_k8s_files: for target_k8s_file in target_k8s_files:
@@ -662,93 +99,81 @@ def get_k8s_json_file(req, inst, target_k8s_files, vnfd, operation):
k8s_resources.extend(list(yaml.safe_load_all(file_content))) k8s_resources.extend(list(yaml.safe_load_all(file_content)))
# check namespace
if operation == v2fields.LcmOperationType.INSTANTIATE:
if req.additionalParams.get('namespace') is None:
_update_k8s_resources('default')
namespaces = {
k8s_res['metadata']['namespace'] for k8s_res in
k8s_resources if k8s_res.get('kind') in
SUPPORTED_NAMESPACE_KINDS}
if len(namespaces) > 1:
raise sol_ex.NamespaceNotUniform()
return k8s_resources, namespaces.pop() if namespaces else None
_update_k8s_resources(req.additionalParams.get('namespace'))
return k8s_resources, req.additionalParams.get('namespace')
return k8s_resources, inst.metadata.get('namespace')
def sort_k8s_resource(k8s_resources, operation):
pos = 0
sorted_k8s_reses = []
if operation == v2fields.LcmOperationType.INSTANTIATE:
sort_order = RESOURCE_CREATION_ORDER
else:
sort_order = list(reversed(RESOURCE_CREATION_ORDER))
copy_k8s_resources = copy.deepcopy(k8s_resources)
for kind in sort_order:
for res_index, res in enumerate(copy_k8s_resources):
if res.get('kind', '') == kind:
index = k8s_resources.index(res)
sorted_k8s_reses.append(k8s_resources.pop(index))
# Other kind (such as PodTemplate, Node, and so on) that are
# not present in `RESOURCE_CREATION_ORDER` are inserted in
# place of the Service kind and created/deleted in the same
# order as the Service kind.
if kind == 'Service':
pos = len(sorted_k8s_reses)
for k8s_res in k8s_resources: for k8s_res in k8s_resources:
sorted_k8s_reses.insert(pos, k8s_res) if not k8s_res.get('kind'):
raise sol_ex.K8sInvalidManifestFound()
if k8s_res['kind'] in SUPPORTED_NAMESPACE_KINDS:
k8s_res.setdefault('metadata', {})
if namespace is None:
k8s_res['metadata'].setdefault('namespace', 'default')
else:
k8s_res['metadata']['namespace'] = namespace
return sorted_k8s_reses # check namespace
if namespace is None:
namespaces = {k8s_res['metadata']['namespace']
for k8s_res in k8s_resources
if k8s_res['kind'] in SUPPORTED_NAMESPACE_KINDS}
if len(namespaces) > 1:
raise sol_ex.NamespaceNotUniform()
namespace = namespaces.pop() if namespaces else 'default'
k8s_reses = []
for k8s_res in k8s_resources:
cls = getattr(kubernetes_resource, k8s_res['kind'])
k8s_reses.append(cls(k8s_api_client, k8s_res))
return k8s_reses, namespace
def get_new_deployment_body( def list_namespaced_pods(k8s_api_client, namespace):
req, inst, vnfd, deployment_names, operation): k8s_client = client.CoreV1Api(api_client=k8s_api_client)
if operation == v2fields.LcmOperationType.CHANGE_VNFPKG: return k8s_client.list_namespaced_pod(namespace=namespace).items
target_k8s_files = req.additionalParams.get(
'lcm-kubernetes-def-files')
else:
target_k8s_files = inst.metadata.get('lcm-kubernetes-def-files')
new_k8s_resources, namespace = get_k8s_json_file(
req, inst, target_k8s_files, vnfd, operation)
new_deploy_reses = []
for k8s_res in new_k8s_resources:
if k8s_res.get('kind', '') == 'Deployment' and k8s_res.get(
'metadata', {}).get('name', '') in deployment_names:
k8s_res['metadata']['namespace'] = namespace
new_deploy_reses.append(k8s_res)
return new_deploy_reses
class CaCertFileContextManager: class AuthContextManager:
def __init__(self, ca_cert_str): def __init__(self, vim_info):
self._file_descriptor = None self.vim_info = vim_info
self.file_path = None self.ca_cert_file = None
self.ca_cert_str = ca_cert_str
def __enter__(self): def __enter__(self):
if not self.ca_cert_str:
return self
self._file_descriptor, self.file_path = tempfile.mkstemp()
ca_cert = re.sub(r'\s', '\n', self.ca_cert_str)
ca_cert = re.sub(r'BEGIN\nCERT', r'BEGIN CERT', ca_cert)
ca_cert = re.sub(r'END\nCERT', r'END CERT', ca_cert)
# write ca cert file
os.write(self._file_descriptor, ca_cert.encode())
return self return self
def __exit__(self, exc_type, exc_value, exc_traceback): def __exit__(self, exc_type, exc_value, exc_traceback):
if not self.ca_cert_str: if self.ca_cert_file:
return os.remove(self.ca_cert_file)
os.close(self._file_descriptor)
os.remove(self.file_path) def _create_ca_cert_file(self, ca_cert_str):
file_descriptor, self.ca_cert_file = tempfile.mkstemp()
ca_cert = re.sub(r'\s', '\n', ca_cert_str)
ca_cert = re.sub(r'BEGIN\nCERT', r'BEGIN CERT', ca_cert)
ca_cert = re.sub(r'END\nCERT', r'END CERT', ca_cert)
# write ca cert file
os.write(file_descriptor, ca_cert.encode())
os.close(file_descriptor)
def init_k8s_api_client(self):
k8s_config = client.Configuration()
k8s_config.host = self.vim_info.interfaceInfo['endpoint']
if ('username' in self.vim_info.accessInfo and
self.vim_info.accessInfo.get('password') is not None):
k8s_config.username = self.vim_info.accessInfo['username']
k8s_config.password = self.vim_info.accessInfo['password']
basic_token = k8s_config.get_basic_auth_token()
k8s_config.api_key['authorization'] = basic_token
if 'bearer_token' in self.vim_info.accessInfo:
k8s_config.api_key_prefix['authorization'] = 'Bearer'
k8s_config.api_key['authorization'] = self.vim_info.accessInfo[
'bearer_token']
if 'ssl_ca_cert' in self.vim_info.interfaceInfo:
self._create_ca_cert_file(
self.vim_info.interfaceInfo['ssl_ca_cert'])
k8s_config.ssl_ca_cert = self.ca_cert_file
k8s_config.verify_ssl = True
else:
k8s_config.verify_ssl = False
return client.api_client.ApiClient(configuration=k8s_config)

View File

@@ -84,6 +84,9 @@ class VnfInstanceV2_InstantiatedVnfInfo(base.TackerObject,
'VirtualStorageResourceInfoV2', nullable=True), 'VirtualStorageResourceInfoV2', nullable=True),
# NOTE: vnfcInfo exists in SOL002 only. # NOTE: vnfcInfo exists in SOL002 only.
'vnfcInfo': fields.ListOfObjectsField('VnfcInfoV2', nullable=True), 'vnfcInfo': fields.ListOfObjectsField('VnfcInfoV2', nullable=True),
# NOTE: metadata is not defined in SOL003. it is original
# definition of Tacker.
'metadata': fields.KeyValuePairsField(nullable=True),
} }

View File

@@ -193,6 +193,16 @@ class BaseVnfLcmKubernetesV2Test(base.BaseTestCase):
return self.tacker_client.do_request( return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.0.0") path, "POST", body=req_body, version="2.0.0")
def scale_vnf_instance(self, inst_id, req_body):
path = f"/vnflcm/v2/vnf_instances/{inst_id}/scale"
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.0.0")
def heal_vnf_instance(self, inst_id, req_body):
path = f"/vnflcm/v2/vnf_instances/{inst_id}/heal"
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.0.0")
def change_vnfpkg(self, inst_id, req_body): def change_vnfpkg(self, inst_id, req_body):
path = f"/vnflcm/v2/vnf_instances/{inst_id}/change_vnfpkg" path = f"/vnflcm/v2/vnf_instances/{inst_id}/change_vnfpkg"
return self.tacker_client.do_request( return self.tacker_client.do_request(

View File

@@ -138,6 +138,28 @@ def max_sample_terminate():
} }
def max_sample_scale_out():
return {
"type": "SCALE_OUT",
"aspectId": "vdu3_aspect",
"numberOfSteps": 2
}
def max_sample_scale_in():
return {
"type": "SCALE_IN",
"aspectId": "vdu3_aspect",
"numberOfSteps": 1
}
def max_sample_heal(vnfc_ids):
return {
"vnfcInstanceId": vnfc_ids
}
def min_sample_instantiate(vim_id_1): def min_sample_instantiate(vim_id_1):
vim_1 = { vim_1 = {
"vimId": vim_id_1, "vimId": vim_id_1,
@@ -166,14 +188,7 @@ def min_sample_terminate():
} }
def change_vnfpkg_instantiate(auth_url, bearer_token): def error_handling_instantiate(auth_url, bearer_token):
# All attributes are set.
# NOTE: All of the following cardinality attributes are set.
# In addition, 0..N or 1..N attributes are set to 2 or more.
# - 0..1 (1)
# - 0..N (2 or more)
# - 1
# - 1..N (2 or more)
vim_id_1 = uuidutils.generate_uuid() vim_id_1 = uuidutils.generate_uuid()
vim_1 = { vim_1 = {
"vimId": vim_id_1, "vimId": vim_id_1,
@@ -191,44 +206,6 @@ def change_vnfpkg_instantiate(auth_url, bearer_token):
"vimConnectionInfo": { "vimConnectionInfo": {
"vim1": vim_1 "vim1": vim_1
}, },
"additionalParams": {
"lcm-kubernetes-def-files": [
"Files/kubernetes/deployment.yaml",
"Files/kubernetes/namespace.yaml"
],
"namespace": "curry"
}
}
def change_vnfpkg_all_params(vnfd_id):
return {
"vnfdId": vnfd_id,
"additionalParams": {
"upgrade_type": "RollingUpdate",
"lcm-operation-coordinate-old-vnf":
"Scripts/coordinate_old_vnf.py",
"lcm-operation-coordinate-new-vnf":
"Scripts/coordinate_new_vnf.py",
"lcm-kubernetes-def-files": [
"Files/new_kubernetes/new_deployment.yaml"],
"vdu_params": [{
"vdu_id": "VDU2"
}]
}
}
def change_vnfpkg_instantiate_min(vim_id_1):
vim_1 = {
"vimId": vim_id_1,
"vimType": "kubernetes",
}
return {
"flavourId": "simple",
"vimConnectionInfo": {
"vim1": vim_1,
},
"additionalParams": { "additionalParams": {
"lcm-kubernetes-def-files": [ "lcm-kubernetes-def-files": [
"Files/kubernetes/deployment.yaml" "Files/kubernetes/deployment.yaml"
@@ -237,33 +214,59 @@ def change_vnfpkg_instantiate_min(vim_id_1):
} }
def change_vnfpkg_min(vnfd_id): def error_handling_scale_out():
return { return {
"vnfdId": vnfd_id, "type": "SCALE_OUT",
"aspectId": "vdu2_aspect",
"numberOfSteps": 1
}
def error_handling_terminate():
return {
"terminationType": "FORCEFUL"
}
def change_vnfpkg_instantiate(auth_url, bearer_token):
vim_id_1 = uuidutils.generate_uuid()
vim_1 = {
"vimId": vim_id_1,
"vimType": "kubernetes",
"interfaceInfo": {"endpoint": auth_url},
"accessInfo": {
"bearer_token": bearer_token,
"region": "RegionOne",
},
"extra": {"dummy-key": "dummy-val"}
}
return {
"flavourId": "simple",
"vimConnectionInfo": {
"vim1": vim_1
},
"additionalParams": { "additionalParams": {
"upgrade_type": "RollingUpdate", "lcm-kubernetes-def-files": [
"lcm-operation-coordinate-old-vnf": "Files/kubernetes/namespace.yaml",
"Scripts/coordinate_old_vnf.py", "Files/kubernetes/deployment.yaml"
"lcm-operation-coordinate-new-vnf": ],
"Scripts/coordinate_new_vnf.py", "namespace": "curry"
} }
} }
def change_vnfpkg_instantiate_error_handing(vim_id_1): def change_vnfpkg(vnfd_id):
vim_1 = {
"vimId": vim_id_1,
"vimType": "kubernetes",
}
return { return {
"flavourId": "simple", "vnfdId": vnfd_id,
"vimConnectionInfo": {
"vim1": vim_1,
},
"additionalParams": { "additionalParams": {
"upgrade_type": "RollingUpdate",
"lcm-kubernetes-def-files": [ "lcm-kubernetes-def-files": [
"Files/kubernetes/deployment_fail_test.yaml" "Files/kubernetes/namespace.yaml",
] "Files/new_kubernetes/new_deployment.yaml"],
"vdu_params": [{
"vdu_id": "VDU2"
}]
} }
} }
@@ -273,11 +276,17 @@ def change_vnfpkg_error(vnfd_id):
"vnfdId": vnfd_id, "vnfdId": vnfd_id,
"additionalParams": { "additionalParams": {
"upgrade_type": "RollingUpdate", "upgrade_type": "RollingUpdate",
"lcm-operation-coordinate-old-vnf":
"Scripts/coordinate_old_vnf.py",
"lcm-operation-coordinate-new-vnf":
"Scripts/coordinate_new_vnf.py",
"lcm-kubernetes-def-files": [ "lcm-kubernetes-def-files": [
"Files/new_kubernetes/error_deployment.yaml"] "Files/kubernetes/namespace.yaml",
"Files/new_kubernetes/not_exist_deployment.yaml"],
"vdu_params": [{
"vdu_id": "VDU2"
}]
} }
} }
def change_vnfpkg_terminate():
return {
"terminationType": "FORCEFUL"
}

View File

@@ -40,6 +40,33 @@ topology_template:
type: company.provider.VNF type: company.provider.VNF
properties: properties:
flavour_description: A simple flavour 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
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: VDU1:
type: tosca.nodes.nfv.Vdu.Compute type: tosca.nodes.nfv.Vdu.Compute
@@ -63,11 +90,29 @@ topology_template:
type: tosca.nodes.nfv.Vdu.Compute type: tosca.nodes.nfv.Vdu.Compute
properties: properties:
name: vdu3 name: vdu3
description: VDU2 compute node description: VDU3 compute node
vdu_profile: vdu_profile:
min_number_of_instances: 1 min_number_of_instances: 1
max_number_of_instances: 3 max_number_of_instances: 3
VDU5:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: vdu5
description: VDU5 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
VDU6:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: vdu6
description: VDU6 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
policies: policies:
- scaling_aspects: - scaling_aspects:
type: tosca.policies.nfv.ScalingAspects type: tosca.policies.nfv.ScalingAspects
@@ -85,12 +130,18 @@ topology_template:
max_scale_level: 2 max_scale_level: 2
step_deltas: step_deltas:
- delta_1 - delta_1
vdu5_aspect:
name: vdu5_aspect
description: vdu5 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU2_initial_delta: - VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta type: tosca.policies.nfv.VduInitialDelta
properties: properties:
initial_delta: initial_delta:
number_of_instances: 1 number_of_instances: 2
targets: [ VDU2 ] targets: [ VDU2 ]
- VDU2_scaling_aspect_deltas: - VDU2_scaling_aspect_deltas:
@@ -118,6 +169,22 @@ topology_template:
number_of_instances: 1 number_of_instances: 1
targets: [ VDU3 ] targets: [ VDU3 ]
- VDU5_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU5 ]
- VDU5_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: vdu5_aspect
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU5 ]
- instantiation_levels: - instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels type: tosca.policies.nfv.InstantiationLevels
properties: properties:
@@ -126,9 +193,11 @@ topology_template:
description: Smallest size description: Smallest size
scale_info: scale_info:
vdu2_aspect: vdu2_aspect:
scale_level: 0 scale_level: 1
vdu3_aspect: vdu3_aspect:
scale_level: 0 scale_level: 0
vdu5_aspect:
scale_level: 0
instantiation_level_2: instantiation_level_2:
description: Largest size description: Largest size
scale_info: scale_info:
@@ -136,6 +205,8 @@ topology_template:
scale_level: 2 scale_level: 2
vdu3_aspect: vdu3_aspect:
scale_level: 2 scale_level: 2
vdu5_aspect:
scale_level: 2
default_level: instantiation_level_1 default_level: instantiation_level_1
- VDU1_instantiation_levels: - VDU1_instantiation_levels:
@@ -153,7 +224,7 @@ topology_template:
properties: properties:
levels: levels:
instantiation_level_1: instantiation_level_1:
number_of_instances: 1 number_of_instances: 2
instantiation_level_2: instantiation_level_2:
number_of_instances: 3 number_of_instances: 3
targets: [ VDU2 ] targets: [ VDU2 ]
@@ -167,3 +238,13 @@ topology_template:
instantiation_level_2: instantiation_level_2:
number_of_instances: 3 number_of_instances: 3
targets: [ VDU3 ] targets: [ VDU3 ]
- VDU5_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU5 ]

View File

@@ -1,7 +1,7 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: vdu3 name: vdu2
namespace: default namespace: default
spec: spec:
replicas: 2 replicas: 2
@@ -20,9 +20,5 @@ spec:
ports: ports:
- containerPort: 80 - containerPort: 80
protocol: TCP protocol: TCP
volumes:
- name: config
configMap:
name: nginx-app-original
strategy: strategy:
type: RollingUpdate type: RollingUpdate

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2022 Fujitsu # Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -12,48 +12,50 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import functools
import os import os
import pickle import pickle
import sys import sys
from oslo_log import log as logging
LOG = logging.getLogger(__name__) class FailScript(object):
CMD_TIMEOUT = 30 """Define error method for each operation
SERVER_WAIT_COMPLETE_TIME = 60
SSH_CONNECT_RETRY_COUNT = 4
For example:
class SampleNewCoordinateVNFScript(object): 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, k8s_info): def __init__(self, req, inst, grant_req, grant, csar_dir):
self.req = req self.req = req
self.inst = inst self.inst = inst
self.grant_req = grant_req self.grant_req = grant_req
self.grant = grant self.grant = grant
self.csar_dir = csar_dir self.csar_dir = csar_dir
self.k8s_info = k8s_info
def coordinate_vnf(self): def _fail(self, method):
pass 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(): def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer) script_dict = pickle.load(sys.stdin.buffer)
operation = script_dict['operation']
req = script_dict['request'] req = script_dict['request']
inst = script_dict['vnf_instance'] inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request'] grant_req = script_dict['grant_request']
grant = script_dict['grant_response'] grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir'] csar_dir = script_dict['tmp_csar_dir']
k8s_info = script_dict['k8s_info']
script = SampleNewCoordinateVNFScript( script = FailScript(req, inst, grant_req, grant, csar_dir)
req, inst, grant_req, grant, getattr(script, operation)()
csar_dir, k8s_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -11,7 +11,7 @@ Hash: 30071afb22afcb0e54e03df3d22f0852994b4120ca85ac72e9c207c97a4755a8
Name: Files/new_kubernetes/error_deployment.yaml Name: Files/new_kubernetes/error_deployment.yaml
Content-Type: test-data Content-Type: test-data
Algorithm: SHA-256 Algorithm: SHA-256
Hash: 1386a46e16e1c07aef97d9c1bb6ca7a6f2af99b314ecac42094634a59577a060 Hash: 1fad945911465b5c12fe20bc8350f17b417e5212544d442f2d56df15b8802d8d
Name: Files/new_kubernetes/new_deployment.yaml Name: Files/new_kubernetes/new_deployment.yaml
Content-Type: test-data Content-Type: test-data

View File

@@ -35,13 +35,18 @@ shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
shutil.rmtree(tmp_dir) shutil.rmtree(tmp_dir)
# if you change_vnfpkg with all parameters # if you change_vnfpkg with all parameters
change_vnfpkg_all_params = paramgen.change_vnfpkg_all_params(vnfd_id) change_vnfpkg = paramgen.change_vnfpkg(vnfd_id)
# if you change_vnfpkg with no operational parameters # if you change_vnfpkg with no operational parameters
change_vnfpkg_min = paramgen.change_vnfpkg_min(vnfd_id) change_vnfpkg_error = paramgen.change_vnfpkg_error(vnfd_id)
with open("change_vnfpkg_all_params", "w", encoding='utf-8') as f: change_vnfpkg_terminate = paramgen.change_vnfpkg_terminate()
f.write(json.dumps(change_vnfpkg_all_params, indent=2))
with open("change_vnfpkg_min", "w", encoding='utf-8') as f: with open("change_vnfpkg", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_min, indent=2)) f.write(json.dumps(change_vnfpkg, indent=2))
with open("change_vnfpkg_error", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_error, indent=2))
with open("change_vnfpkg_terminate", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_terminate, indent=2))

View File

@@ -40,6 +40,33 @@ topology_template:
type: company.provider.VNF type: company.provider.VNF
properties: properties:
flavour_description: A simple flavour 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
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: VDU1:
type: tosca.nodes.nfv.Vdu.Compute type: tosca.nodes.nfv.Vdu.Compute
@@ -56,7 +83,7 @@ topology_template:
name: vdu2 name: vdu2
description: VDU2 compute node description: VDU2 compute node
vdu_profile: vdu_profile:
min_number_of_instances: 2 min_number_of_instances: 1
max_number_of_instances: 3 max_number_of_instances: 3
VDU3: VDU3:
@@ -68,15 +95,6 @@ topology_template:
min_number_of_instances: 1 min_number_of_instances: 1
max_number_of_instances: 3 max_number_of_instances: 3
VDU4:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: vdu4
description: VDU4 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
VDU5: VDU5:
type: tosca.nodes.nfv.Vdu.Compute type: tosca.nodes.nfv.Vdu.Compute
properties: properties:
@@ -93,7 +111,7 @@ topology_template:
description: VDU6 compute node description: VDU6 compute node
vdu_profile: vdu_profile:
min_number_of_instances: 1 min_number_of_instances: 1
max_number_of_instances: 3 max_number_of_instances: 1
policies: policies:
- scaling_aspects: - scaling_aspects:
@@ -112,24 +130,12 @@ topology_template:
max_scale_level: 2 max_scale_level: 2
step_deltas: step_deltas:
- delta_1 - delta_1
vdu4_aspect:
name: vdu4_aspect
description: vdu4 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
vdu5_aspect: vdu5_aspect:
name: vdu5_aspect name: vdu5_aspect
description: vdu5 scaling aspect description: vdu5 scaling aspect
max_scale_level: 2 max_scale_level: 2
step_deltas: step_deltas:
- delta_1 - delta_1
vdu6_aspect:
name: vdu6_aspect
description: vdu6 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU2_initial_delta: - VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta type: tosca.policies.nfv.VduInitialDelta
@@ -163,22 +169,6 @@ topology_template:
number_of_instances: 1 number_of_instances: 1
targets: [ VDU3 ] targets: [ VDU3 ]
- VDU4_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU4 ]
- VDU4_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: vdu4_aspect
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU4 ]
- VDU5_initial_delta: - VDU5_initial_delta:
type: tosca.policies.nfv.VduInitialDelta type: tosca.policies.nfv.VduInitialDelta
properties: properties:
@@ -195,22 +185,6 @@ topology_template:
number_of_instances: 1 number_of_instances: 1
targets: [ VDU5 ] targets: [ VDU5 ]
- VDU6_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU6 ]
- VDU6_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: vdu6_aspect
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU6 ]
- instantiation_levels: - instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels type: tosca.policies.nfv.InstantiationLevels
properties: properties:
@@ -219,15 +193,11 @@ topology_template:
description: Smallest size description: Smallest size
scale_info: scale_info:
vdu2_aspect: vdu2_aspect:
scale_level: 0 scale_level: 1
vdu3_aspect: vdu3_aspect:
scale_level: 0 scale_level: 0
vdu4_aspect:
scale_level: 2
vdu5_aspect: vdu5_aspect:
scale_level: 2 scale_level: 0
vdu6_aspect:
scale_level: 2
instantiation_level_2: instantiation_level_2:
description: Largest size description: Largest size
scale_info: scale_info:
@@ -235,12 +205,8 @@ topology_template:
scale_level: 2 scale_level: 2
vdu3_aspect: vdu3_aspect:
scale_level: 2 scale_level: 2
vdu4_aspect:
scale_level: 2
vdu5_aspect: vdu5_aspect:
scale_level: 2 scale_level: 2
vdu6_aspect:
scale_level: 2
default_level: instantiation_level_1 default_level: instantiation_level_1
- VDU1_instantiation_levels: - VDU1_instantiation_levels:
@@ -273,16 +239,6 @@ topology_template:
number_of_instances: 3 number_of_instances: 3
targets: [ VDU3 ] targets: [ VDU3 ]
- VDU4_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU4 ]
- VDU5_instantiation_levels: - VDU5_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels type: tosca.policies.nfv.VduInstantiationLevels
properties: properties:
@@ -292,13 +248,3 @@ topology_template:
instantiation_level_2: instantiation_level_2:
number_of_instances: 3 number_of_instances: 3
targets: [ VDU5 ] targets: [ VDU5 ]
- VDU6_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU6 ]

View File

@@ -1,63 +0,0 @@
# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import pickle
import sys
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class SampleOldCoordinateVNFScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir, k8s_info):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.k8s_info = k8s_info
def coordinate_vnf(self):
pass
def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer)
req = script_dict['request']
inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
k8s_info = script_dict['k8s_info']
script = SampleOldCoordinateVNFScript(
req, inst, grant_req, grant,
csar_dir, k8s_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__":
try:
main()
os._exit(0)
except Exception as ex:
sys.stderr.write(str(ex))
sys.stderr.flush()
os._exit(1)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2022 Fujitsu # Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -12,45 +12,50 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import functools
import os import os
import pickle import pickle
import sys import sys
from oslo_log import log as logging
LOG = logging.getLogger(__name__) class FailScript(object):
"""Define error method for each operation
For example:
class SampleOldCoordinateVNFScript(object): 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, k8s_info): def __init__(self, req, inst, grant_req, grant, csar_dir):
self.req = req self.req = req
self.inst = inst self.inst = inst
self.grant_req = grant_req self.grant_req = grant_req
self.grant = grant self.grant = grant
self.csar_dir = csar_dir self.csar_dir = csar_dir
self.k8s_info = k8s_info
def coordinate_vnf(self): def _fail(self, method):
pass 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(): def main():
operation = "coordinate_vnf"
script_dict = pickle.load(sys.stdin.buffer) script_dict = pickle.load(sys.stdin.buffer)
operation = script_dict['operation']
req = script_dict['request'] req = script_dict['request']
inst = script_dict['vnf_instance'] inst = script_dict['vnf_instance']
grant_req = script_dict['grant_request'] grant_req = script_dict['grant_request']
grant = script_dict['grant_response'] grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir'] csar_dir = script_dict['tmp_csar_dir']
k8s_info = script_dict['k8s_info']
script = SampleOldCoordinateVNFScript( script = FailScript(req, inst, grant_req, grant, csar_dir)
req, inst, grant_req, grant, getattr(script, operation)()
csar_dir, k8s_info)
try:
getattr(script, operation)()
except Exception:
raise Exception
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -44,6 +44,9 @@ max_sample_instantiate = paramgen.max_sample_instantiate(
auth_url, bearer_token) auth_url, bearer_token)
max_sample_terminate = paramgen.max_sample_terminate() max_sample_terminate = paramgen.max_sample_terminate()
max_sample_scale_out = paramgen.max_sample_scale_out()
max_sample_scale_in = paramgen.max_sample_scale_in()
max_sample_heal = paramgen.max_sample_heal(["replace real vnfc ids"])
# if you instantiate with only one resource # if you instantiate with only one resource
# please change vim_id to your k8s's vim id # please change vim_id to your k8s's vim id
@@ -55,6 +58,11 @@ min_sample_terminate = paramgen.min_sample_terminate()
change_vnfpkg_instantiate = paramgen.change_vnfpkg_instantiate( change_vnfpkg_instantiate = paramgen.change_vnfpkg_instantiate(
auth_url, bearer_token) auth_url, bearer_token)
error_handling_instantiate = paramgen.error_handling_instantiate(
auth_url, bearer_token)
error_handling_scale_out = paramgen.error_handling_scale_out()
error_handling_terminate = paramgen.error_handling_terminate()
with open("create_req", "w", encoding='utf-8') as f: with open("create_req", "w", encoding='utf-8') as f:
f.write(json.dumps(create_req, indent=2)) f.write(json.dumps(create_req, indent=2))
@@ -64,6 +72,15 @@ with open("max_sample_instantiate", "w", encoding='utf-8') as f:
with open("max_sample_terminate", "w", encoding='utf-8') as f: with open("max_sample_terminate", "w", encoding='utf-8') as f:
f.write(json.dumps(max_sample_terminate, indent=2)) f.write(json.dumps(max_sample_terminate, indent=2))
with open("max_sample_scale_out", "w", encoding='utf-8') as f:
f.write(json.dumps(max_sample_scale_out, indent=2))
with open("max_sample_scale_in", "w", encoding='utf-8') as f:
f.write(json.dumps(max_sample_scale_in, indent=2))
with open("max_sample_heal", "w", encoding='utf-8') as f:
f.write(json.dumps(max_sample_heal, indent=2))
with open("min_sample_instantiate", "w", encoding='utf-8') as f: with open("min_sample_instantiate", "w", encoding='utf-8') as f:
f.write(json.dumps(min_sample_instantiate, indent=2)) f.write(json.dumps(min_sample_instantiate, indent=2))
@@ -72,3 +89,12 @@ with open("min_sample_terminate", "w", encoding='utf-8') as f:
with open("change_vnfpkg_instantiate", "w", encoding='utf-8') as f: with open("change_vnfpkg_instantiate", "w", encoding='utf-8') as f:
f.write(json.dumps(change_vnfpkg_instantiate, indent=2)) f.write(json.dumps(change_vnfpkg_instantiate, indent=2))
with open("error_handling_instantiate", "w", encoding='utf-8') as f:
f.write(json.dumps(error_handling_instantiate, indent=2))
with open("error_handling_scale_out", "w", encoding='utf-8') as f:
f.write(json.dumps(error_handling_scale_out, indent=2))
with open("error_handling_terminate", "w", encoding='utf-8') as f:
f.write(json.dumps(error_handling_terminate, indent=2))

View File

@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ddt
import os import os
import time import time
@@ -21,7 +20,6 @@ from tacker.tests.functional.sol_kubernetes_v2 import base_v2
from tacker.tests.functional.sol_kubernetes_v2 import paramgen from tacker.tests.functional.sol_kubernetes_v2 import paramgen
@ddt.ddt
class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test): class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
@classmethod @classmethod
@@ -50,16 +48,8 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
def setUp(self): def setUp(self):
super(VnfLcmKubernetesChangeVnfpkgTest, self).setUp() super(VnfLcmKubernetesChangeVnfpkgTest, self).setUp()
def test_change_vnfpkg_for_deployment_res_with_all_params(self): def test_change_vnfpkg_for_deployment_res(self):
"""Test ChangeCurrentVNFPackage with all attributes set """Test ChangeCurrentVNFPackage
* About attributes:
All of the following cardinality attributes are set.
In addition, 0..N or 1..N attributes are set to 2 or more.
- 0..1 (1)
- 0..N (2 or more)
- 1
- 1..N (2 or more)
* About LCM operations: * About LCM operations:
This test includes the following operations. This test includes the following operations.
@@ -114,9 +104,6 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) self.wait_lcmocc_complete(lcmocc_id)
# check vnfc_resource_info
# TODO()
# 3. Show VNF instance # 3. Show VNF instance
additional_inst_attrs = [ additional_inst_attrs = [
'vimConnectionInfo', 'vimConnectionInfo',
@@ -128,19 +115,20 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
self.check_resp_headers_in_get(resp) self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs) self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get( vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
'vnfcResourceInfo') before_resource_ids = {vnfc_info['computeResource']['resourceId']
before_resource_ids = [vnfc_info['computeResource']['resourceId'] for vnfc_info in vnfc_resource_infos}
for vnfc_info in vnfc_resource_infos] self.assertEqual(2, len(before_resource_ids))
# 4. Change Current VNF Package # 4. Change Current VNF Package
change_vnfpkg_req = paramgen.change_vnfpkg_all_params(self.vnfd_id_2) change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp) self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) self.wait_lcmocc_complete(lcmocc_id)
time.sleep(3)
# check usageState of VNF Package # check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState') usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState')
@@ -160,14 +148,14 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
self.check_resp_headers_in_get(resp) self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs) self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get( vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
'vnfcResourceInfo') after_resource_ids = {vnfc_info['computeResource']['resourceId']
after_resource_ids = [vnfc_info['computeResource']['resourceId'] for vnfc_info in vnfc_resource_infos}
for vnfc_info in vnfc_resource_infos] self.assertEqual(2, len(after_resource_ids))
self.assertNotEqual(before_resource_ids, after_resource_ids) self.assertNotEqual(before_resource_ids, after_resource_ids)
# 6. Terminate a VNF instance # 6. Terminate a VNF instance
terminate_req = paramgen.max_sample_terminate() terminate_req = paramgen.change_vnfpkg_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req) resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp) self.check_resp_headers_in_operation_task(resp)
@@ -177,7 +165,7 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
# wait a bit because there is a bit time lag between lcmocc DB # wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion. # update and terminate completion.
time.sleep(10) time.sleep(3)
# 7. Delete a VNF instance # 7. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id) resp, body = self.delete_vnf_instance(inst_id)
@@ -192,14 +180,8 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState') usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
self.assertEqual('NOT_IN_USE', usage_state) self.assertEqual('NOT_IN_USE', usage_state)
def test_change_vnfpkg_for_deployment_res_with_no_op_params(self): def test_change_vnfpkg_failed_and_rollback(self):
"""Test ChangeCurrentVNFPackage with no optional attributes """Test LCM operations error handing
* About attributes:
Omit except for required attributes.
Only the following cardinality attributes are set.
- 1
- 1..N (1)
* About LCM operations: * About LCM operations:
This test includes the following operations. This test includes the following operations.
@@ -207,9 +189,10 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
- 2. Instantiate a VNF instance - 2. Instantiate a VNF instance
- 3. Show VNF instance - 3. Show VNF instance
- 4. Change Current VNF Package - 4. Change Current VNF Package
- 5. Show VNF instance - 5. Rollback Change Current VNF Package
- 6. Terminate a VNF instance - 6. Show VNF instance
- 7. Delete a VNF instance - 7. Terminate a VNF instance
- 8. Delete a VNF instance
""" """
# 1. Create a new VNF instance resource # 1. Create a new VNF instance resource
@@ -245,8 +228,8 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
self.assertEqual('IN_USE', usage_state) self.assertEqual('IN_USE', usage_state)
# 2. Instantiate a VNF instance # 2. Instantiate a VNF instance
vim_id = self.get_k8s_vim_id() instantiate_req = paramgen.change_vnfpkg_instantiate(
instantiate_req = paramgen.change_vnfpkg_instantiate_min(vim_id) self.auth_url, self.bearer_token)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp) self.check_resp_headers_in_operation_task(resp)
@@ -265,28 +248,26 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
self.check_resp_headers_in_get(resp) self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs) self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get( vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
'vnfcResourceInfo')
before_resource_ids = [vnfc_info['computeResource']['resourceId'] before_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos] for vnfc_info in vnfc_resource_infos]
# 4. Change Current VNF Package # 4. Change Current VNF Package (will fail)
change_vnfpkg_req = paramgen.change_vnfpkg_min(self.vnfd_id_2) change_vnfpkg_req = paramgen.change_vnfpkg_error(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp) self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) self.wait_lcmocc_failed_temp(lcmocc_id)
# check usageState of VNF Package # 5. Rollback Change Current VNF Package operation
usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState') resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual('NOT_IN_USE', usage_state) self.assertEqual(202, resp.status_code)
# check usageState of VNF Package self.check_resp_headers_in_delete(resp)
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState') self.wait_lcmocc_rolled_back(lcmocc_id)
self.assertEqual('IN_USE', usage_state)
# 5. Show VNF instance # 6. Show VNF instance
additional_inst_attrs = [ additional_inst_attrs = [
'vimConnectionInfo', 'vimConnectionInfo',
'instantiatedVnfInfo' 'instantiatedVnfInfo'
@@ -297,14 +278,13 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
self.check_resp_headers_in_get(resp) self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs) self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get( vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
'vnfcResourceInfo')
after_resource_ids = [vnfc_info['computeResource']['resourceId'] after_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos] for vnfc_info in vnfc_resource_infos]
self.assertNotEqual(before_resource_ids, after_resource_ids) self.assertEqual(before_resource_ids, after_resource_ids)
# 6. Terminate a VNF instance # 7. Terminate a VNF instance
terminate_req = paramgen.max_sample_terminate() terminate_req = paramgen.change_vnfpkg_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req) resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp) self.check_resp_headers_in_operation_task(resp)
@@ -314,9 +294,9 @@ class VnfLcmKubernetesChangeVnfpkgTest(base_v2.BaseVnfLcmKubernetesV2Test):
# wait a bit because there is a bit time lag between lcmocc DB # wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion. # update and terminate completion.
time.sleep(10) time.sleep(3)
# 7. Delete a VNF instance # 8. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id) resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code) self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp) self.check_resp_headers_in_delete(resp)

View File

@@ -60,8 +60,14 @@ class VnfLcmKubernetesTest(base_v2.BaseVnfLcmKubernetesV2Test):
- 1. Create a new VNF instance resource - 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance - 2. Instantiate a VNF instance
- 3. Show VNF instance - 3. Show VNF instance
- 4. Terminate a VNF instance - 4. Scale out a VNF instance
- 5. Delete a VNF instance - 5. Show VNF instance
- 6. Scale in a VNF instance
- 7. Show VNF instance
- 8. Heal in a VNF instance
- 9. Show VNF instance
- 10. Terminate a VNF instance
- 11. Delete a VNF instance
""" """
# 1. Create a new VNF instance resource # 1. Create a new VNF instance resource
@@ -118,26 +124,99 @@ class VnfLcmKubernetesTest(base_v2.BaseVnfLcmKubernetesV2Test):
self.check_resp_body(body, expected_inst_attrs) self.check_resp_body(body, expected_inst_attrs)
# check vnfc_resource_info # check vnfc_resource_info
vnfc_resource_infos = body['instantiatedVnfInfo'].get( vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
'vnfcResourceInfo') vdu_nums = {'VDU1': 0, 'VDU2': 0, 'VDU3': 0, 'VDU5': 0, 'VDU6': 0}
for vnfc_info in vnfc_resource_infos: for vnfc_info in vnfc_resource_infos:
if vnfc_info['vduId'] == 'VDU1': if vnfc_info['vduId'] == 'VDU1':
self.assertEqual('Pod', vnfc_info[ self.assertEqual('Pod', vnfc_info[
'computeResource']['vimLevelResourceType']) 'computeResource']['vimLevelResourceType'])
vdu_nums['VDU1'] += 1
elif vnfc_info['vduId'] == 'VDU2': elif vnfc_info['vduId'] == 'VDU2':
self.assertEqual('Deployment', vnfc_info[ self.assertEqual('Deployment', vnfc_info[
'computeResource']['vimLevelResourceType']) 'computeResource']['vimLevelResourceType'])
vdu_nums['VDU2'] += 1
elif vnfc_info['vduId'] == 'VDU3': elif vnfc_info['vduId'] == 'VDU3':
self.assertEqual('ReplicaSet', vnfc_info[ self.assertEqual('ReplicaSet', vnfc_info[
'computeResource']['vimLevelResourceType']) 'computeResource']['vimLevelResourceType'])
vdu_nums['VDU3'] += 1
elif vnfc_info['vduId'] == 'VDU5': elif vnfc_info['vduId'] == 'VDU5':
self.assertEqual('StatefulSet', vnfc_info[ self.assertEqual('StatefulSet', vnfc_info[
'computeResource']['vimLevelResourceType']) 'computeResource']['vimLevelResourceType'])
vdu_nums['VDU5'] += 1
elif vnfc_info['vduId'] == 'VDU6': elif vnfc_info['vduId'] == 'VDU6':
self.assertEqual('DaemonSet', vnfc_info[ self.assertEqual('DaemonSet', vnfc_info[
'computeResource']['vimLevelResourceType']) 'computeResource']['vimLevelResourceType'])
vdu_nums['VDU6'] += 1
expected = {'VDU1': 1, 'VDU2': 2, 'VDU3': 1, 'VDU5': 1, 'VDU6': 1}
self.assertEqual(expected, vdu_nums)
# 4. Terminate a VNF instance # 4. Scale out a VNF instance
scale_out_req = paramgen.max_sample_scale_out()
resp, body = self.scale_vnf_instance(inst_id, scale_out_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 5. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
# check vnfc_resource_info
vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
vdu3_infos = [vnfc_info for vnfc_info in vnfc_resource_infos
if vnfc_info['vduId'] == 'VDU3']
self.assertEqual(3, len(vdu3_infos))
# 6. Scale in a VNF instance
scale_in_req = paramgen.max_sample_scale_in()
resp, body = self.scale_vnf_instance(inst_id, scale_in_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 7. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
# check vnfc_resource_info
vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
vdu3_infos = [vnfc_info for vnfc_info in vnfc_resource_infos
if vnfc_info['vduId'] == 'VDU3']
self.assertEqual(2, len(vdu3_infos))
# 8. Heal a VNF instance
vnfc_infos = body['instantiatedVnfInfo']['vnfcInfo']
vdu2_ids = [vnfc_info['id'] for vnfc_info in vnfc_infos
if vnfc_info['vduId'] == 'VDU2']
target = [vdu2_ids[0]]
heal_req = paramgen.max_sample_heal(target)
resp, body = self.heal_vnf_instance(inst_id, heal_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 9. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
# check vnfc_resource_info
vnfc_infos = body['instantiatedVnfInfo']['vnfcInfo']
result_vdu2_ids = [vnfc_info['id'] for vnfc_info in vnfc_infos
if vnfc_info['vduId'] == 'VDU2']
self.assertEqual(2, len(result_vdu2_ids))
self.assertNotIn(vdu2_ids[0], result_vdu2_ids)
self.assertIn(vdu2_ids[1], result_vdu2_ids)
# 10. Terminate a VNF instance
terminate_req = paramgen.max_sample_terminate() terminate_req = paramgen.max_sample_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req) resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
@@ -148,9 +227,9 @@ class VnfLcmKubernetesTest(base_v2.BaseVnfLcmKubernetesV2Test):
# wait a bit because there is a bit time lag between lcmocc DB # wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion. # update and terminate completion.
time.sleep(10) time.sleep(3)
# 5. Delete a VNF instance # 11. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id) resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code) self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp) self.check_resp_headers_in_delete(resp)
@@ -250,7 +329,7 @@ class VnfLcmKubernetesTest(base_v2.BaseVnfLcmKubernetesV2Test):
# wait a bit because there is a bit time lag between lcmocc DB # wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion. # update and terminate completion.
time.sleep(10) time.sleep(3)
# 5. Delete a VNF instance # 5. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id) resp, body = self.delete_vnf_instance(inst_id)
@@ -264,3 +343,152 @@ class VnfLcmKubernetesTest(base_v2.BaseVnfLcmKubernetesV2Test):
# check usageState of VNF Package # check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState') usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState')
self.assertEqual('NOT_IN_USE', usage_state) self.assertEqual('NOT_IN_USE', usage_state)
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 test_instantiate_rollback(self):
"""Test LCM operations with all attributes set
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance => FAILED_TEMP
- 3. Show VNF instance
- 4. Rollback instantiate
- 5. Show VNF instance
- 6. Delete a VNF instance
"""
# 1. Create a new VNF instance resource
create_req = paramgen.test_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
inst_id = body['id']
# 2. Instantiate a VNF instance
self._put_fail_file('instantiate_end')
instantiate_req = paramgen.error_handling_instantiate(
self.auth_url, self.bearer_token)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
self._rm_fail_file('instantiate_end')
# 3. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.assertEqual('NOT_INSTANTIATED', body['instantiationState'])
# 4. Rollback instantiate
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
# 5. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.assertEqual('NOT_INSTANTIATED', body['instantiationState'])
# 6. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
def test_scale_out_rollback(self):
"""Test LCM operations with all attributes set
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. Show VNF instance
- 4. Scale out ==> FAILED_TEMP
- 5. Rollback
- 5. Show VNF instance
- 6. Terminate a VNF instance
- 7. Delete a VNF instance
"""
# 1. Create a new VNF instance resource
create_req = paramgen.test_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
inst_id = body['id']
# 2. Instantiate a VNF instance
instantiate_req = paramgen.error_handling_instantiate(
self.auth_url, self.bearer_token)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 3. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
vdu2_ids_0 = {vnfc_info['id'] for vnfc_info in vnfc_resource_infos
if vnfc_info['vduId'] == 'VDU2'}
self.assertEqual(2, len(vdu2_ids_0))
# 4. Scale out a VNF instance
self._put_fail_file('scale_end')
scale_out_req = paramgen.error_handling_scale_out()
resp, body = self.scale_vnf_instance(inst_id, scale_out_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
self._rm_fail_file('scale_end')
# 5. Rollback instantiate
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
# 6. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
vdu2_ids_1 = {vnfc_info['id'] for vnfc_info in vnfc_resource_infos
if vnfc_info['vduId'] == 'VDU2'}
self.assertEqual(vdu2_ids_0, vdu2_ids_1)
# 7. Terminate a VNF instance
terminate_req = paramgen.error_handling_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(3)
# 8. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)

View File

@@ -1,474 +0,0 @@
# Copyright (C) 2022 FUJITSU
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import os
import time
import unittest
from tacker.tests.functional.sol_kubernetes_v2 import base_v2
from tacker.tests.functional.sol_kubernetes_v2 import paramgen
@ddt.ddt
class VnfLcmKubernetesErrorHandingTest(base_v2.BaseVnfLcmKubernetesV2Test):
@classmethod
def setUpClass(cls):
super(VnfLcmKubernetesErrorHandingTest, cls).setUpClass()
cur_dir = os.path.dirname(__file__)
test_instantiate_cnf_resources_path = os.path.join(
cur_dir, "samples/test_instantiate_cnf_resources")
cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package(
test_instantiate_cnf_resources_path)
test_change_vnf_pkg_with_deployment_path = os.path.join(
cur_dir, "samples/test_change_vnf_pkg_with_deployment")
cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package(
test_change_vnf_pkg_with_deployment_path)
@classmethod
def tearDownClass(cls):
super(VnfLcmKubernetesErrorHandingTest, cls).tearDownClass()
cls.delete_vnf_package(cls.vnf_pkg_1)
cls.delete_vnf_package(cls.vnf_pkg_2)
def setUp(self):
super(VnfLcmKubernetesErrorHandingTest, self).setUp()
@unittest.skip("Until refactor CNF v2 API")
def test_change_vnfpkg_failed_in_update_wait_and_rollback(self):
"""Test LCM operations error handing
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. Show VNF instance
- 4. Change Current VNF Package
- 5. Rollback Change Current VNF Package
- 6. Show VNF instance
- 7. Terminate a VNF instance
- 8. Delete a VNF instance
"""
# 1. Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
create_req = paramgen.test_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
# check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState']
self.assertEqual('IN_USE', usage_state)
# 2. Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.change_vnfpkg_instantiate_error_handing(
vim_id)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 3. Show VNF instance
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get(
'vnfcResourceInfo')
before_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos]
# 4. Change Current VNF Package (will fail)
change_vnfpkg_req = paramgen.change_vnfpkg_error(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
# 5. Rollback Change Current VNF Package operation
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_delete(resp)
self.wait_lcmocc_rolled_back(lcmocc_id)
# 6. Show VNF instance
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get(
'vnfcResourceInfo')
after_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos]
self.assertNotEqual(before_resource_ids, after_resource_ids)
# 7. Terminate a VNF instance
terminate_req = paramgen.max_sample_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# 8. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)
# check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
self.assertEqual('NOT_IN_USE', usage_state)
@unittest.skip("Until refactor CNF v2 API")
def test_change_vnfpkg_failed_and_retry(self):
"""Test LCM operations error handing
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. Show VNF instance
- 4. Change Current VNF Package(will fail)
- 5. Retry Change Current VNF Package
- 6. Rollback Change Current VNF Package
- 7. Show VNF instance
- 8. Terminate a VNF instance
- 9. Delete a VNF instance
"""
# 1. Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
create_req = paramgen.test_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
# check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState']
self.assertEqual('IN_USE', usage_state)
# 2. Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.change_vnfpkg_instantiate_error_handing(
vim_id)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 3. Show VNF instance
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get(
'vnfcResourceInfo')
before_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos]
# 4. Change Current VNF Package (will fail)
change_vnfpkg_req = paramgen.change_vnfpkg_error(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
# 5. Retry Change Current VNF Package operation
resp, body = self.retry_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_delete(resp)
self.wait_lcmocc_failed_temp(lcmocc_id)
# 6. Rollback Change Current VNF Package operation
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_delete(resp)
self.wait_lcmocc_rolled_back(lcmocc_id)
# 7. Show VNF instance
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get(
'vnfcResourceInfo')
after_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos]
self.assertNotEqual(before_resource_ids, after_resource_ids)
# 8. Terminate a VNF instance
terminate_req = paramgen.max_sample_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# 9. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)
# check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
self.assertEqual('NOT_IN_USE', usage_state)
def test_change_vnfpkg_failed_and_fail(self):
"""Test LCM operations error handing
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. Show VNF instance
- 4. Change Current VNF Package
- 5. Fail Change Current VNF Package
- 6. Show VNF instance
- 7. Terminate VNF instance
- 8. Delete a VNF instance
"""
# 1. Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
expected_inst_attrs = [
'id',
'vnfInstanceName',
'vnfInstanceDescription',
'vnfdId',
'vnfProvider',
'vnfProductName',
'vnfSoftwareVersion',
'vnfdVersion',
# 'vnfConfigurableProperties', # omitted
# 'vimConnectionInfo', # omitted
'instantiationState',
# 'instantiatedVnfInfo', # omitted
'metadata',
# 'extensions', # omitted
'_links'
]
create_req = paramgen.test_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, expected_inst_attrs)
inst_id = body['id']
# check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState']
self.assertEqual('IN_USE', usage_state)
# 2. Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.change_vnfpkg_instantiate_error_handing(
vim_id)
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 3. Show VNF instance
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get(
'vnfcResourceInfo')
before_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos]
# 4. Change Current VNF Package (will fail)
change_vnfpkg_req = paramgen.change_vnfpkg_error(self.vnfd_id_2)
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_failed_temp(lcmocc_id)
# 5. Fail Change Current VNF Package operation
expected_inst_attrs_fail = [
'id',
'operationState',
'stateEnteredTime',
'startTime',
'vnfInstanceId',
'grantId',
'operation',
'isAutomaticInvocation',
'operationParams',
'isCancelPending',
'error',
'_links'
]
resp, body = self.fail_lcmocc(lcmocc_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs_fail)
resp, body = self.show_lcmocc(lcmocc_id)
self.assertEqual(200, resp.status_code)
self.assertEqual('FAILED', body['operationState'])
# 6. Show VNF instance
additional_inst_attrs = [
'vimConnectionInfo',
'instantiatedVnfInfo'
]
expected_inst_attrs.extend(additional_inst_attrs)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, expected_inst_attrs)
vnfc_resource_infos = body['instantiatedVnfInfo'].get(
'vnfcResourceInfo')
after_resource_ids = [vnfc_info['computeResource']['resourceId']
for vnfc_info in vnfc_resource_infos]
self.assertEqual(before_resource_ids, after_resource_ids)
# 7. Terminate a VNF instance
terminate_req = paramgen.max_sample_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# 8. Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)
# check usageState of VNF Package
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
self.assertEqual('NOT_IN_USE', usage_state)