782 lines
34 KiB
Python
782 lines
34 KiB
Python
# 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 re
|
|
import toscaparser.utils.yamlparser
|
|
from urllib.parse import urlparse
|
|
import urllib.request as urllib2
|
|
import yaml
|
|
|
|
from kubernetes import client
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from tacker.common import exceptions
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
|
|
NEWLINE_CHARACTER = "\n"
|
|
COLON_CHARACTER = ':'
|
|
WHITE_SPACE_CHARACTER = ' '
|
|
NON_WHITE_SPACE_CHARACTER = ''
|
|
HYPHEN_CHARACTER = '-'
|
|
DASH_CHARACTER = '_'
|
|
# Due to the dependency of k8s resource creation, according to the design,
|
|
# other resources (resources not mentioned in self.RESOURCE_CREATION_SORT)
|
|
# will be created after the NetworkPolicy resource. This number is a flag
|
|
# to ensure that when multiple resources are to be created, the order of
|
|
# other resources is after NetworkPolicy and before Service.
|
|
OTHER_RESOURCE_SORT_POSITION = 8
|
|
|
|
|
|
class Transformer(object):
|
|
"""Transform TOSCA template to Kubernetes resources"""
|
|
|
|
def __init__(self, core_v1_api_client, app_v1_api_client,
|
|
scaling_api_client, k8s_client_dict):
|
|
# the old param used when creating vnf with TOSCA template
|
|
self.core_v1_api_client = core_v1_api_client
|
|
self.app_v1_api_client = app_v1_api_client
|
|
self.scaling_api_client = scaling_api_client
|
|
# the new param used when instantiating vnf with addtionalParams
|
|
self.k8s_client_dict = k8s_client_dict
|
|
self.RESOURCE_CREATION_ORDER = [
|
|
'StorageClass',
|
|
'PersistentVolume',
|
|
'PriorityClass',
|
|
'Namespace',
|
|
'LimitRange',
|
|
'ResourceQuota',
|
|
'HorizontalPodAutoscaler',
|
|
'NetworkPolicy',
|
|
'Service',
|
|
'Endpoints',
|
|
'PersistentVolumeClaim',
|
|
'ConfigMap',
|
|
'Secret',
|
|
'StatefulSet',
|
|
'Job',
|
|
'Deployment',
|
|
'DaemonSet',
|
|
'Pod'
|
|
]
|
|
self.method_value = {
|
|
"Pod": 'create_namespaced_pod',
|
|
"Service": 'create_namespaced_service',
|
|
"ConfigMap": 'create_namespaced_config_map',
|
|
"Secret": 'create_namespaced_secret',
|
|
"PersistentVolumeClaim":
|
|
'create_namespaced_persistent_volume_claim',
|
|
"LimitRange": 'create_namespaced_limit_range',
|
|
"PodTemplate": 'create_namespaced_pod_template',
|
|
"Binding": 'create_namespaced_binding',
|
|
"Namespace": 'create_namespace',
|
|
"Node": 'create_node',
|
|
"PersistentVolume": 'create_persistent_volume',
|
|
"ResourceQuota": 'create_namespaced_resource_quota',
|
|
"ServiceAccount": 'create_namespaced_service_account',
|
|
"APIService": 'create_api_service',
|
|
"DaemonSet": 'create_namespaced_daemon_set',
|
|
"Deployment": 'create_namespaced_deployment',
|
|
"ReplicaSet": 'create_namespaced_replica_set',
|
|
"StatefulSet": 'create_namespaced_stateful_set',
|
|
"ControllerRevision": 'create_namespaced_controller_revision',
|
|
"TokenReview": 'create_token_review',
|
|
"LocalSubjectAccessReview": 'create_namespaced_local_'
|
|
'subject_access_review',
|
|
"SelfSubjectAccessReview": 'create_self_subject_access_review',
|
|
"SelfSubjectRulesReview": 'create_self_subject_rules_review',
|
|
"SubjectAccessReview": 'create_subject_access_review',
|
|
"HorizontalPodAutoscaler": 'create_namespaced_horizontal_'
|
|
'pod_autoscaler',
|
|
"Job": 'create_namespaced_job',
|
|
"Lease": 'create_namespaced_lease',
|
|
"NetworkPolicy": 'create_namespaced_network_policy',
|
|
"ClusterRole": 'create_cluster_role',
|
|
"ClusterRoleBinding": 'create_cluster_role_binding',
|
|
"Role": 'create_namespaced_role',
|
|
"RoleBinding": 'create_namespaced_role_binding',
|
|
"PriorityClass": 'create_priority_class',
|
|
"StorageClass": 'create_storage_class',
|
|
"VolumeAttachment": 'create_volume_attachment',
|
|
}
|
|
|
|
def transform(self, tosca_kube_objects):
|
|
"""transform function translates from tosca_kube_object to
|
|
|
|
kubernetes_object (ConfigMap, Deployment, Service, HPA)
|
|
"""
|
|
|
|
# kubernetes_objects store all kubernetes objects that are transformed
|
|
# from TOSCA VNF template
|
|
kubernetes_objects = dict()
|
|
for tosca_kube_obj in tosca_kube_objects:
|
|
namespace = tosca_kube_obj.namespace
|
|
kubernetes_objects['namespace'] = namespace
|
|
kubernetes_objects['objects'] = list()
|
|
kube_obj_name = tosca_kube_obj.name
|
|
new_kube_obj_name = self.pre_process_name(kube_obj_name)
|
|
|
|
# translate environments to ConfigMap objects
|
|
for container in tosca_kube_obj.containers:
|
|
config_map_object = \
|
|
self.init_configmap(container_props=container,
|
|
kube_obj_name=new_kube_obj_name)
|
|
if config_map_object:
|
|
kubernetes_objects['objects'].append(config_map_object)
|
|
|
|
# translate Deployment object
|
|
deployment_object = \
|
|
self.init_deployment(tosca_kube_obj=tosca_kube_obj,
|
|
kube_obj_name=new_kube_obj_name)
|
|
kubernetes_objects['objects'].append(deployment_object)
|
|
|
|
# translate to Horizontal Pod Autoscaler object
|
|
hpa_object = self.init_hpa(tosca_kube_obj=tosca_kube_obj,
|
|
kube_obj_name=new_kube_obj_name)
|
|
if hpa_object:
|
|
kubernetes_objects['objects'].append(hpa_object)
|
|
|
|
# translate to Service object
|
|
service_object = self.init_service(
|
|
tosca_kube_obj=tosca_kube_obj,
|
|
kube_obj_name=new_kube_obj_name)
|
|
kubernetes_objects['objects'].append(service_object)
|
|
|
|
return kubernetes_objects
|
|
|
|
def _create_k8s_object(self, kind, file_content_dict):
|
|
# must_param referring K8s official object page
|
|
# *e.g:https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Service.md
|
|
# initiating k8s object, you need to
|
|
# give the must param an empty value.
|
|
must_param = {
|
|
'V1LocalSubjectAccessReview': '(spec="")',
|
|
'V1HTTPGetAction': '(port="")',
|
|
'V1DeploymentSpec': '(selector="", template="")',
|
|
'V1PodSpec': '(containers=[])',
|
|
'V1ConfigMapKeySelector': '(key="")',
|
|
'V1Container': '(name="")',
|
|
'V1EnvVar': '(name="")',
|
|
'V1SecretKeySelector': '(key="")',
|
|
'V1ContainerPort': '(container_port="")',
|
|
'V1VolumeMount': '(mount_path="", name="")',
|
|
'V1PodCondition': '(status="", type="")',
|
|
'V1ContainerStatus': '('
|
|
'image="", image_id="", '
|
|
'name="", ready="", '
|
|
'restart_count="")',
|
|
'V1ServicePort': '(port=80)',
|
|
'V1TypedLocalObjectReference': '(kind="", name="")',
|
|
'V1LabelSelectorRequirement': '(key="", operator="")',
|
|
'V1PersistentVolumeClaimCondition': '(status="", type="")',
|
|
'V1AWSElasticBlockStoreVolumeSource': '(volume_id="")',
|
|
'V1AzureDiskVolumeSource': '(disk_name="", disk_uri="")',
|
|
'V1AzureFileVolumeSource': '(secret_name="", share_name="")',
|
|
'V1CephFSVolumeSource': '(monitors=[])',
|
|
'V1CinderVolumeSource': '(volume_id="")',
|
|
'V1KeyToPath': '(key="", path="")',
|
|
'V1CSIVolumeSource': '(driver="")',
|
|
'V1DownwardAPIVolumeFile': '(path="")',
|
|
'V1ObjectFieldSelector': '(field_path="")',
|
|
'V1ResourceFieldSelector': '(resource="")',
|
|
'V1FlexVolumeSource': '(driver="")',
|
|
'V1GCEPersistentDiskVolumeSource': '(pd_name="")',
|
|
'V1GitRepoVolumeSource': '(repository="")',
|
|
'V1GlusterfsVolumeSource': '(endpoints="", path="")',
|
|
'V1HostPathVolumeSource': '(path="")',
|
|
'V1ISCSIVolumeSource': '(iqn="", lun=0, target_portal="")',
|
|
'V1Volume': '(name="")',
|
|
'V1NFSVolumeSource': '(path="", server="")',
|
|
'V1PersistentVolumeClaimVolumeSource': '(claim_name="")',
|
|
'V1PhotonPersistentDiskVolumeSource': '(pd_id="")',
|
|
'V1PortworxVolumeSource': '(volume_id="")',
|
|
'V1ProjectedVolumeSource': '(sources=[])',
|
|
'V1ServiceAccountTokenProjection': '(path="")',
|
|
'V1QuobyteVolumeSource': '(registry="", volume="")',
|
|
'V1RBDVolumeSource': '(image="", monitors=[])',
|
|
'V1ScaleIOVolumeSource': '('
|
|
'gateway="", secret_ref="", '
|
|
'system="")',
|
|
'V1VsphereVirtualDiskVolumeSource': '(volume_path="")',
|
|
'V1LimitRangeSpec': '(limits=[])',
|
|
'V1Binding': '(target="")',
|
|
'V1ComponentCondition': '(status="", type="")',
|
|
'V1NamespaceCondition': '(status="", type="")',
|
|
'V1ConfigMapNodeConfigSource': '(kubelet_config_key="", '
|
|
'name="", namespace="")',
|
|
'V1Taint': '(effect="", key="")',
|
|
'V1NodeAddress': '(address="", type="")',
|
|
'V1NodeCondition': '(status="", type="")',
|
|
'V1DaemonEndpoint': '(port=0)',
|
|
'V1ContainerImage': '(names=[])',
|
|
'V1NodeSystemInfo': '(architecture="", boot_id="", '
|
|
'container_runtime_version="",'
|
|
'kernel_version="", '
|
|
'kube_proxy_version="", '
|
|
'kubelet_version="",'
|
|
'machine_id="", operating_system="", '
|
|
'os_image="", system_uuid="")',
|
|
'V1AttachedVolume': '(device_path="", name="")',
|
|
'V1ScopedResourceSelectorRequirement':
|
|
'(operator="", scope_name="")',
|
|
'V1APIServiceSpec': '(group_priority_minimum=0, '
|
|
'service="", '
|
|
'version_priority=0)',
|
|
'V1APIServiceCondition': '(status="", type="")',
|
|
'V1DaemonSetSpec': '(selector="", template="")',
|
|
'V1ReplicaSetSpec': '(selector="")',
|
|
'V1StatefulSetSpec': '(selector="", '
|
|
'service_name="", template="")',
|
|
'V1StatefulSetCondition': '(status="", type="")',
|
|
'V1StatefulSetStatus': '(replicas=0)',
|
|
'V1ControllerRevision': '(revision=0)',
|
|
'V1TokenReview': '(spec="")',
|
|
'V1SubjectAccessReviewStatus': '(allowed=True)',
|
|
'V1SelfSubjectAccessReview': '(spec="")',
|
|
'V1SelfSubjectRulesReview': '(spec="")',
|
|
'V1SubjectRulesReviewStatus': '(incomplete=True, '
|
|
'non_resource_rules=[], '
|
|
'resource_rules=[])',
|
|
'V1NonResourceRule': '(verbs=[])',
|
|
'V1SubjectAccessReview': '(spec="")',
|
|
'V1HorizontalPodAutoscalerSpec':
|
|
'(max_replicas=0, scale_target_ref="")',
|
|
'V1CrossVersionObjectReference': '(kind="", name="")',
|
|
'V1HorizontalPodAutoscalerStatus':
|
|
'(current_replicas=0, desired_replicas=0)',
|
|
'V1JobSpec': '(template="")',
|
|
'V1NetworkPolicySpec': '(pod_selector="")',
|
|
'V1PolicyRule': '(verbs=[])',
|
|
'V1ClusterRoleBinding': '(role_ref="")',
|
|
'V1RoleRef': '(api_group="", kind="", name="")',
|
|
'V1Subject': '(kind="", name="")',
|
|
'V1RoleBinding': '(role_ref="")',
|
|
'V1PriorityClass': '(value=0)',
|
|
'V1StorageClass': '(provisioner="")',
|
|
'V1TopologySelectorLabelRequirement': '(key="", values=[])',
|
|
'V1VolumeAttachment': '(spec="")',
|
|
'V1VolumeAttachmentSpec':
|
|
'(attacher="", node_name="", source="")',
|
|
'V1VolumeAttachmentStatus': '(attached=True)',
|
|
'V1NodeSelector': '(node_selector_terms=[])',
|
|
'V1NodeSelectorRequirement': '(key="", operator="")',
|
|
'V1PreferredSchedulingTerm': '(preference="", weight=1)',
|
|
'V1PodAffinityTerm': '(topology_key="")',
|
|
'V1WeightedPodAffinityTerm': '(pod_affinity_term="", weight=1)',
|
|
'V1OwnerReference': '(api_version="", kind="", name="", uid="")',
|
|
'V1HTTPHeader': '(name="", value="")',
|
|
'V1TCPSocketAction': '(port="")',
|
|
'V1VolumeDevice': '(device_path="", name="")',
|
|
'V1PodReadinessGate': '(condition_type="")',
|
|
'V1Sysctl': '(name="", value="")',
|
|
'V1ContainerStateTerminated': '(exit_code=0)',
|
|
'V1AzureFilePersistentVolumeSource': '(secret_name="",'
|
|
' share_name="")',
|
|
'V1CephFSPersistentVolumeSource': '(monitors=[])',
|
|
'V1CinderPersistentVolumeSource': '(volume_id="")',
|
|
'V1CSIPersistentVolumeSource': '(driver="", volume_handle="")',
|
|
'V1FlexPersistentVolumeSource': '(driver="")',
|
|
'V1GlusterfsPersistentVolumeSource': '(endpoints="", path="")',
|
|
'V1ISCSIPersistentVolumeSource': '(iqn="", lun=0,'
|
|
' target_portal="")',
|
|
'V1LocalVolumeSource': '(path="")',
|
|
'V1RBDPersistentVolumeSource': '(image="", monitors=[])',
|
|
'V1ScaleIOPersistentVolumeSource': '('
|
|
'gateway="",'
|
|
' secret_ref="",'
|
|
' system="")',
|
|
'V1DaemonSetStatus': '(current_number_scheduled=0, '
|
|
'desired_number_scheduled=0, '
|
|
'number_misscheduled=0, '
|
|
'number_ready=0)',
|
|
'V1DaemonSetCondition': '(status="", type="")',
|
|
'V1DeploymentCondition': '(status="", type="")',
|
|
'V1ReplicaSetStatus': '(replicas=0)',
|
|
'V1ReplicaSetCondition': '(status="", type="")',
|
|
'V1ResourceRule': '(verbs=[])',
|
|
'V1JobCondition': '(status="", type="")',
|
|
'V1IPBlock': '(cidr="")',
|
|
'V1EphemeralContainer': '(name="")',
|
|
'V1TopologySpreadConstraint': '(max_skew=0, topology_key="",'
|
|
' when_unsatisfiable="")',
|
|
'V1LimitRangeItem': '(type="")'
|
|
}
|
|
whole_kind = 'V1' + kind
|
|
if whole_kind in must_param.keys():
|
|
k8s_obj = eval('client.V1' + kind + must_param.get(whole_kind))
|
|
else:
|
|
k8s_obj = eval('client.V1' + kind + '()')
|
|
self._init_k8s_obj(k8s_obj, file_content_dict, must_param)
|
|
return k8s_obj
|
|
|
|
def _get_k8s_obj_from_file_content_dict(self, file_content_dict,
|
|
namespace=None):
|
|
k8s_obj = {}
|
|
kind = file_content_dict.get('kind', '')
|
|
try:
|
|
k8s_obj['object'] = self._create_k8s_object(
|
|
kind, file_content_dict)
|
|
except Exception as e:
|
|
if isinstance(e, client.rest.ApiException):
|
|
msg = '{kind} create failure. Reason={reason}'.format(
|
|
kind=file_content_dict.get('kind', ''), reason=e.body)
|
|
else:
|
|
msg = '{kind} create failure. Reason={reason}'.format(
|
|
kind=file_content_dict.get('kind', ''), reason=e)
|
|
LOG.error(msg)
|
|
raise exceptions.InitApiFalse(error=msg)
|
|
|
|
k8s_obj['namespace'] = namespace
|
|
if k8s_obj['object'].metadata:
|
|
k8s_obj['object'].metadata.namespace = namespace
|
|
|
|
return k8s_obj
|
|
|
|
def get_k8s_objs_from_yaml(self, artifact_files, vnf_package_path,
|
|
namespace=None):
|
|
k8s_objs = []
|
|
for artifact_file in artifact_files:
|
|
if ((urlparse(artifact_file).scheme == 'file') or
|
|
(bool(urlparse(artifact_file).scheme) and
|
|
bool(urlparse(artifact_file).netloc))):
|
|
file_content = urllib2.urlopen(artifact_file).read()
|
|
else:
|
|
artifact_file_path = os.path.join(
|
|
vnf_package_path, artifact_file)
|
|
with open(artifact_file_path, 'r') as f:
|
|
file_content = f.read()
|
|
file_content_dicts = list(yaml.safe_load_all(file_content))
|
|
for file_content_dict in file_content_dicts:
|
|
k8s_obj = self._get_k8s_obj_from_file_content_dict(
|
|
file_content_dict, namespace)
|
|
k8s_objs.append(k8s_obj)
|
|
return k8s_objs
|
|
|
|
def get_k8s_objs_from_manifest(self, mf_content, namespace):
|
|
mkobj_kind_list = [
|
|
"Pod",
|
|
"Service",
|
|
"PersistentVolumeClaim",
|
|
"Namespace",
|
|
"Node",
|
|
"PersistentVolume",
|
|
"DaemonSet",
|
|
"Deployment",
|
|
"ReplicaSet",
|
|
"StatefulSet",
|
|
"Job"
|
|
]
|
|
k8s_objs = []
|
|
mf_content_dicts = list(yaml.safe_load_all(mf_content))
|
|
for mf_content_dict in mf_content_dicts:
|
|
kind = mf_content_dict.get('kind', '')
|
|
if kind in mkobj_kind_list:
|
|
k8s_obj = self._get_k8s_obj_from_file_content_dict(
|
|
file_content_dict=mf_content_dict,
|
|
namespace=namespace)
|
|
k8s_objs.append(k8s_obj)
|
|
return k8s_objs
|
|
|
|
def _select_k8s_client_and_api(
|
|
self, kind, namespace, api_version, body):
|
|
k8s_client_obj = self.k8s_client_dict[api_version]
|
|
if 'namespaced' in self.method_value[kind]:
|
|
response = getattr(k8s_client_obj, self.method_value.get(kind))(
|
|
namespace=namespace, body=body
|
|
)
|
|
else:
|
|
response = getattr(k8s_client_obj, self.method_value.get(kind))(
|
|
body=body
|
|
)
|
|
return response
|
|
|
|
def deploy(self, kubernetes_objects):
|
|
"""Deploy Kubernetes objects on Kubernetes VIM and return
|
|
|
|
a list name of services
|
|
"""
|
|
deployment_names = list()
|
|
namespace = kubernetes_objects.get('namespace')
|
|
k8s_objects = kubernetes_objects.get('objects')
|
|
|
|
for k8s_object in k8s_objects:
|
|
object_type = k8s_object.kind
|
|
|
|
if object_type == 'ConfigMap':
|
|
self.core_v1_api_client.create_namespaced_config_map(
|
|
namespace=namespace,
|
|
body=k8s_object)
|
|
LOG.debug('Successfully created ConfigMap %s',
|
|
k8s_object.metadata.name)
|
|
elif object_type == 'Deployment':
|
|
self.app_v1_api_client.create_namespaced_deployment(
|
|
namespace=namespace,
|
|
body=k8s_object)
|
|
LOG.debug('Successfully created Deployment %s',
|
|
k8s_object.metadata.name)
|
|
elif object_type == 'HorizontalPodAutoscaler':
|
|
self.scaling_api_client. \
|
|
create_namespaced_horizontal_pod_autoscaler(
|
|
namespace=namespace,
|
|
body=k8s_object)
|
|
LOG.debug('Successfully created Horizontal Pod Autoscaler %s',
|
|
k8s_object.metadata.name)
|
|
elif object_type == 'Service':
|
|
self.core_v1_api_client.create_namespaced_service(
|
|
namespace=namespace,
|
|
body=k8s_object)
|
|
LOG.debug('Successfully created Service %s',
|
|
k8s_object.metadata.name)
|
|
deployment_names.append(namespace)
|
|
deployment_names.append(k8s_object.metadata.name)
|
|
|
|
# return a string that contains all deployment namespace and names
|
|
# for tracking resources pattern:
|
|
# namespace1,deployment1,namespace2,deployment2,namespace3,deployment3
|
|
return ",".join(deployment_names)
|
|
|
|
def deploy_k8s(self, kubernetes_objects):
|
|
"""Deploy kubernetes
|
|
|
|
Deploy Kubernetes objects on Kubernetes VIM and
|
|
return a list name of services
|
|
"""
|
|
kubernetes_objects = self._sort_k8s_obj(kubernetes_objects)
|
|
new_k8s_objs = list()
|
|
for kubernetes_object in kubernetes_objects:
|
|
namespace = kubernetes_object.get('namespace', '')
|
|
kind = kubernetes_object.get('object', '').kind
|
|
api_version = kubernetes_object.get('object', '').api_version
|
|
body = kubernetes_object.get('object', '')
|
|
if kubernetes_object.get('object', '').metadata:
|
|
name = kubernetes_object.get('object', '').metadata.name
|
|
else:
|
|
name = ''
|
|
try:
|
|
LOG.debug("{kind} begin create.".format(kind=kind))
|
|
self._select_k8s_client_and_api(
|
|
kind, namespace, api_version, body)
|
|
kubernetes_object['status'] = 'Creating'
|
|
except Exception as e:
|
|
if isinstance(e, client.rest.ApiException):
|
|
kubernetes_object['status'] = 'creating_failed'
|
|
msg = '''The request to create a resource failed.
|
|
namespace: {namespace}, name: {name},kind: {kind},
|
|
Reason: {exception}'''.format(
|
|
namespace=namespace, name=name, kind=kind,
|
|
exception=e.body
|
|
)
|
|
else:
|
|
kubernetes_object['status'] = 'creating_failed'
|
|
msg = '''The request to create a resource failed.
|
|
namespace: {namespace}, name: {name},kind: {kind},
|
|
Reason: {exception}'''.format(
|
|
namespace=namespace, name=name, kind=kind,
|
|
exception=e
|
|
)
|
|
LOG.error(msg)
|
|
raise exceptions.CreateApiFalse(error=msg)
|
|
new_k8s_objs.append(kubernetes_object)
|
|
return new_k8s_objs
|
|
|
|
def _get_lower_case_name(self, name):
|
|
name = name.strip()
|
|
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
|
|
|
|
def _init_k8s_obj(self, obj, content, must_param):
|
|
for key, value in content.items():
|
|
param_value = self._get_lower_case_name(key)
|
|
if hasattr(obj, param_value) and \
|
|
not isinstance(value, dict) and \
|
|
not isinstance(value, list):
|
|
setattr(obj, param_value, value)
|
|
elif isinstance(value, dict):
|
|
obj_name = obj.openapi_types.get(param_value)
|
|
if obj_name == 'dict(str, str)':
|
|
setattr(obj, param_value, value)
|
|
else:
|
|
if obj_name in must_param.keys():
|
|
rely_obj = eval('client.' + obj_name +
|
|
must_param.get(obj_name))
|
|
else:
|
|
rely_obj = eval('client.' + obj_name + '()')
|
|
self._init_k8s_obj(rely_obj, value, must_param)
|
|
setattr(obj, param_value, rely_obj)
|
|
elif isinstance(value, list):
|
|
obj_name = obj.openapi_types.get(param_value)
|
|
if obj_name == 'list[str]':
|
|
setattr(obj, param_value, value)
|
|
else:
|
|
rely_objs = []
|
|
rely_obj_name = \
|
|
re.findall(r".*\[([^\[\]]*)\].*", obj_name)[0]
|
|
for v in value:
|
|
if rely_obj_name in must_param.keys():
|
|
rely_obj = eval('client.' + rely_obj_name +
|
|
must_param.get(rely_obj_name))
|
|
else:
|
|
rely_obj = \
|
|
eval('client.' + rely_obj_name + '()')
|
|
self._init_k8s_obj(rely_obj, v, must_param)
|
|
rely_objs.append(rely_obj)
|
|
setattr(obj, param_value, rely_objs)
|
|
|
|
def _sort_k8s_obj(self, k8s_objs):
|
|
pos = 0
|
|
objs = k8s_objs
|
|
sorted_k8s_objs = list()
|
|
for sort_index, kind in enumerate(self.RESOURCE_CREATION_ORDER):
|
|
for obj_index, obj in enumerate(objs):
|
|
if obj["object"].kind == kind:
|
|
sorted_k8s_objs.append(objs.pop(obj_index))
|
|
if sort_index == OTHER_RESOURCE_SORT_POSITION:
|
|
pos = len(sorted_k8s_objs)
|
|
for obj in objs:
|
|
sorted_k8s_objs.insert(pos, obj)
|
|
|
|
return sorted_k8s_objs
|
|
|
|
def get_object_meta(self, content):
|
|
must_param = {}
|
|
v1_object_meta = client.V1ObjectMeta()
|
|
self._init_k8s_obj(v1_object_meta, content, must_param)
|
|
return v1_object_meta
|
|
|
|
# config_labels configures label
|
|
def config_labels(self, deployment_name=None, scaling_name=None):
|
|
label = dict()
|
|
if deployment_name:
|
|
label.update({"selector": deployment_name})
|
|
if scaling_name:
|
|
label.update({"scaling_name": scaling_name})
|
|
return label
|
|
|
|
# Init resource requirement for container
|
|
def init_resource_requirements(self, container):
|
|
limits = dict()
|
|
requests = dict()
|
|
if container.num_cpus:
|
|
limits.update({'cpu': container.num_cpus})
|
|
requests.update({'cpu': container.num_cpus})
|
|
if container.mem_size:
|
|
limits.update({'memory': container.mem_size})
|
|
requests.update({'memory': container.mem_size})
|
|
return client.V1ResourceRequirements(limits=limits,
|
|
requests=requests)
|
|
|
|
def init_envs(self, container_props, name):
|
|
config = container_props.config
|
|
config_dict = self.pre_process_config(config)
|
|
configmap_name = name
|
|
|
|
list_envs = []
|
|
for key in config_dict:
|
|
config_map_ref = client.V1ConfigMapKeySelector(
|
|
key=key,
|
|
name=configmap_name)
|
|
env_var = client.V1EnvVarSource(
|
|
config_map_key_ref=config_map_ref)
|
|
env_object = client.V1EnvVar(
|
|
name=key,
|
|
value_from=env_var)
|
|
list_envs.append(env_object)
|
|
return list_envs
|
|
|
|
# Init container object
|
|
def init_containers(self, container_props, limit_resource, name):
|
|
list_env_var = self.init_envs(container_props, name)
|
|
container_name = self.pre_process_name(container_props.name)
|
|
list_container_port = list()
|
|
if container_props.ports:
|
|
for container_port in container_props.ports:
|
|
port = int(container_port)
|
|
cport = client.V1ContainerPort(container_port=port)
|
|
list_container_port.append(cport)
|
|
container = client.V1Container(
|
|
name=container_name,
|
|
image=container_props.image,
|
|
ports=list_container_port,
|
|
resources=limit_resource,
|
|
command=container_props.command,
|
|
args=container_props.args,
|
|
env=list_env_var,
|
|
image_pull_policy="IfNotPresent")
|
|
return container
|
|
|
|
# init_deployment initializes Kubernetes Pod object
|
|
def init_deployment(self, tosca_kube_obj, kube_obj_name):
|
|
"""Instantiate the deployment object"""
|
|
|
|
deployment_name = kube_obj_name
|
|
# Create a list of container, which made a Pod
|
|
containers = list()
|
|
for container_prop in tosca_kube_obj.containers:
|
|
limit_resource = self.init_resource_requirements(container_prop)
|
|
container = self.init_containers(
|
|
container_props=container_prop,
|
|
limit_resource=limit_resource,
|
|
name=deployment_name)
|
|
containers.append(container)
|
|
|
|
# Make a label with pattern {"selector": "deployment_name"}
|
|
if tosca_kube_obj.scaling_object:
|
|
scaling_name = tosca_kube_obj.scaling_object.scaling_name
|
|
update_label = self.config_labels(deployment_name=deployment_name,
|
|
scaling_name=scaling_name)
|
|
else:
|
|
update_label = self.config_labels(deployment_name=deployment_name)
|
|
if tosca_kube_obj.labels:
|
|
if 'selector' in update_label:
|
|
del update_label['selector']
|
|
update_label.update(tosca_kube_obj.labels)
|
|
labels = update_label
|
|
|
|
# Create and configure a spec section
|
|
pod_template = client.V1PodTemplateSpec(
|
|
metadata=client.V1ObjectMeta(
|
|
labels=labels, annotations=tosca_kube_obj.annotations),
|
|
spec=client.V1PodSpec(containers=containers))
|
|
# Create the specification of deployment
|
|
label_selector = client.V1LabelSelector(match_labels=labels)
|
|
deployment_spec = client.V1DeploymentSpec(
|
|
template=pod_template, selector=label_selector)
|
|
metadata = client.V1ObjectMeta(name=deployment_name, labels=labels)
|
|
|
|
# Instantiate the deployment object
|
|
deployment = client.V1Deployment(
|
|
api_version="apps/v1",
|
|
kind="Deployment",
|
|
metadata=metadata,
|
|
spec=deployment_spec)
|
|
return deployment
|
|
|
|
# init_hpa initializes Kubernetes Horizon Pod Auto-scaling object
|
|
def init_hpa(self, tosca_kube_obj, kube_obj_name):
|
|
scaling_props = tosca_kube_obj.scaling_object
|
|
hpa = None
|
|
if scaling_props:
|
|
min_replicas = scaling_props.min_replicas
|
|
max_replicas = scaling_props.max_replicas
|
|
cpu_util = scaling_props.target_cpu_utilization_percentage
|
|
deployment_name = kube_obj_name
|
|
|
|
# Create target Deployment object
|
|
target = client.V1CrossVersionObjectReference(
|
|
api_version="apps/v1",
|
|
kind="Deployment",
|
|
name=deployment_name)
|
|
# Create the specification of horizon pod auto-scaling
|
|
hpa_spec = client.V1HorizontalPodAutoscalerSpec(
|
|
min_replicas=min_replicas,
|
|
max_replicas=max_replicas,
|
|
target_cpu_utilization_percentage=cpu_util,
|
|
scale_target_ref=target)
|
|
metadata = client.V1ObjectMeta(name=deployment_name)
|
|
# Create Horizon Pod Auto-Scaling
|
|
hpa = client.V1HorizontalPodAutoscaler(
|
|
api_version="autoscaling/v1",
|
|
kind="HorizontalPodAutoscaler",
|
|
spec=hpa_spec,
|
|
metadata=metadata)
|
|
return hpa
|
|
|
|
# init_service initializes Kubernetes service object
|
|
def init_service(self, tosca_kube_obj, kube_obj_name):
|
|
list_service_port = list()
|
|
service_label = tosca_kube_obj.labels
|
|
for port in tosca_kube_obj.mapping_ports:
|
|
if COLON_CHARACTER in port:
|
|
ports = port.split(COLON_CHARACTER)
|
|
published_port = int(ports[0])
|
|
target_port = int(ports[1])
|
|
else:
|
|
target_port = published_port = int(port)
|
|
service_port = client.V1ServicePort(
|
|
name=str(published_port),
|
|
port=published_port,
|
|
target_port=target_port)
|
|
list_service_port.append(service_port)
|
|
|
|
deployment_name = kube_obj_name
|
|
selector_by_name = self.config_labels(deployment_name)
|
|
if tosca_kube_obj.labels:
|
|
selectors = tosca_kube_obj.labels.copy()
|
|
else:
|
|
selectors = selector_by_name
|
|
if tosca_kube_obj.mgmt_connection_point:
|
|
service_label['management_connection'] = 'True'
|
|
if tosca_kube_obj.network_name:
|
|
service_label['network_name'] = tosca_kube_obj.network_name
|
|
service_label['vdu_name'] = tosca_kube_obj.name
|
|
|
|
metadata = client.V1ObjectMeta(name=deployment_name,
|
|
labels=service_label)
|
|
if tosca_kube_obj.service_type:
|
|
service_type = tosca_kube_obj.service_type
|
|
else:
|
|
service_type = None
|
|
service_spec = client.V1ServiceSpec(
|
|
selector=selectors,
|
|
ports=list_service_port,
|
|
type=service_type)
|
|
|
|
service = client.V1Service(
|
|
api_version="v1",
|
|
kind="Service",
|
|
spec=service_spec,
|
|
metadata=metadata)
|
|
return service
|
|
|
|
# init_config_map initializes Kubernetes ConfigMap object
|
|
def init_configmap(self, container_props, kube_obj_name):
|
|
config_map = None
|
|
if container_props.config:
|
|
configmap_name = kube_obj_name
|
|
metadata = client.V1ObjectMeta(name=configmap_name)
|
|
config_dict = self.pre_process_config(container_props.config)
|
|
config_map = client.V1ConfigMap(
|
|
api_version="v1",
|
|
kind="ConfigMap",
|
|
data=config_dict,
|
|
metadata=metadata)
|
|
return config_map
|
|
|
|
def pre_process_name(self, name):
|
|
# replace '_' by '-' to meet Kubernetes' requirement
|
|
new_name = name.replace(DASH_CHARACTER, HYPHEN_CHARACTER).lower()
|
|
return new_name
|
|
|
|
def pre_process_config(self, config):
|
|
# Split by separating lines
|
|
config_dict = {}
|
|
if config:
|
|
configs = config.split(NEWLINE_CHARACTER)
|
|
for config_item in configs:
|
|
# Ignore if config_item is null
|
|
if config_item:
|
|
# Strip all types of white-space characters
|
|
config_item = config_item.replace(
|
|
WHITE_SPACE_CHARACTER,
|
|
NON_WHITE_SPACE_CHARACTER)
|
|
config_prop = config_item.split(COLON_CHARACTER)
|
|
config_dict[config_prop[0]] = config_prop[1]
|
|
# config_dict has the pattern such as
|
|
# {'param1': 'key1', 'param0': 'key0'}
|
|
return config_dict
|