Helm chart support for CNF v2 API
This patch enables CNF v2 API to operate using Helm chart. New vimType 'ETSINFV.HELM.V_3' is introduced. Since helm VIM uses existing functions of k8s VIM, k8s VIM code is refactored to share between k8s VIM and helm VIM. Implements: blueprint helmchart-k8s-vim Change-Id: I0329a0d43294181b7ffb1494bb5dd2d0528eb5dc
This commit is contained in:
parent
4467f162c0
commit
bb7b3cfce9
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Support LCM operations using Helm chart
|
||||
for v2 LCM API. This feature enables
|
||||
v2 LCM operations with Helm chart,
|
||||
instantiationLevel parameter to
|
||||
determine the initial number of Pods,
|
||||
and vimConnectionInfo.extra field.
|
@ -104,3 +104,24 @@
|
||||
- inventory_hostname == 'controller'
|
||||
- helm_version is defined
|
||||
|
||||
- block:
|
||||
- name: Download Helm
|
||||
get_url:
|
||||
url: "https://get.helm.sh/helm-v{{ helm_version }}-linux-amd64.tar.gz"
|
||||
dest: "/tmp/helm-v{{ helm_version }}-linux-amd64.tar.gz"
|
||||
force: yes
|
||||
|
||||
- name: Unarchive Helm
|
||||
unarchive:
|
||||
src: "/tmp/helm-v{{ helm_version }}-linux-amd64.tar.gz"
|
||||
dest: "/tmp"
|
||||
remote_src: yes
|
||||
become: yes
|
||||
|
||||
- name: Move Helm binary
|
||||
shell: mv /tmp/linux-amd64/helm /usr/local/bin/helm
|
||||
become: yes
|
||||
|
||||
when:
|
||||
- inventory_hostname == 'controller-tacker'
|
||||
- helm_version is defined
|
||||
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tacker._i18n import _
|
||||
|
||||
|
||||
class SolException(Exception):
|
||||
"""Exception for SOL ProblemDetails
|
||||
@ -361,3 +363,12 @@ class K8sInvalidManifestFound(SolHttpError400):
|
||||
class OIDCAuthFailed(SolHttpError400):
|
||||
message = _("OIDC authentication and authorization failed."
|
||||
" Detail: %(detail)s")
|
||||
|
||||
|
||||
class HelmOperationFailed(SolHttpError422):
|
||||
title = 'Helm operation failed'
|
||||
# detail set in the code
|
||||
|
||||
|
||||
class HelmParameterNotFound(SolHttpError400):
|
||||
message = _("Helm parameter for scale vdu %(vdu_name)s is not found.")
|
||||
|
@ -100,10 +100,14 @@ def vim_to_conn_info(vim):
|
||||
if 'ssl_ca_cert' in vim_auth.keys():
|
||||
interface_info['ssl_ca_cert'] = vim_auth['ssl_ca_cert']
|
||||
|
||||
vim_type = ('ETSINFV.HELM.V_3'
|
||||
if vim.get('extra', {}).get('use_helm', False)
|
||||
else 'kubernetes')
|
||||
return objects.VimConnectionInfo(
|
||||
vimId=vim['vim_id'],
|
||||
vimType='kubernetes',
|
||||
vimType=vim_type,
|
||||
interfaceInfo=interface_info,
|
||||
accessInfo=access_info
|
||||
)
|
||||
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
|
@ -25,6 +25,7 @@ from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils
|
||||
from tacker.sol_refactored.common import vim_utils
|
||||
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import helm
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes
|
||||
from tacker.sol_refactored.infra_drivers.openstack import openstack
|
||||
from tacker.sol_refactored.nfvo import nfvo_client
|
||||
@ -398,11 +399,28 @@ class VnfLcmDriverV2(object):
|
||||
# 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(
|
||||
if vim_info.vimType == "kubernetes":
|
||||
if 'endpoint' not in vim_info.interfaceInfo:
|
||||
detail = "Required attribute missing in vimConnectionInfo"
|
||||
raise sol_ex.SolValidationError(detail=detail)
|
||||
if (not req.get('additionalParams', {}).get(
|
||||
'lcm-kubernetes-def-files')):
|
||||
raise sol_ex.SolValidationError(
|
||||
detail="'lcm-kubernetes-def-files' must be specified")
|
||||
raise sol_ex.SolValidationError(
|
||||
detail="'lcm-kubernetes-def-files' must be specified")
|
||||
elif vim_info.vimType == "ETSINFV.HELM.V_3":
|
||||
if ('endpoint' not in vim_info.interfaceInfo or
|
||||
'ssl_ca_cert' not in vim_info.interfaceInfo or
|
||||
'bearer_token' not in vim_info.accessInfo):
|
||||
detail = "Required attribute missing in vimConnectionInfo"
|
||||
raise sol_ex.SolValidationError(detail=detail)
|
||||
if (not req.get('additionalParams', {}).get(
|
||||
'helm_chart_path')):
|
||||
raise sol_ex.SolValidationError(
|
||||
detail="'helm_chart_path' must be specified")
|
||||
if (not req.get('additionalParams', {}).get(
|
||||
'helm_value_names')):
|
||||
raise sol_ex.SolValidationError(
|
||||
detail="'helm_value_names' must be specified")
|
||||
|
||||
def instantiate_process(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
@ -417,6 +435,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.instantiate(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.instantiate(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -433,6 +454,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -575,6 +599,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.terminate(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.terminate(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -688,6 +715,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.scale(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.scale(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -705,6 +735,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.scale_rollback(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.scale_rollback(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -887,6 +920,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.heal(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.heal(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -1055,6 +1091,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'kubernetes':
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.change_vnfpkg(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.change_vnfpkg(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -1073,6 +1112,9 @@ class VnfLcmDriverV2(object):
|
||||
driver = kubernetes.Kubernetes()
|
||||
driver.change_vnfpkg_rollback(
|
||||
req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.change_vnfpkg_rollback(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
|
340
tacker/sol_refactored/infra_drivers/kubernetes/helm.py
Normal file
340
tacker/sol_refactored/infra_drivers/kubernetes/helm.py
Normal file
@ -0,0 +1,340 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_common
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_resource
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Helm(kubernetes_common.KubernetesCommon):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def instantiate(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
self._instantiate(req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client)
|
||||
|
||||
def _instantiate(self, req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client):
|
||||
|
||||
namespace = req.additionalParams.get('namespace', 'default')
|
||||
helm_chart_path = req.additionalParams['helm_chart_path']
|
||||
chart_name = os.path.join(vnfd.csar_dir, helm_chart_path)
|
||||
release_name = self._get_release_name(inst)
|
||||
helm_value_names = req.additionalParams['helm_value_names']
|
||||
|
||||
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
|
||||
grant_req.addResources)
|
||||
|
||||
# Create parameters
|
||||
parameters = req.additionalParams.get('helm_parameters', {})
|
||||
for vdu_name, vdu_num in vdus_num.items():
|
||||
replicaParam = helm_value_names.get(vdu_name, {}).get('replica')
|
||||
if replicaParam:
|
||||
parameters[replicaParam] = vdu_num
|
||||
|
||||
if helm_client.is_release_exist(release_name, namespace):
|
||||
# helm upgrade. It is retry case.
|
||||
revision = helm_client.upgrade(release_name, chart_name,
|
||||
namespace, parameters)
|
||||
else:
|
||||
# helm install
|
||||
revision = helm_client.install(release_name, chart_name,
|
||||
namespace, parameters)
|
||||
|
||||
# get manifest from helm chart
|
||||
k8s_resources = helm_client.get_manifest(release_name, namespace)
|
||||
k8s_reses = self._create_reses_from_manifest(k8s_api_client, namespace,
|
||||
k8s_resources)
|
||||
vdu_reses = self._select_vdu_reses(vnfd, req.flavourId, k8s_reses)
|
||||
|
||||
# wait k8s resource create complete
|
||||
self._wait_k8s_reses_ready(k8s_reses)
|
||||
|
||||
# make instantiated info
|
||||
self._init_instantiated_vnf_info(inst, req.flavourId, vdu_reses,
|
||||
namespace, helm_chart_path, helm_value_names, release_name,
|
||||
revision)
|
||||
self._update_vnfc_info(inst, k8s_api_client)
|
||||
|
||||
def instantiate_rollback(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
namespace = req.additionalParams.get('namespace', 'default')
|
||||
release_name = self._get_release_name(inst)
|
||||
|
||||
self._delete_resource(release_name, namespace,
|
||||
k8s_api_client, helm_client)
|
||||
|
||||
def terminate(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
namespace = inst.instantiatedVnfInfo.metadata['namespace']
|
||||
release_name = inst.instantiatedVnfInfo.metadata['release_name']
|
||||
|
||||
self._delete_resource(release_name, namespace,
|
||||
k8s_api_client, helm_client)
|
||||
|
||||
def _delete_resource(self, release_name, namespace, k8s_api_client,
|
||||
helm_client):
|
||||
if not helm_client.is_release_exist(release_name, namespace):
|
||||
LOG.info(f'HELM release {release_name} is not exist.')
|
||||
return
|
||||
|
||||
# get k8s manifest from helm chart
|
||||
k8s_resources = helm_client.get_manifest(release_name, namespace)
|
||||
k8s_reses = self._create_reses_from_manifest(k8s_api_client,
|
||||
namespace, k8s_resources)
|
||||
|
||||
# uninstall release
|
||||
helm_client.uninstall(release_name, namespace)
|
||||
|
||||
# wait k8s resource delete complete
|
||||
self._wait_k8s_reses_deleted(k8s_reses)
|
||||
|
||||
def scale(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
self._scale(req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client)
|
||||
|
||||
def _scale(self, req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client):
|
||||
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)
|
||||
|
||||
metadata = inst.instantiatedVnfInfo.metadata
|
||||
namespace = metadata['namespace']
|
||||
release_name = metadata['release_name']
|
||||
chart_name = os.path.join(vnfd.csar_dir, metadata['helm_chart_path'])
|
||||
helm_value_names = metadata['helm_value_names']
|
||||
|
||||
# Create scale parameters
|
||||
parameters = {}
|
||||
for vdu_name, vdu_num in vdus_num.items():
|
||||
replicaParam = helm_value_names.get(vdu_name, {}).get('replica')
|
||||
if not replicaParam:
|
||||
raise sol_ex.HelmParameterNotFound(vdu_name=vdu_name)
|
||||
parameters[replicaParam] = vdu_num
|
||||
|
||||
# update
|
||||
revision = helm_client.upgrade(release_name, chart_name,
|
||||
namespace, parameters)
|
||||
|
||||
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)
|
||||
|
||||
# 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)
|
||||
metadata['revision'] = revision
|
||||
|
||||
def scale_rollback(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
self._scale_rollback(req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client)
|
||||
|
||||
def _scale_rollback(self, req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client):
|
||||
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
|
||||
grant_req.addResources)
|
||||
|
||||
metadata = inst.instantiatedVnfInfo.metadata
|
||||
namespace = metadata['namespace']
|
||||
release_name = metadata['release_name']
|
||||
revision = metadata['revision']
|
||||
|
||||
# rollback
|
||||
helm_client.rollback(release_name, revision, namespace)
|
||||
|
||||
vdu_reses = [self._get_vdu_res(inst, k8s_api_client, vdu_name)
|
||||
for vdu_name in vdus_num]
|
||||
|
||||
# wait k8s resource create complete
|
||||
self._wait_k8s_reses_updated(vdu_reses, k8s_api_client,
|
||||
namespace, old_pods_names=set())
|
||||
|
||||
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
if req.additionalParams['upgrade_type'] == 'RollingUpdate':
|
||||
self._change_vnfpkg_rolling_update(req, inst, grant_req,
|
||||
grant, vnfd, k8s_api_client, helm_client)
|
||||
else:
|
||||
# not reach here
|
||||
pass
|
||||
|
||||
def _change_vnfpkg_rolling_update(self, req, inst, grant_req, grant, vnfd,
|
||||
k8s_api_client, helm_client):
|
||||
metadata = inst.instantiatedVnfInfo.metadata
|
||||
namespace = metadata['namespace']
|
||||
release_name = metadata['release_name']
|
||||
helm_chart_path = req.additionalParams.get('helm_chart_path',
|
||||
metadata['helm_chart_path'])
|
||||
chart_name = os.path.join(vnfd.csar_dir, helm_chart_path)
|
||||
|
||||
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
|
||||
grant_req.addResources)
|
||||
|
||||
# update
|
||||
revision = helm_client.upgrade(release_name, chart_name,
|
||||
namespace, {})
|
||||
|
||||
# get manifest from helm chart
|
||||
k8s_resources = helm_client.get_manifest(release_name, namespace)
|
||||
k8s_reses = self._create_reses_from_manifest(
|
||||
k8s_api_client, namespace, k8s_resources)
|
||||
vdu_reses = self._select_vdu_reses(
|
||||
vnfd, inst.instantiatedVnfInfo.flavourId, k8s_reses)
|
||||
|
||||
# wait k8s resource update complete
|
||||
target_reses = {vdu: res for vdu, res in vdu_reses.items()
|
||||
if vdu in vdus_num.keys()}
|
||||
old_pods_names = {vnfc.computeResource.resourceId
|
||||
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo
|
||||
if vnfc.vduId in vdus_num.keys()}
|
||||
|
||||
self._wait_k8s_reses_updated(
|
||||
list(target_reses.values()), k8s_api_client, namespace,
|
||||
old_pods_names)
|
||||
|
||||
# make instantiated info
|
||||
self._update_vnfc_info(inst, k8s_api_client)
|
||||
metadata['vdu_reses'].update(
|
||||
{vdu: res.body for vdu, res in target_reses.items()})
|
||||
metadata['helm_chart_path'] = helm_chart_path
|
||||
metadata['revision'] = revision
|
||||
inst.vnfdId = req.vnfdId
|
||||
|
||||
def change_vnfpkg_rollback(self, req, inst, grant_req, grant, vnfd):
|
||||
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()
|
||||
helm_client = acm.init_helm_client()
|
||||
if req.additionalParams['upgrade_type'] == 'RollingUpdate':
|
||||
self._change_vnfpkg_rolling_update_rollback(
|
||||
req, inst, grant_req, grant, vnfd, k8s_api_client,
|
||||
helm_client)
|
||||
else:
|
||||
# not reach here
|
||||
pass
|
||||
|
||||
def _change_vnfpkg_rolling_update_rollback(self, req, inst, grant_req,
|
||||
grant, vnfd, k8s_api_client, helm_client):
|
||||
metadata = inst.instantiatedVnfInfo.metadata
|
||||
namespace = metadata['namespace']
|
||||
release_name = metadata['release_name']
|
||||
revision = metadata['revision']
|
||||
|
||||
original_pods = {vnfc.computeResource.resourceId for vnfc in
|
||||
inst.instantiatedVnfInfo.vnfcResourceInfo}
|
||||
all_pods = kubernetes_utils.list_namespaced_pods(
|
||||
k8s_api_client, namespace)
|
||||
current_pods = {pod.metadata.name for pod in all_pods}
|
||||
old_pods_names = current_pods - original_pods
|
||||
|
||||
# rollback
|
||||
helm_client.rollback(release_name, revision, namespace)
|
||||
|
||||
target_vdus = {res_def.resourceTemplateId
|
||||
for res_def in grant_req.addResources
|
||||
if res_def.type == 'COMPUTE'}
|
||||
target_reses = [self._get_vdu_res(inst, k8s_api_client, vdu_name)
|
||||
for vdu_name in target_vdus]
|
||||
|
||||
# wait k8s resource update complete
|
||||
self._wait_k8s_reses_updated(
|
||||
target_reses, k8s_api_client, namespace, old_pods_names)
|
||||
|
||||
# make instantiated info
|
||||
self._update_vnfc_info(inst, k8s_api_client)
|
||||
|
||||
def _create_reses_from_manifest(self, k8s_api_client, namespace,
|
||||
k8s_resources):
|
||||
for k8s_res in k8s_resources:
|
||||
if k8s_res['kind'] in kubernetes_utils.SUPPORTED_NAMESPACE_KINDS:
|
||||
k8s_res.setdefault('metadata', {})
|
||||
k8s_res['metadata'].setdefault('namespace', namespace)
|
||||
|
||||
k8s_reses = []
|
||||
for k8s_res in k8s_resources:
|
||||
try:
|
||||
cls = getattr(kubernetes_resource, k8s_res['kind'])
|
||||
k8s_reses.append(cls(k8s_api_client, k8s_res))
|
||||
except AttributeError:
|
||||
LOG.info("Not support kind %s. ignored.", k8s_res['kind'])
|
||||
|
||||
return k8s_reses
|
||||
|
||||
def _get_release_name(self, inst):
|
||||
return f'vnf-{inst.id}'
|
||||
|
||||
def _init_instantiated_vnf_info(self, inst, flavour_id, vdu_reses,
|
||||
namespace, helm_chart_path, helm_value_names, release_name,
|
||||
revision):
|
||||
super()._init_instantiated_vnf_info(inst, flavour_id, vdu_reses,
|
||||
namespace)
|
||||
inst.instantiatedVnfInfo.metadata.update(
|
||||
{
|
||||
'helm_chart_path': helm_chart_path,
|
||||
'helm_value_names': helm_value_names,
|
||||
'release_name': release_name,
|
||||
'revision': revision
|
||||
}
|
||||
)
|
||||
|
||||
def _is_match_pod_naming_rule(self, rsc_kind, rsc_name, pod_name):
|
||||
return rsc_name in pod_name
|
100
tacker/sol_refactored/infra_drivers/kubernetes/helm_utils.py
Normal file
100
tacker/sol_refactored/infra_drivers/kubernetes/helm_utils.py
Normal file
@ -0,0 +1,100 @@
|
||||
# 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 subprocess
|
||||
import yaml
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
HELM_INSTALL_TIMEOUT = "120s"
|
||||
|
||||
|
||||
class HelmClient():
|
||||
|
||||
def __init__(self, helm_auth_params):
|
||||
self.helm_auth_params = helm_auth_params
|
||||
|
||||
def _execute_command(self, helm_command, raise_ex=True):
|
||||
helm_command.extend(self.helm_auth_params)
|
||||
result = subprocess.run(helm_command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True)
|
||||
if raise_ex and result.returncode != 0:
|
||||
raise sol_ex.HelmOperationFailed(sol_detail=str(result))
|
||||
return result
|
||||
|
||||
def _get_revision(self, result):
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'REVISION' in line:
|
||||
revision = line.split()[-1]
|
||||
return revision
|
||||
|
||||
def is_release_exist(self, release_name, namespace):
|
||||
# execute helm status command
|
||||
helm_command = ["helm", "status", release_name, "--namespace",
|
||||
namespace]
|
||||
result = self._execute_command(helm_command, False)
|
||||
return result.returncode == 0
|
||||
|
||||
def install(self, release_name, chart_name, namespace, parameters):
|
||||
# execute helm install command
|
||||
helm_command = ["helm", "install", release_name, chart_name,
|
||||
"--namespace", namespace, "--create-namespace"]
|
||||
if parameters:
|
||||
set_params = ','.join([f"{key}={value}"
|
||||
for key, value in parameters.items()])
|
||||
helm_command.extend(["--set", set_params])
|
||||
helm_command.extend(["--timeout", HELM_INSTALL_TIMEOUT])
|
||||
result = self._execute_command(helm_command)
|
||||
|
||||
return self._get_revision(result)
|
||||
|
||||
def upgrade(self, release_name, chart_name, namespace, parameters):
|
||||
# execute helm install command
|
||||
helm_command = ["helm", "upgrade", release_name, chart_name,
|
||||
"--namespace", namespace, "--reuse-values"]
|
||||
if parameters:
|
||||
set_params = ','.join([f"{key}={value}"
|
||||
for key, value in parameters.items()])
|
||||
helm_command.extend(["--set", set_params])
|
||||
helm_command.extend(["--timeout", HELM_INSTALL_TIMEOUT])
|
||||
result = self._execute_command(helm_command)
|
||||
|
||||
return self._get_revision(result)
|
||||
|
||||
def uninstall(self, release_name, namespace):
|
||||
# execute helm uninstall command
|
||||
helm_command = ["helm", "uninstall", release_name, "--namespace",
|
||||
namespace, "--timeout", HELM_INSTALL_TIMEOUT]
|
||||
self._execute_command(helm_command)
|
||||
|
||||
def get_manifest(self, release_name, namespace):
|
||||
# execute helm get manifest command
|
||||
helm_command = ["helm", "get", "manifest", release_name,
|
||||
"--namespace", namespace]
|
||||
result = self._execute_command(helm_command)
|
||||
|
||||
return list(yaml.safe_load_all(result.stdout))
|
||||
|
||||
def rollback(self, release_name, revision_no, namespace):
|
||||
# execute helm get manifest command
|
||||
helm_command = ["helm", "rollback", release_name, revision_no,
|
||||
"--namespace", namespace]
|
||||
self._execute_command(helm_command)
|
@ -13,16 +13,16 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
from kubernetes import client
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
|
||||
from tacker.sol_refactored.common import config
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_resource
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_common
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_utils
|
||||
from tacker.sol_refactored import objects
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -30,11 +30,10 @@ LOG = logging.getLogger(__name__)
|
||||
CONF = config.CONF
|
||||
CHECK_INTERVAL = 10
|
||||
|
||||
TARGET_KIND = {"Pod", "Deployment", "DaemonSet", "StatefulSet", "ReplicaSet"}
|
||||
SCALABLE_KIND = {"Deployment", "ReplicaSet", "StatefulSet"}
|
||||
|
||||
|
||||
class Kubernetes(object):
|
||||
class Kubernetes(kubernetes_common.KubernetesCommon):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
@ -75,8 +74,8 @@ class Kubernetes(object):
|
||||
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._init_instantiated_vnf_info(inst, req.flavourId, vdu_reses,
|
||||
namespace, target_k8s_files)
|
||||
self._update_vnfc_info(inst, k8s_api_client)
|
||||
|
||||
def _setup_k8s_reses(self, vnfd, target_k8s_files, k8s_api_client,
|
||||
@ -207,8 +206,8 @@ class Kubernetes(object):
|
||||
vnfd, inst.instantiatedVnfInfo.flavourId, k8s_reses)
|
||||
|
||||
self._init_instantiated_vnf_info(
|
||||
inst, inst.instantiatedVnfInfo.flavourId, target_k8s_files,
|
||||
vdu_reses, namespace)
|
||||
inst, inst.instantiatedVnfInfo.flavourId, vdu_reses,
|
||||
namespace, target_k8s_files)
|
||||
|
||||
self._change_vnfpkg_rolling_update(
|
||||
inst, grant_req, grant, vnfd, k8s_api_client, namespace,
|
||||
@ -245,53 +244,6 @@ class Kubernetes(object):
|
||||
# not reach here
|
||||
pass
|
||||
|
||||
def heal(self, req, inst, grant_req, grant, vnfd):
|
||||
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._heal(req, inst, grant_req, grant, vnfd, k8s_api_client)
|
||||
|
||||
def _heal(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
|
||||
namespace = inst.instantiatedVnfInfo.metadata['namespace']
|
||||
|
||||
# get heal Pod name
|
||||
vnfc_res_ids = [res_def.resource.resourceId
|
||||
for res_def in grant_req.removeResources
|
||||
if res_def.type == 'COMPUTE']
|
||||
|
||||
target_vnfcs = [vnfc
|
||||
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo
|
||||
if vnfc.computeResource.resourceId in vnfc_res_ids]
|
||||
|
||||
# check running Pod
|
||||
all_pods = kubernetes_utils.list_namespaced_pods(
|
||||
k8s_api_client, namespace)
|
||||
current_pods_name = [pod.metadata.name for pod in all_pods]
|
||||
|
||||
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']
|
||||
|
||||
@ -344,131 +296,36 @@ class Kubernetes(object):
|
||||
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_ids = {value.get('properties').get('name'): key
|
||||
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}
|
||||
|
||||
def _init_instantiated_vnf_info(self, inst, flavour_id, def_files,
|
||||
vdu_reses, namespace):
|
||||
metadata = {
|
||||
'namespace': namespace,
|
||||
'lcm-kubernetes-def-files': def_files,
|
||||
'vdu_reses': {vdu_name: vdu_res.body
|
||||
for vdu_name, vdu_res in vdu_reses.items()}
|
||||
}
|
||||
inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo(
|
||||
flavourId=flavour_id,
|
||||
vnfState='STARTED',
|
||||
metadata=metadata
|
||||
def _init_instantiated_vnf_info(self, inst, flavour_id, vdu_reses,
|
||||
namespace, target_k8s_files):
|
||||
super()._init_instantiated_vnf_info(inst, flavour_id,
|
||||
vdu_reses, namespace)
|
||||
inst.instantiatedVnfInfo.metadata.update(
|
||||
{'lcm-kubernetes-def-files': target_k8s_files}
|
||||
)
|
||||
|
||||
def _get_vdu_res(self, inst, k8s_api_client, vdu):
|
||||
# must be found
|
||||
res = inst.instantiatedVnfInfo.metadata['vdu_reses'][vdu]
|
||||
cls = getattr(kubernetes_resource, res['kind'])
|
||||
return cls(k8s_api_client, res)
|
||||
def _is_match_pod_naming_rule(self, rsc_kind, rsc_name, pod_name):
|
||||
match_result = None
|
||||
if rsc_kind == 'Pod':
|
||||
# Expected example: name
|
||||
if rsc_name == pod_name:
|
||||
return True
|
||||
elif rsc_kind == 'Deployment':
|
||||
# Expected example: name-012789abef-019az
|
||||
# NOTE(horie): The naming rule of Pod in deployment is
|
||||
# "(deployment name)-(pod template hash)-(5 charactors)".
|
||||
# The "pod template hash" string is generated from 32 bit hash.
|
||||
# This may be from 1 to 10 caracters but not sure the lower limit
|
||||
# from the source code of Kubernetes.
|
||||
match_result = re.match(
|
||||
rsc_name + '-([0-9a-f]{1,10})-([0-9a-z]{5})+$', pod_name)
|
||||
elif rsc_kind in ('ReplicaSet', 'DaemonSet'):
|
||||
# Expected example: name-019az
|
||||
match_result = re.match(rsc_name + '-([0-9a-z]{5})+$', pod_name)
|
||||
elif rsc_kind == 'StatefulSet':
|
||||
# Expected example: name-0
|
||||
match_result = re.match(rsc_name + '-[0-9]+$', pod_name)
|
||||
if match_result:
|
||||
return True
|
||||
|
||||
def _update_vnfc_info(self, inst, k8s_api_client):
|
||||
all_pods = kubernetes_utils.list_namespaced_pods(
|
||||
k8s_api_client, inst.instantiatedVnfInfo.metadata['namespace'])
|
||||
vnfc_resources = []
|
||||
for pod in all_pods:
|
||||
pod_name = pod.metadata.name
|
||||
for vdu_name, vdu_res in (
|
||||
inst.instantiatedVnfInfo.metadata['vdu_reses'].items()):
|
||||
if kubernetes_utils.is_match_pod_naming_rule(
|
||||
vdu_res['kind'], vdu_res['metadata']['name'],
|
||||
pod_name):
|
||||
vnfc_resources.append(objects.VnfcResourceInfoV2(
|
||||
id=pod_name,
|
||||
vduId=vdu_name,
|
||||
computeResource=objects.ResourceHandle(
|
||||
resourceId=pod_name,
|
||||
vimLevelResourceType=vdu_res['kind']
|
||||
),
|
||||
# lcmocc_utils.update_lcmocc assumes its existence
|
||||
metadata={}
|
||||
))
|
||||
|
||||
inst.instantiatedVnfInfo.vnfcResourceInfo = vnfc_resources
|
||||
|
||||
# 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)
|
||||
return False
|
||||
|
@ -0,0 +1,216 @@
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
|
||||
from tacker.sol_refactored.common import config
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_resource
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_utils
|
||||
from tacker.sol_refactored import objects
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = config.CONF
|
||||
CHECK_INTERVAL = 10
|
||||
|
||||
TARGET_KIND = {"Pod", "Deployment", "DaemonSet", "StatefulSet", "ReplicaSet"}
|
||||
|
||||
|
||||
class KubernetesCommon(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def heal(self, req, inst, grant_req, grant, vnfd):
|
||||
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._heal(req, inst, grant_req, grant, vnfd, k8s_api_client)
|
||||
|
||||
def _heal(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
|
||||
namespace = inst.instantiatedVnfInfo.metadata['namespace']
|
||||
|
||||
# get heal Pod name
|
||||
vnfc_res_ids = [res_def.resource.resourceId
|
||||
for res_def in grant_req.removeResources
|
||||
if res_def.type == 'COMPUTE']
|
||||
|
||||
target_vnfcs = [vnfc
|
||||
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo
|
||||
if vnfc.computeResource.resourceId in vnfc_res_ids]
|
||||
|
||||
# check running Pod
|
||||
all_pods = kubernetes_utils.list_namespaced_pods(
|
||||
k8s_api_client, namespace)
|
||||
current_pods_name = [pod.metadata.name for pod in all_pods]
|
||||
|
||||
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 _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_ids = {value.get('properties').get('name'): key
|
||||
for key, value in vdu_nodes.items()}
|
||||
# res.name is properties.name itself or
|
||||
# {properties.name}-{some string}. later is helm case.
|
||||
return {vdu_ids[res.name.split("-")[0]]: res
|
||||
for res in k8s_reses
|
||||
if (res.kind in TARGET_KIND
|
||||
and res.name.split("-")[0] in vdu_ids)}
|
||||
|
||||
def _init_instantiated_vnf_info(self, inst, flavour_id,
|
||||
vdu_reses, namespace):
|
||||
metadata = {
|
||||
'namespace': namespace,
|
||||
'vdu_reses': {vdu_name: vdu_res.body
|
||||
for vdu_name, vdu_res in vdu_reses.items()}
|
||||
}
|
||||
inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo(
|
||||
flavourId=flavour_id,
|
||||
vnfState='STARTED',
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
def _get_vdu_res(self, inst, k8s_api_client, vdu):
|
||||
# must be found
|
||||
res = inst.instantiatedVnfInfo.metadata['vdu_reses'][vdu]
|
||||
cls = getattr(kubernetes_resource, res['kind'])
|
||||
return cls(k8s_api_client, res)
|
||||
|
||||
def _update_vnfc_info(self, inst, k8s_api_client):
|
||||
all_pods = kubernetes_utils.list_namespaced_pods(
|
||||
k8s_api_client, inst.instantiatedVnfInfo.metadata['namespace'])
|
||||
vnfc_resources = []
|
||||
for pod in all_pods:
|
||||
pod_name = pod.metadata.name
|
||||
for vdu_name, vdu_res in (
|
||||
inst.instantiatedVnfInfo.metadata['vdu_reses'].items()):
|
||||
if self._is_match_pod_naming_rule(
|
||||
vdu_res['kind'], vdu_res['metadata']['name'],
|
||||
pod_name):
|
||||
vnfc_resources.append(objects.VnfcResourceInfoV2(
|
||||
id=pod_name,
|
||||
vduId=vdu_name,
|
||||
computeResource=objects.ResourceHandle(
|
||||
resourceId=pod_name,
|
||||
vimLevelResourceType=vdu_res['kind']
|
||||
),
|
||||
# lcmocc_utils.update_lcmocc assumes its existence
|
||||
metadata={}
|
||||
))
|
||||
|
||||
inst.instantiatedVnfInfo.vnfcResourceInfo = vnfc_resources
|
||||
|
||||
# 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_updated(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 self._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_updated, check_reses, k8s_api_client,
|
||||
namespace, old_pods_names)
|
@ -25,62 +25,36 @@ import yaml
|
||||
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import oidc_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import helm_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes_resource
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SUPPORTED_NAMESPACE_KINDS = [
|
||||
"Pod",
|
||||
SUPPORTED_NAMESPACE_KINDS = {
|
||||
"Binding",
|
||||
"ConfigMap",
|
||||
"LimitRange",
|
||||
"PersistentVolumeClaim",
|
||||
"PodTemplate",
|
||||
"ResourceQuota",
|
||||
"Secret",
|
||||
"ServiceAccount",
|
||||
"Service",
|
||||
"ControllerRevision",
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"ReplicaSet",
|
||||
"StatefulSet",
|
||||
"LocalSubjectAccessReview",
|
||||
"HorizontalPodAutoscaler",
|
||||
"Job",
|
||||
"Lease",
|
||||
"LimitRange",
|
||||
"LocalSubjectAccessReview",
|
||||
"NetworkPolicy",
|
||||
"RoleBinding",
|
||||
"PersistentVolumeClaim",
|
||||
"Pod",
|
||||
"PodTemplate",
|
||||
"ReplicaSet",
|
||||
"ResourceQuota",
|
||||
"Role"
|
||||
]
|
||||
|
||||
|
||||
def is_match_pod_naming_rule(rsc_kind, rsc_name, pod_name):
|
||||
match_result = None
|
||||
if rsc_kind == 'Pod':
|
||||
# Expected example: name
|
||||
if rsc_name == pod_name:
|
||||
return True
|
||||
elif rsc_kind == 'Deployment':
|
||||
# Expected example: name-012789abef-019az
|
||||
# NOTE(horie): The naming rule of Pod in deployment is
|
||||
# "(deployment name)-(pod template hash)-(5 charactors)".
|
||||
# The "pod template hash" string is generated from 32 bit hash.
|
||||
# This may be from 1 to 10 caracters but not sure the lower limit
|
||||
# from the source code of Kubernetes.
|
||||
match_result = re.match(
|
||||
rsc_name + '-([0-9a-f]{1,10})-([0-9a-z]{5})+$', pod_name)
|
||||
elif rsc_kind in ('ReplicaSet', 'DaemonSet'):
|
||||
# Expected example: name-019az
|
||||
match_result = re.match(rsc_name + '-([0-9a-z]{5})+$', pod_name)
|
||||
elif rsc_kind == 'StatefulSet':
|
||||
# Expected example: name-0
|
||||
match_result = re.match(rsc_name + '-[0-9]+$', pod_name)
|
||||
if match_result:
|
||||
return True
|
||||
|
||||
return False
|
||||
"RoleBinding",
|
||||
"Secret",
|
||||
"Service",
|
||||
"ServiceAccount",
|
||||
"StatefulSet",
|
||||
}
|
||||
|
||||
|
||||
def get_k8s_reses_from_json_files(target_k8s_files, vnfd, k8s_api_client,
|
||||
@ -122,8 +96,11 @@ def get_k8s_reses_from_json_files(target_k8s_files, vnfd, k8s_api_client,
|
||||
|
||||
k8s_reses = []
|
||||
for k8s_res in k8s_resources:
|
||||
cls = getattr(kubernetes_resource, k8s_res['kind'])
|
||||
k8s_reses.append(cls(k8s_api_client, k8s_res))
|
||||
try:
|
||||
cls = getattr(kubernetes_resource, k8s_res['kind'])
|
||||
k8s_reses.append(cls(k8s_api_client, k8s_res))
|
||||
except AttributeError:
|
||||
LOG.info("Not support kind %s. ignored.", k8s_res['kind'])
|
||||
|
||||
return k8s_reses, namespace
|
||||
|
||||
@ -146,6 +123,8 @@ class AuthContextManager:
|
||||
os.remove(self.ca_cert_file)
|
||||
|
||||
def _create_ca_cert_file(self, ca_cert_str):
|
||||
if self.ca_cert_file:
|
||||
return
|
||||
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)
|
||||
@ -194,3 +173,17 @@ class AuthContextManager:
|
||||
k8s_config.verify_ssl = False
|
||||
|
||||
return client.api_client.ApiClient(configuration=k8s_config)
|
||||
|
||||
def _get_helm_auth_params(self):
|
||||
kube_apiserver = self.vim_info.interfaceInfo['endpoint']
|
||||
kube_token = self.vim_info.accessInfo['bearer_token']
|
||||
self._create_ca_cert_file(
|
||||
self.vim_info.interfaceInfo['ssl_ca_cert'])
|
||||
helm_auth_params = ["--kube-apiserver", kube_apiserver,
|
||||
"--kube-ca-file", self.ca_cert_file,
|
||||
"--kube-token", kube_token]
|
||||
|
||||
return helm_auth_params
|
||||
|
||||
def init_helm_client(self):
|
||||
return helm_utils.HelmClient(self._get_helm_auth_params())
|
||||
|
@ -63,7 +63,6 @@ def max_sample_instantiate(auth_url, bearer_token, ssl_ca_cert=None):
|
||||
"interfaceInfo": {"endpoint": auth_url},
|
||||
"accessInfo": {
|
||||
"bearer_token": bearer_token,
|
||||
"region": "RegionOne",
|
||||
},
|
||||
"extra": {"dummy-key": "dummy-val"}
|
||||
}
|
||||
@ -73,7 +72,6 @@ def max_sample_instantiate(auth_url, bearer_token, ssl_ca_cert=None):
|
||||
"interfaceInfo": {"endpoint": auth_url},
|
||||
"accessInfo": {
|
||||
"username": "dummy_user",
|
||||
"region": "RegionOne",
|
||||
"password": "dummy_password",
|
||||
},
|
||||
"extra": {"dummy-key": "dummy-val"}
|
||||
@ -217,7 +215,6 @@ def error_handling_instantiate(auth_url, bearer_token):
|
||||
"interfaceInfo": {"endpoint": auth_url},
|
||||
"accessInfo": {
|
||||
"bearer_token": bearer_token,
|
||||
"region": "RegionOne",
|
||||
},
|
||||
"extra": {"dummy-key": "dummy-val"}
|
||||
}
|
||||
@ -257,7 +254,6 @@ def change_vnfpkg_instantiate(auth_url, bearer_token):
|
||||
"interfaceInfo": {"endpoint": auth_url},
|
||||
"accessInfo": {
|
||||
"bearer_token": bearer_token,
|
||||
"region": "RegionOne",
|
||||
},
|
||||
"extra": {"dummy-key": "dummy-val"}
|
||||
}
|
||||
@ -311,3 +307,103 @@ def change_vnfpkg_terminate():
|
||||
return {
|
||||
"terminationType": "FORCEFUL"
|
||||
}
|
||||
|
||||
|
||||
def test_helm_instantiate_create(vnfd_id):
|
||||
return {
|
||||
"vnfdId": vnfd_id,
|
||||
"vnfInstanceName": "test_helm_instantiate",
|
||||
"vnfInstanceDescription": "test_helm_instantiate",
|
||||
"metadata": {"dummy-key": "dummy-val"}
|
||||
}
|
||||
|
||||
|
||||
def helm_instantiate(auth_url, bearer_token, ssl_ca_cert):
|
||||
vim_id_1 = uuidutils.generate_uuid()
|
||||
vim_1 = {
|
||||
"vimId": vim_id_1,
|
||||
"vimType": "ETSINFV.HELM.V_3",
|
||||
"interfaceInfo": {
|
||||
"endpoint": auth_url,
|
||||
"ssl_ca_cert": ssl_ca_cert
|
||||
},
|
||||
"accessInfo": {
|
||||
"bearer_token": bearer_token
|
||||
}
|
||||
}
|
||||
return {
|
||||
"flavourId": "simple",
|
||||
"vimConnectionInfo": {
|
||||
"vim1": vim_1,
|
||||
},
|
||||
"additionalParams": {
|
||||
"helm_chart_path": "Files/kubernetes/test-chart-0.1.0.tgz",
|
||||
"helm_parameters": {
|
||||
"service.port": 8081
|
||||
},
|
||||
"helm_value_names": {
|
||||
"VDU1": {
|
||||
"replica": "replicaCountVdu1"
|
||||
},
|
||||
"VDU2": {
|
||||
"replica": "replicaCountVdu2"
|
||||
}
|
||||
},
|
||||
"namespace": "default"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def helm_terminate():
|
||||
return {
|
||||
"terminationType": "FORCEFUL"
|
||||
}
|
||||
|
||||
|
||||
def helm_scale_out():
|
||||
return {
|
||||
"type": "SCALE_OUT",
|
||||
"aspectId": "vdu2_aspect",
|
||||
"numberOfSteps": 2
|
||||
}
|
||||
|
||||
|
||||
def helm_scale_in():
|
||||
return {
|
||||
"type": "SCALE_IN",
|
||||
"aspectId": "vdu2_aspect",
|
||||
"numberOfSteps": 1
|
||||
}
|
||||
|
||||
|
||||
def helm_heal(vnfc_ids):
|
||||
return {
|
||||
"vnfcInstanceId": vnfc_ids
|
||||
}
|
||||
|
||||
|
||||
def helm_change_vnfpkg(vnfd_id):
|
||||
return {
|
||||
"vnfdId": vnfd_id,
|
||||
"additionalParams": {
|
||||
"upgrade_type": "RollingUpdate",
|
||||
"helm_chart_path": "Files/kubernetes/test-chart-0.1.1.tgz",
|
||||
"vdu_params": [{
|
||||
"vdu_id": "VDU2"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def helm_error_handling_change_vnfpkg(vnfd_id):
|
||||
return {
|
||||
"vnfdId": vnfd_id,
|
||||
"additionalParams": {
|
||||
"upgrade_type": "RollingUpdate",
|
||||
"helm_chart_path": "Files/kubernetes/test-chart-error-"
|
||||
"handling.tgz",
|
||||
"vdu_params": [{
|
||||
"vdu_id": "VDU2"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,178 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: Simple deployment flavour for Sample VNF
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
- sample_cnf_types.yaml
|
||||
|
||||
topology_template:
|
||||
inputs:
|
||||
descriptor_id:
|
||||
type: string
|
||||
descriptor_version:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
product_name:
|
||||
type: string
|
||||
software_version:
|
||||
type: string
|
||||
vnfm_info:
|
||||
type: list
|
||||
entry_schema:
|
||||
type: string
|
||||
flavour_id:
|
||||
type: string
|
||||
flavour_description:
|
||||
type: string
|
||||
|
||||
substitution_mappings:
|
||||
node_type: company.provider.VNF
|
||||
properties:
|
||||
flavour_id: simple
|
||||
requirements:
|
||||
virtual_link_external: []
|
||||
|
||||
node_templates:
|
||||
VNF:
|
||||
type: company.provider.VNF
|
||||
properties:
|
||||
flavour_description: A simple flavour
|
||||
interfaces:
|
||||
Vnflcm:
|
||||
instantiate_start:
|
||||
implementation: sample-script
|
||||
instantiate_end:
|
||||
implementation: sample-script
|
||||
terminate_start:
|
||||
implementation: sample-script
|
||||
terminate_end:
|
||||
implementation: sample-script
|
||||
scale_start:
|
||||
implementation: sample-script
|
||||
scale_end:
|
||||
implementation: sample-script
|
||||
heal_start:
|
||||
implementation: sample-script
|
||||
heal_end:
|
||||
implementation: sample-script
|
||||
modify_information_start:
|
||||
implementation: sample-script
|
||||
modify_information_end:
|
||||
implementation: sample-script
|
||||
artifacts:
|
||||
sample-script:
|
||||
description: Sample script
|
||||
type: tosca.artifacts.Implementation.Python
|
||||
file: ../Scripts/sample_script.py
|
||||
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu1
|
||||
description: VDU1 compute node
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 3
|
||||
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu2
|
||||
description: VDU2 compute node
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 3
|
||||
|
||||
policies:
|
||||
- scaling_aspects:
|
||||
type: tosca.policies.nfv.ScalingAspects
|
||||
properties:
|
||||
aspects:
|
||||
vdu1_aspect:
|
||||
name: vdu1_aspect
|
||||
description: vdu1 scaling aspect
|
||||
max_scale_level: 2
|
||||
step_deltas:
|
||||
- delta_1
|
||||
vdu2_aspect:
|
||||
name: vdu2_aspect
|
||||
description: vdu2 scaling aspect
|
||||
max_scale_level: 2
|
||||
step_deltas:
|
||||
- delta_1
|
||||
|
||||
- VDU1_initial_delta:
|
||||
type: tosca.policies.nfv.VduInitialDelta
|
||||
properties:
|
||||
initial_delta:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU1_scaling_aspect_deltas:
|
||||
type: tosca.policies.nfv.VduScalingAspectDeltas
|
||||
properties:
|
||||
aspect: vdu1_aspect
|
||||
deltas:
|
||||
delta_1:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU2_initial_delta:
|
||||
type: tosca.policies.nfv.VduInitialDelta
|
||||
properties:
|
||||
initial_delta:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU2 ]
|
||||
|
||||
- VDU2_scaling_aspect_deltas:
|
||||
type: tosca.policies.nfv.VduScalingAspectDeltas
|
||||
properties:
|
||||
aspect: vdu2_aspect
|
||||
deltas:
|
||||
delta_1:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU2 ]
|
||||
|
||||
- instantiation_levels:
|
||||
type: tosca.policies.nfv.InstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
description: Smallest size
|
||||
scale_info:
|
||||
vdu1_aspect:
|
||||
scale_level: 0
|
||||
vdu2_aspect:
|
||||
scale_level: 0
|
||||
instantiation_level_2:
|
||||
description: Largest size
|
||||
scale_info:
|
||||
vdu1_aspect:
|
||||
scale_level: 2
|
||||
vdu2_aspect:
|
||||
scale_level: 2
|
||||
default_level: instantiation_level_1
|
||||
|
||||
- VDU1_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 1
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU2_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 1
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU2 ]
|
||||
|
@ -0,0 +1,31 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: Sample VNF
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
- sample_cnf_types.yaml
|
||||
- sample_cnf_df_simple.yaml
|
||||
|
||||
topology_template:
|
||||
inputs:
|
||||
selected_flavour:
|
||||
type: string
|
||||
description: VNF deployment flavour selected by the consumer. It is provided in the API
|
||||
|
||||
node_templates:
|
||||
VNF:
|
||||
type: company.provider.VNF
|
||||
properties:
|
||||
flavour_id: { get_input: selected_flavour }
|
||||
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
|
||||
provider: Company
|
||||
product_name: Sample VNF
|
||||
software_version: '1.0'
|
||||
descriptor_version: '1.0'
|
||||
vnfm_info:
|
||||
- Tacker
|
||||
requirements:
|
||||
#- virtual_link_external # mapped in lower-level templates
|
||||
#- virtual_link_internal # mapped in lower-level templates
|
@ -0,0 +1,53 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: VNF type definition
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
|
||||
node_types:
|
||||
company.provider.VNF:
|
||||
derived_from: tosca.nodes.nfv.VNF
|
||||
properties:
|
||||
descriptor_id:
|
||||
type: string
|
||||
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ]
|
||||
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
|
||||
descriptor_version:
|
||||
type: string
|
||||
constraints: [ valid_values: [ '1.0' ] ]
|
||||
default: '1.0'
|
||||
provider:
|
||||
type: string
|
||||
constraints: [ valid_values: [ 'Company' ] ]
|
||||
default: 'Company'
|
||||
product_name:
|
||||
type: string
|
||||
constraints: [ valid_values: [ 'Sample VNF' ] ]
|
||||
default: 'Sample VNF'
|
||||
software_version:
|
||||
type: string
|
||||
constraints: [ valid_values: [ '1.0' ] ]
|
||||
default: '1.0'
|
||||
vnfm_info:
|
||||
type: list
|
||||
entry_schema:
|
||||
type: string
|
||||
constraints: [ valid_values: [ Tacker ] ]
|
||||
default: [ Tacker ]
|
||||
flavour_id:
|
||||
type: string
|
||||
constraints: [ valid_values: [ simple,complex ] ]
|
||||
default: simple
|
||||
flavour_description:
|
||||
type: string
|
||||
default: ""
|
||||
requirements:
|
||||
- virtual_link_external:
|
||||
capability: tosca.capabilities.nfv.VirtualLinkable
|
||||
- virtual_link_internal:
|
||||
capability: tosca.capabilities.nfv.VirtualLinkable
|
||||
interfaces:
|
||||
Vnflcm:
|
||||
type: tosca.interfaces.nfv.Vnflcm
|
Binary file not shown.
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.16.0
|
||||
description: A Helm chart for Kubernetes
|
||||
name: test-chart
|
||||
type: application
|
||||
version: 0.1.1
|
@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "test-chart.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "test-chart.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "test-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "test-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "test-chart.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "test-chart.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "test-chart.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "test-chart.labels" -}}
|
||||
helm.sh/chart: {{ include "test-chart.chart" . }}
|
||||
{{ include "test-chart.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "test-chart.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "test-chart.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "test-chart.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "test-chart.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vdu1-{{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCountVdu1 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "test-chart.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vdu2-{{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCountVdu2 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "test-chart.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: nginx:alpine
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,28 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "test-chart.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,61 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "test-chart.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 4 }}
|
@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "test-chart.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "test-chart.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "test-chart.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
@ -0,0 +1,83 @@
|
||||
# Default values for test-chart.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCountVdu1: 1
|
||||
replicaCountVdu2: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
|
||||
class FailScript(object):
|
||||
"""Define error method for each operation
|
||||
|
||||
For example:
|
||||
|
||||
def instantiate_start(self):
|
||||
if os.path.exists('/tmp/instantiate_start')
|
||||
raise Exception('test instantiate_start error')
|
||||
"""
|
||||
|
||||
def __init__(self, req, inst, grant_req, grant, csar_dir):
|
||||
self.req = req
|
||||
self.inst = inst
|
||||
self.grant_req = grant_req
|
||||
self.grant = grant
|
||||
self.csar_dir = csar_dir
|
||||
|
||||
def _fail(self, method):
|
||||
if os.path.exists(f'/tmp/{method}'):
|
||||
raise Exception(f'test {method} error')
|
||||
|
||||
def __getattr__(self, name):
|
||||
return functools.partial(self._fail, name)
|
||||
|
||||
|
||||
def main():
|
||||
script_dict = pickle.load(sys.stdin.buffer)
|
||||
|
||||
operation = script_dict['operation']
|
||||
req = script_dict['request']
|
||||
inst = script_dict['vnf_instance']
|
||||
grant_req = script_dict['grant_request']
|
||||
grant = script_dict['grant_response']
|
||||
csar_dir = script_dict['tmp_csar_dir']
|
||||
|
||||
script = FailScript(req, inst, grant_req, grant, csar_dir)
|
||||
getattr(script, operation)()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
os._exit(0)
|
||||
except Exception as ex:
|
||||
sys.stderr.write(str(ex))
|
||||
sys.stderr.flush()
|
||||
os._exit(1)
|
@ -0,0 +1,9 @@
|
||||
TOSCA-Meta-File-Version: 1.0
|
||||
Created-by: dummy_user
|
||||
CSAR-Version: 1.1
|
||||
Entry-Definitions: Definitions/sample_cnf_top.vnfd.yaml
|
||||
|
||||
Name: Files/kubernetes/test-chart-0.1.1.tgz
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: 6d5177b34c732835cc15f9f39c3f93867f10af4d69c2224dd9122645b1ee17f7
|
@ -0,0 +1,47 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tacker.tests.functional.sol_kubernetes_v2 import paramgen
|
||||
from tacker.tests.functional.sol_v2_common import utils
|
||||
|
||||
|
||||
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
vnfd_id = uuidutils.generate_uuid()
|
||||
|
||||
# tacker/tests/functional/sol_kubernetes_v2/samples/{package_name}
|
||||
utils.make_zip(".", tmp_dir, vnfd_id)
|
||||
|
||||
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
helm_change_vnfpkg = (
|
||||
paramgen.helm_change_vnfpkg(vnfd_id))
|
||||
|
||||
helm_error_handling_change_vnfpkg = (
|
||||
paramgen.helm_error_handling_change_vnfpkg(vnfd_id))
|
||||
|
||||
with open("helm_change_vnfpkg", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_change_vnfpkg, indent=2))
|
||||
|
||||
with open("helm_error_handling_change_vnfpkg", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_error_handling_change_vnfpkg, indent=2))
|
@ -0,0 +1,177 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: Simple deployment flavour for Sample VNF
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
- sample_cnf_types.yaml
|
||||
|
||||
topology_template:
|
||||
inputs:
|
||||
descriptor_id:
|
||||
type: string
|
||||
descriptor_version:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
product_name:
|
||||
type: string
|
||||
software_version:
|
||||
type: string
|
||||
vnfm_info:
|
||||
type: list
|
||||
entry_schema:
|
||||
type: string
|
||||
flavour_id:
|
||||
type: string
|
||||
flavour_description:
|
||||
type: string
|
||||
|
||||
substitution_mappings:
|
||||
node_type: company.provider.VNF
|
||||
properties:
|
||||
flavour_id: simple
|
||||
requirements:
|
||||
virtual_link_external: []
|
||||
|
||||
node_templates:
|
||||
VNF:
|
||||
type: company.provider.VNF
|
||||
properties:
|
||||
flavour_description: A simple flavour
|
||||
interfaces:
|
||||
Vnflcm:
|
||||
instantiate_start:
|
||||
implementation: sample-script
|
||||
instantiate_end:
|
||||
implementation: sample-script
|
||||
terminate_start:
|
||||
implementation: sample-script
|
||||
terminate_end:
|
||||
implementation: sample-script
|
||||
scale_start:
|
||||
implementation: sample-script
|
||||
scale_end:
|
||||
implementation: sample-script
|
||||
heal_start:
|
||||
implementation: sample-script
|
||||
heal_end:
|
||||
implementation: sample-script
|
||||
modify_information_start:
|
||||
implementation: sample-script
|
||||
modify_information_end:
|
||||
implementation: sample-script
|
||||
artifacts:
|
||||
sample-script:
|
||||
description: Sample script
|
||||
type: tosca.artifacts.Implementation.Python
|
||||
file: ../Scripts/sample_script.py
|
||||
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu1
|
||||
description: VDU1 compute node
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 3
|
||||
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu2
|
||||
description: VDU2 compute node
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 3
|
||||
|
||||
policies:
|
||||
- scaling_aspects:
|
||||
type: tosca.policies.nfv.ScalingAspects
|
||||
properties:
|
||||
aspects:
|
||||
vdu1_aspect:
|
||||
name: vdu1_aspect
|
||||
description: vdu1 scaling aspect
|
||||
max_scale_level: 2
|
||||
step_deltas:
|
||||
- delta_1
|
||||
vdu2_aspect:
|
||||
name: vdu2_aspect
|
||||
description: vdu2 scaling aspect
|
||||
max_scale_level: 2
|
||||
step_deltas:
|
||||
- delta_1
|
||||
|
||||
- VDU1_initial_delta:
|
||||
type: tosca.policies.nfv.VduInitialDelta
|
||||
properties:
|
||||
initial_delta:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU1_scaling_aspect_deltas:
|
||||
type: tosca.policies.nfv.VduScalingAspectDeltas
|
||||
properties:
|
||||
aspect: vdu1_aspect
|
||||
deltas:
|
||||
delta_1:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU2_initial_delta:
|
||||
type: tosca.policies.nfv.VduInitialDelta
|
||||
properties:
|
||||
initial_delta:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU2 ]
|
||||
|
||||
- VDU2_scaling_aspect_deltas:
|
||||
type: tosca.policies.nfv.VduScalingAspectDeltas
|
||||
properties:
|
||||
aspect: vdu2_aspect
|
||||
deltas:
|
||||
delta_1:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU2 ]
|
||||
|
||||
- instantiation_levels:
|
||||
type: tosca.policies.nfv.InstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
description: Smallest size
|
||||
scale_info:
|
||||
vdu1_aspect:
|
||||
scale_level: 0
|
||||
vdu2_aspect:
|
||||
scale_level: 0
|
||||
instantiation_level_2:
|
||||
description: Largest size
|
||||
scale_info:
|
||||
vdu1_aspect:
|
||||
scale_level: 2
|
||||
vdu2_aspect:
|
||||
scale_level: 2
|
||||
default_level: instantiation_level_1
|
||||
|
||||
- VDU1_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 1
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU2_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 1
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU2 ]
|
@ -0,0 +1,31 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: Sample VNF
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
- sample_cnf_types.yaml
|
||||
- sample_cnf_df_simple.yaml
|
||||
|
||||
topology_template:
|
||||
inputs:
|
||||
selected_flavour:
|
||||
type: string
|
||||
description: VNF deployment flavour selected by the consumer. It is provided in the API
|
||||
|
||||
node_templates:
|
||||
VNF:
|
||||
type: company.provider.VNF
|
||||
properties:
|
||||
flavour_id: { get_input: selected_flavour }
|
||||
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
|
||||
provider: Company
|
||||
product_name: Sample VNF
|
||||
software_version: '1.0'
|
||||
descriptor_version: '1.0'
|
||||
vnfm_info:
|
||||
- Tacker
|
||||
requirements:
|
||||
#- virtual_link_external # mapped in lower-level templates
|
||||
#- virtual_link_internal # mapped in lower-level templates
|
@ -0,0 +1,53 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: VNF type definition
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
|
||||
node_types:
|
||||
company.provider.VNF:
|
||||
derived_from: tosca.nodes.nfv.VNF
|
||||
properties:
|
||||
descriptor_id:
|
||||
type: string
|
||||
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ]
|
||||
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
|
||||
descriptor_version:
|
||||
type: string
|
||||
constraints: [ valid_values: [ '1.0' ] ]
|
||||
default: '1.0'
|
||||
provider:
|
||||
type: string
|
||||
constraints: [ valid_values: [ 'Company' ] ]
|
||||
default: 'Company'
|
||||
product_name:
|
||||
type: string
|
||||
constraints: [ valid_values: [ 'Sample VNF' ] ]
|
||||
default: 'Sample VNF'
|
||||
software_version:
|
||||
type: string
|
||||
constraints: [ valid_values: [ '1.0' ] ]
|
||||
default: '1.0'
|
||||
vnfm_info:
|
||||
type: list
|
||||
entry_schema:
|
||||
type: string
|
||||
constraints: [ valid_values: [ Tacker ] ]
|
||||
default: [ Tacker ]
|
||||
flavour_id:
|
||||
type: string
|
||||
constraints: [ valid_values: [ simple,complex ] ]
|
||||
default: simple
|
||||
flavour_description:
|
||||
type: string
|
||||
default: ""
|
||||
requirements:
|
||||
- virtual_link_external:
|
||||
capability: tosca.capabilities.nfv.VirtualLinkable
|
||||
- virtual_link_internal:
|
||||
capability: tosca.capabilities.nfv.VirtualLinkable
|
||||
interfaces:
|
||||
Vnflcm:
|
||||
type: tosca.interfaces.nfv.Vnflcm
|
Binary file not shown.
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.16.0
|
||||
description: A Helm chart for Kubernetes
|
||||
name: test-chart
|
||||
type: application
|
||||
version: 0.1.0
|
@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "test-chart.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "test-chart.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "test-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "test-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "test-chart.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "test-chart.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "test-chart.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "test-chart.labels" -}}
|
||||
helm.sh/chart: {{ include "test-chart.chart" . }}
|
||||
{{ include "test-chart.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "test-chart.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "test-chart.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "test-chart.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "test-chart.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vdu1-{{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCountVdu1 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "test-chart.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vdu2-{{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCountVdu2 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "test-chart.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: nginx
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,28 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "test-chart.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,61 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "test-chart.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "test-chart.fullname" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "test-chart.selectorLabels" . | nindent 4 }}
|
@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "test-chart.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "test-chart.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "test-chart.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "test-chart.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
@ -0,0 +1,83 @@
|
||||
# Default values for test-chart.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCountVdu1: 1
|
||||
replicaCountVdu2: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
|
||||
class FailScript(object):
|
||||
"""Define error method for each operation
|
||||
|
||||
For example:
|
||||
|
||||
def instantiate_start(self):
|
||||
if os.path.exists('/tmp/instantiate_start')
|
||||
raise Exception('test instantiate_start error')
|
||||
"""
|
||||
|
||||
def __init__(self, req, inst, grant_req, grant, csar_dir):
|
||||
self.req = req
|
||||
self.inst = inst
|
||||
self.grant_req = grant_req
|
||||
self.grant = grant
|
||||
self.csar_dir = csar_dir
|
||||
|
||||
def _fail(self, method):
|
||||
if os.path.exists(f'/tmp/{method}'):
|
||||
raise Exception(f'test {method} error')
|
||||
|
||||
def __getattr__(self, name):
|
||||
return functools.partial(self._fail, name)
|
||||
|
||||
|
||||
def main():
|
||||
script_dict = pickle.load(sys.stdin.buffer)
|
||||
|
||||
operation = script_dict['operation']
|
||||
req = script_dict['request']
|
||||
inst = script_dict['vnf_instance']
|
||||
grant_req = script_dict['grant_request']
|
||||
grant = script_dict['grant_response']
|
||||
csar_dir = script_dict['tmp_csar_dir']
|
||||
|
||||
script = FailScript(req, inst, grant_req, grant, csar_dir)
|
||||
getattr(script, operation)()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
os._exit(0)
|
||||
except Exception as ex:
|
||||
sys.stderr.write(str(ex))
|
||||
sys.stderr.flush()
|
||||
os._exit(1)
|
@ -0,0 +1,9 @@
|
||||
TOSCA-Meta-File-Version: 1.0
|
||||
Created-by: dummy_user
|
||||
CSAR-Version: 1.1
|
||||
Entry-Definitions: Definitions/sample_cnf_top.vnfd.yaml
|
||||
|
||||
Name: Files/kubernetes/test-chart-0.1.0.tgz
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: 511df66c2d34bc2d3b1ea80118c4ad3c61ad7816a45bfadbb223d172b8503d30
|
@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tacker.tests.functional.sol_kubernetes_v2 import paramgen
|
||||
from tacker.tests.functional.sol_v2_common import utils
|
||||
|
||||
|
||||
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
vnfd_id = uuidutils.generate_uuid()
|
||||
|
||||
# tacker/tests/functional/sol_kubernetes_v2/samples/{package_name}
|
||||
utils.make_zip(".", tmp_dir, vnfd_id)
|
||||
|
||||
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
create_req = paramgen.test_helm_instantiate_create(vnfd_id)
|
||||
|
||||
# if you instantiate with all k8s resource
|
||||
# please change auth_url and bear_token to your own k8s cluster's info
|
||||
auth_url = "https://127.0.0.1:6443"
|
||||
bearer_token = "your_k8s_cluster_bearer_token"
|
||||
ssl_ca_cert = "k8s_ssl_ca_cert"
|
||||
helm_instantiate_req = paramgen.helm_instantiate(
|
||||
auth_url, bearer_token, ssl_ca_cert)
|
||||
|
||||
helm_terminate_req = paramgen.helm_terminate()
|
||||
helm_scale_out = paramgen.helm_scale_out()
|
||||
helm_scale_in = paramgen.helm_scale_in()
|
||||
helm_heal = paramgen.helm_heal(["replace real vnfc ids"])
|
||||
|
||||
with open("create_req", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(create_req, indent=2))
|
||||
|
||||
with open("helm_instantiate_req", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_instantiate_req, indent=2))
|
||||
|
||||
with open("helm_terminate_req", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_terminate_req, indent=2))
|
||||
|
||||
with open("helm_scale_out", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_scale_out, indent=2))
|
||||
|
||||
with open("helm_scale_in", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_scale_in, indent=2))
|
||||
|
||||
with open("helm_heal", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(helm_heal, indent=2))
|
545
tacker/tests/functional/sol_kubernetes_v2/test_helm.py
Normal file
545
tacker/tests/functional/sol_kubernetes_v2/test_helm.py
Normal file
@ -0,0 +1,545 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from tacker.tests.functional.sol_kubernetes_v2 import base_v2
|
||||
from tacker.tests.functional.sol_kubernetes_v2 import paramgen
|
||||
|
||||
|
||||
class VnfLcmHelmTest(base_v2.BaseVnfLcmKubernetesV2Test):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(VnfLcmHelmTest, cls).setUpClass()
|
||||
|
||||
cur_dir = os.path.dirname(__file__)
|
||||
|
||||
test_helm_instantiate_path = os.path.join(
|
||||
cur_dir, "samples/test_helm_instantiate")
|
||||
cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package(
|
||||
test_helm_instantiate_path)
|
||||
|
||||
test_helm_change_vnf_pkg_path = os.path.join(
|
||||
cur_dir, "samples/test_helm_change_vnf_pkg")
|
||||
cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package(
|
||||
test_helm_change_vnf_pkg_path)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(VnfLcmHelmTest, cls).tearDownClass()
|
||||
|
||||
cls.delete_vnf_package(cls.vnf_pkg_1)
|
||||
cls.delete_vnf_package(cls.vnf_pkg_2)
|
||||
|
||||
def setUp(self):
|
||||
super(VnfLcmHelmTest, self).setUp()
|
||||
|
||||
def test_basic_lcms(self):
|
||||
"""Test basic LCM operations
|
||||
|
||||
* 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 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. Change Current VNF Package
|
||||
- 11. Show VNF instance
|
||||
- 12. Terminate a VNF instance
|
||||
- 13. 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_helm_instantiate_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
|
||||
instantiate_req = paramgen.helm_instantiate(
|
||||
self.auth_url, self.bearer_token, self.ssl_ca_cert)
|
||||
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)
|
||||
|
||||
# check vnfc_resource_info
|
||||
vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
|
||||
vdu_nums = {'VDU1': 0, 'VDU2': 0}
|
||||
for vnfc_info in vnfc_resource_infos:
|
||||
if vnfc_info['vduId'] == 'VDU1':
|
||||
self.assertEqual('Deployment', vnfc_info[
|
||||
'computeResource']['vimLevelResourceType'])
|
||||
vdu_nums['VDU1'] += 1
|
||||
elif vnfc_info['vduId'] == 'VDU2':
|
||||
self.assertEqual('Deployment', vnfc_info[
|
||||
'computeResource']['vimLevelResourceType'])
|
||||
vdu_nums['VDU2'] += 1
|
||||
expected = {'VDU1': 1, 'VDU2': 1}
|
||||
self.assertEqual(expected, vdu_nums)
|
||||
|
||||
# 4. Scale out a VNF instance
|
||||
scale_out_req = paramgen.helm_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']
|
||||
vdu2_infos = [vnfc_info for vnfc_info in vnfc_resource_infos
|
||||
if vnfc_info['vduId'] == 'VDU2']
|
||||
self.assertEqual(3, len(vdu2_infos))
|
||||
|
||||
# 6. Scale in a VNF instance
|
||||
scale_in_req = paramgen.helm_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']
|
||||
vdu2_infos = [vnfc_info for vnfc_info in vnfc_resource_infos
|
||||
if vnfc_info['vduId'] == 'VDU2']
|
||||
self.assertEqual(2, len(vdu2_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.helm_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. Change Current VNF Package
|
||||
vnfc_resource_infos = body['instantiatedVnfInfo']['vnfcResourceInfo']
|
||||
before_vdu2_ids = [vnfc_info['id'] for vnfc_info in vnfc_resource_infos
|
||||
if vnfc_info['vduId'] == 'VDU2']
|
||||
change_vnfpkg_req = paramgen.helm_change_vnfpkg(self.vnfd_id_2)
|
||||
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self.check_resp_headers_in_operation_task(resp)
|
||||
|
||||
lcmocc_id = os.path.basename(resp.headers['Location'])
|
||||
self.wait_lcmocc_complete(lcmocc_id)
|
||||
time.sleep(3)
|
||||
|
||||
# check usageState of VNF Package
|
||||
usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState')
|
||||
self.assertEqual('NOT_IN_USE', usage_state)
|
||||
# check usageState of VNF Package
|
||||
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
|
||||
self.assertEqual('IN_USE', usage_state)
|
||||
|
||||
# 11. 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']['vnfcResourceInfo']
|
||||
after_vdu2_ids = [vnfc_info['id'] for vnfc_info in vnfc_resource_infos
|
||||
if vnfc_info['vduId'] == 'VDU2']
|
||||
self.assertEqual(2, len(after_vdu2_ids))
|
||||
self.assertNotEqual(before_vdu2_ids, after_vdu2_ids)
|
||||
|
||||
# 12. Terminate a VNF instance
|
||||
terminate_req = paramgen.helm_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)
|
||||
|
||||
# 13. 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_1).get('usageState')
|
||||
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_helm_instantiate_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 => FAILED_TEMP
|
||||
self._put_fail_file('instantiate_end')
|
||||
instantiate_req = paramgen.helm_instantiate(
|
||||
self.auth_url, self.bearer_token, self.ssl_ca_cert)
|
||||
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 a VNF instance => FAILED_TEMP
|
||||
- 5. Rollback scale out
|
||||
- 6. Show VNF instance
|
||||
- 7. Terminate a VNF instance
|
||||
- 8. Delete a VNF instance
|
||||
"""
|
||||
|
||||
# 1. Create a new VNF instance resource
|
||||
create_req = paramgen.test_helm_instantiate_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.helm_instantiate(
|
||||
self.auth_url, self.bearer_token, self.ssl_ca_cert)
|
||||
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(1, len(vdu2_ids_0))
|
||||
|
||||
# 4. Scale out a VNF instance => FAILED_TEMP
|
||||
self._put_fail_file('scale_end')
|
||||
scale_out_req = paramgen.helm_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 scale out
|
||||
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.helm_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)
|
||||
|
||||
def test_change_vnfpkg_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 => FAILED_TEMP
|
||||
- 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_helm_instantiate_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
|
||||
instantiate_req = paramgen.helm_instantiate(
|
||||
self.auth_url, self.bearer_token, self.ssl_ca_cert)
|
||||
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']['vnfcResourceInfo']
|
||||
before_vdu2_ids = [vnfc_info['id'] for vnfc_info in vnfc_resource_infos
|
||||
if vnfc_info['vduId'] == 'VDU2']
|
||||
|
||||
# 4. Change Current VNF Package => FAILED_TEMP
|
||||
change_vnfpkg_req = paramgen.helm_error_handling_change_vnfpkg(
|
||||
self.vnfd_id_2)
|
||||
resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req)
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self.check_resp_headers_in_operation_task(resp)
|
||||
|
||||
lcmocc_id = os.path.basename(resp.headers['Location'])
|
||||
self.wait_lcmocc_failed_temp(lcmocc_id)
|
||||
|
||||
# 5. Rollback Change Current VNF Package
|
||||
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)
|
||||
|
||||
# check usageState of VNF Package
|
||||
usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
|
||||
self.assertEqual('NOT_IN_USE', usage_state)
|
||||
|
||||
# 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']['vnfcResourceInfo']
|
||||
after_vdu2_ids = [vnfc_info['id'] for vnfc_info in vnfc_resource_infos
|
||||
if vnfc_info['vduId'] == 'VDU2']
|
||||
self.assertEqual(before_vdu2_ids, after_vdu2_ids)
|
||||
|
||||
# 7. Terminate a VNF instance
|
||||
terminate_req = paramgen.helm_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)
|
||||
|
||||
# check deletion of VNF instance
|
||||
resp, body = self.show_vnf_instance(inst_id)
|
||||
self.assertEqual(404, resp.status_code)
|
@ -0,0 +1,74 @@
|
||||
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import vnfd_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import helm
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.tests.unit import base
|
||||
|
||||
|
||||
CNF_SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d70a1177"
|
||||
|
||||
|
||||
class TestHelm(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHelm, self).setUp()
|
||||
objects.register_all()
|
||||
self.driver = helm.Helm()
|
||||
|
||||
cur_dir = os.path.dirname(__file__)
|
||||
# NOTE: bollow a sample of k8s at the moment since it is enough
|
||||
# for current tests.
|
||||
sample_dir = os.path.join(cur_dir, "../..", "samples")
|
||||
self.vnfd_1 = vnfd_utils.Vnfd(CNF_SAMPLE_VNFD_ID)
|
||||
self.vnfd_1.init_from_csar_dir(os.path.join(sample_dir, "sample2"))
|
||||
|
||||
def test_scale_invalid_parameter(self):
|
||||
req = objects.ScaleVnfRequest(
|
||||
type='SCALE_OUT',
|
||||
aspectId='vdu1_aspect',
|
||||
numberOfSteps=1
|
||||
)
|
||||
inst_vnf_info = objects.VnfInstanceV2_InstantiatedVnfInfo(
|
||||
vnfcResourceInfo=[objects.VnfcResourceInfoV2(vduId='VDU1')],
|
||||
metadata={
|
||||
'namespace': 'default',
|
||||
'release_name': 'test-release',
|
||||
'helm_chart_path': 'Files/kubernetes/test-chart.tgz',
|
||||
'helm_value_names': {'VDU2': {'replica': 'values.replica'}}
|
||||
}
|
||||
)
|
||||
inst = objects.VnfInstanceV2(
|
||||
instantiatedVnfInfo=inst_vnf_info
|
||||
)
|
||||
grant_req = objects.GrantRequestV1(
|
||||
addResources=[
|
||||
objects.ResourceDefinitionV1(
|
||||
type='COMPUTE',
|
||||
resourceTemplateId='VDU1'
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
expected_ex = sol_ex.HelmParameterNotFound(vdu_name='VDU1')
|
||||
ex = self.assertRaises(sol_ex.HelmParameterNotFound,
|
||||
self.driver._scale, req, inst, grant_req, mock.Mock(),
|
||||
self.vnfd_1, mock.Mock(), mock.Mock())
|
||||
self.assertEqual(expected_ex.detail, ex.detail)
|
Loading…
Reference in New Issue
Block a user