tacker/tacker/sol_refactored/infra_drivers/kubernetes/kubernetes.py

314 lines
13 KiB
Python

# Copyright (C) 2022 Fujitsu
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from kubernetes import client
from oslo_log import log as logging
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
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_utils
LOG = logging.getLogger(__name__)
CONF = config.CONF
CHECK_INTERVAL = 10
class Kubernetes(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()
self._instantiate(req, inst, grant_req, grant, vnfd,
k8s_api_client)
def _instantiate(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
target_k8s_files = req.additionalParams['lcm-kubernetes-def-files']
k8s_reses, namespace = self._setup_k8s_reses(
vnfd, target_k8s_files, k8s_api_client,
req.additionalParams.get('namespace'), inst.id)
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
vdu_reses = self._select_vdu_reses(vnfd, req.flavourId, k8s_reses)
for vdu_name, vdu_res in vdu_reses.items():
if vdu_name not in vdus_num:
LOG.debug(f'resource name {vdu_res.name} in the kubernetes'
f' manifest does not match the VNFD.')
continue
if vdu_res.kind in kubernetes_utils.SCALABLE_KIND:
vdu_res.body['spec']['replicas'] = vdus_num[vdu_name]
# deploy k8s resources
for k8s_res in k8s_reses:
if not k8s_res.is_exists():
k8s_res.create()
# wait k8s resource create complete
self._wait_k8s_reses_ready(k8s_reses)
# make instantiated info
self._init_instantiated_vnf_info(inst, req.flavourId, 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,
namespace, inst_id):
# NOTE: this check should be done in STARTING phase.
vnf_artifact_files = vnfd.get_vnf_artifact_files()
diff_files = set(target_k8s_files) - set(vnf_artifact_files)
if diff_files:
diff_files = ','.join(list(diff_files))
raise sol_ex.CnfDefinitionNotFound(diff_files=diff_files)
# get k8s content from yaml file
return kubernetes_utils.get_k8s_reses_from_json_files(
target_k8s_files, vnfd, k8s_api_client, namespace, inst_id)
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()
self._instantiate_rollback(req, inst, grant_req, grant, vnfd,
k8s_api_client)
def _instantiate_rollback(self, req, inst, grant_req, grant, vnfd,
k8s_api_client):
target_k8s_files = req.additionalParams['lcm-kubernetes-def-files']
try:
k8s_reses, _ = self._setup_k8s_reses(
vnfd, target_k8s_files, k8s_api_client,
req.additionalParams.get('namespace'), inst.id)
except sol_ex.SolException:
# it means it failed in a basic check and it failes always.
# nothing to do since instantiate failed in it too.
return
k8s_reses.reverse()
# delete k8s resources
body = client.V1DeleteOptions(propagation_policy='Foreground')
self._delete_k8s_resource(k8s_reses, body)
# wait k8s resource delete complete
self._wait_k8s_reses_deleted(k8s_reses)
def _delete_k8s_resource(self, k8s_reses, body):
for k8s_res in k8s_reses:
if k8s_res.is_exists():
k8s_res.delete(body)
def terminate(self, req, inst, grant_req, grant, vnfd):
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._terminate(req, inst, grant_req, grant, vnfd,
k8s_api_client)
def _terminate(self, req, inst, grant_req, grant, vnfd, k8s_api_client):
target_k8s_files = inst.instantiatedVnfInfo.metadata[
'lcm-kubernetes-def-files']
# get k8s content from yaml file
namespace = inst.instantiatedVnfInfo.metadata['namespace']
k8s_reses, _ = kubernetes_utils.get_k8s_reses_from_json_files(
target_k8s_files, vnfd, k8s_api_client, namespace, inst.id)
k8s_reses.reverse()
# delete k8s resources
timeout = 0
if req.terminationType == 'GRACEFUL':
timeout = CONF.v2_vnfm.default_graceful_termination_timeout
if req.obj_attr_is_set('gracefulTerminationTimeout'):
timeout = req.gracefulTerminationTimeout
body = client.V1DeleteOptions(propagation_policy='Foreground',
grace_period_seconds=timeout)
self._delete_k8s_resource(k8s_reses, body)
# wait k8s resource delete complete
self._wait_k8s_reses_deleted(k8s_reses)
def _change_vnfpkg_rolling_update(
self, inst, grant_req, grant, vnfd, k8s_api_client,
namespace, old_pods_names):
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
vdu_reses = []
for vdu_name, vdu_num in vdus_num.items():
vdu_res = self._get_vdu_res(inst, k8s_api_client, vdu_name)
vdu_res.body['spec']['replicas'] = vdu_num
vdu_reses.append(vdu_res)
# apply new deployment
for vdu_res in vdu_reses:
vdu_res.patch()
# wait k8s resource update complete
self._wait_k8s_reses_updated(
vdu_reses, k8s_api_client, namespace, old_pods_names)
# update cnf instantiated info
self._update_vnfc_info(inst, k8s_api_client)
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
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._change_vnfpkg(req, inst, grant_req, grant, vnfd,
k8s_api_client)
def _change_vnfpkg(self, req, inst, grant_req, grant, vnfd,
k8s_api_client):
if req.additionalParams['upgrade_type'] == 'RollingUpdate':
target_k8s_files = req.additionalParams[
'lcm-kubernetes-def-files']
namespace = inst.instantiatedVnfInfo.metadata['namespace']
target_vdus = {res_def.resourceTemplateId
for res_def in grant_req.addResources
if res_def.type == 'COMPUTE'}
old_pods_names = {vnfc.computeResource.resourceId
for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo
if vnfc.vduId in target_vdus}
k8s_reses, _ = self._setup_k8s_reses(
vnfd, target_k8s_files, k8s_api_client, namespace, inst.id)
vdu_reses = self._select_vdu_reses(
vnfd, inst.instantiatedVnfInfo.flavourId, k8s_reses)
self._init_instantiated_vnf_info(
inst, inst.instantiatedVnfInfo.flavourId, vdu_reses,
namespace, target_k8s_files)
self._change_vnfpkg_rolling_update(
inst, grant_req, grant, vnfd, k8s_api_client, namespace,
old_pods_names)
else:
# not reach here
pass
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()
self._change_vnfpkg_rollback(req, inst, grant_req, grant, vnfd,
k8s_api_client)
def _change_vnfpkg_rollback(self, req, inst, grant_req, grant, vnfd,
k8s_api_client):
if req.additionalParams['upgrade_type'] == 'RollingUpdate':
namespace = inst.instantiatedVnfInfo.metadata['namespace']
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
self._change_vnfpkg_rolling_update(
inst, grant_req, grant, vnfd, k8s_api_client, namespace,
old_pods_names)
else:
# not reach here
pass
def _scale_k8s_resource(self, inst, vdus_num, k8s_api_client):
namespace = inst.instantiatedVnfInfo.metadata['namespace']
vdu_reses = []
for vdu_name, vdu_num in vdus_num.items():
vdu_res = self._get_vdu_res(inst, k8s_api_client, vdu_name)
if vdu_res.kind not in kubernetes_utils.SCALABLE_KIND:
LOG.error(f'scale vdu {vdu_name}'
f' is not scalable resource')
continue
vdu_res.scale(vdu_num)
vdu_reses.append(vdu_res)
# wait k8s resource create complete
self._wait_k8s_reses_updated(vdu_reses, k8s_api_client,
namespace, old_pods_names=set())
# make instantiated info
self._update_vnfc_info(inst, k8s_api_client)
def scale(self, req, inst, grant_req, grant, vnfd):
if req.type == 'SCALE_OUT':
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
for vdu_name, vdu_num in vdus_num.items():
vdus_num[vdu_name] = (self._get_current_vdu_num(inst, vdu_name)
+ vdu_num)
elif req.type == 'SCALE_IN':
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.removeResources)
for vdu_name, vdu_num in vdus_num.items():
vdus_num[vdu_name] = (self._get_current_vdu_num(inst, vdu_name)
- vdu_num)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._scale_k8s_resource(inst, vdus_num, k8s_api_client)
def scale_rollback(self, req, inst, grant_req, grant, vnfd):
vdus_num = self._get_vdus_num_from_grant_req_res_defs(
grant_req.addResources)
for vdu_name, _ in vdus_num.items():
vdus_num[vdu_name] = self._get_current_vdu_num(inst, vdu_name)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
with kubernetes_utils.AuthContextManager(vim_info) as acm:
k8s_api_client = acm.init_k8s_api_client()
self._scale_k8s_resource(inst, vdus_num, k8s_api_client)
def _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
return {vdu_ids[res.name]: res
for res in k8s_reses
if (res.kind in kubernetes_utils.TARGET_KIND
and res.name in vdu_ids)}
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 _is_match_pod_naming_rule(self, rsc_kind, rsc_name, pod_name):
return self.is_match_pod_naming(rsc_kind, rsc_name, pod_name)