Tacker: ETSI MANO NFV Orchestrator / VNF Manager. See https://wiki.openstack.org/wiki/Tacker
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

281 lines
12 KiB

# 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_config import cfg
import json
from oslo_log import log as logging
from oslo_utils import uuidutils
from tacker.common import log
from tacker.extensions import vnfm
from tacker.tosca import utils as toscautils
from tacker.vnfm.infra_drivers.kubernetes.k8s import tosca_kube_object
from toscaparser.functions import GetInput
from toscaparser import tosca_template
import toscaparser.utils.yamlparser
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
SCALING = 'tosca.policies.Scaling'
TACKER_CP = 'tosca.nodes.nfv.CP.Tacker'
TACKER_VL = 'tosca.nodes.nfv.VL'
COLON_CHARACTER = ':'
WHITE_SPACE_CHARACTER = ' '
NON_WHITE_SPACE_CHARACTER = ''
TOSCA_LINKS_TO = 'tosca.relationships.network.LinksTo'
TOSCA_BINDS_TO = 'tosca.relationships.network.BindsTo'
ALLOWED_KUBERNETES_OBJECT_PROPS = ('namespace', 'mapping_ports', 'labels',
'vnfcs', 'service_type',
'mgmt_driver', 'config_drive')
ALLOWED_CONTAINER_OBJECT_PROPS = ('num_cpus', 'mem_size', 'image', 'config',
'command', 'args', 'ports')
ALLOWED_SCALING_OBJECT_PROPS = ('min_instances', 'max_instances',
'target_cpu_utilization_percentage')
SCALAR_UNIT_DICT = {'B': 1, 'kB': 1000, 'KiB': 1024, 'MB': 1000000,
'MiB': 1048576, 'GB': 1000000000,
'GiB': 1073741824, 'TB': 1000000000000,
'TiB': 1099511627776}
class Parser(object):
"""Convert TOSCA template to Tosca Kube object"""
def __init__(self, vnfd_dict):
self.vnfd_dict = vnfd_dict
def loader(self):
"""Load TOSCA template and start parsing"""
try:
parserd_params = None
toscautils.updateimports(self.vnfd_dict)
tosca = tosca_template.\
ToscaTemplate(parsed_params=parserd_params,
a_file=False,
yaml_dict_tpl=self.vnfd_dict)
except Exception as e:
LOG.debug("tosca-parser error: %s", str(e))
raise vnfm.ToscaParserFailed(error_msg_details=str(e))
# Initiate a list tosca_kube_object which are defined from VDU
tosca_kube_objects = []
vdus = toscautils.findvdus(tosca)
for node_template in vdus:
vdu_name = node_template.name
tosca_kube_obj = self.tosca_to_kube_mapping(node_template)
# Find network name in which VDU is attached
network_names = self.find_networks(tosca, vdu_name)
if network_names:
annotations_pad = \
json.dumps([{"name": "%s" % net}
for net in network_names])
tosca_kube_obj.annotations =\
{'k8s.v1.cni.cncf.io/networks': annotations_pad}
# If connection_point is True, Tacker will manage its service ip
tosca_kube_obj.mgmt_connection_point = \
self.check_mgmt_cp(tosca, vdu_name)
# Find scaling policy that is used for this VDU, different to
# VM-based VNF, there are no alarm policies.
tosca_kube_obj.scaling_object = \
self.get_scaling_policy(tosca, vdu_name)
tosca_kube_objects.append(tosca_kube_obj)
return tosca_kube_objects
@log.log
def tosca_to_kube_mapping(self, node_template):
"""Map TOSCA template to ToscaKubeObject properties"""
tosca_props = self.get_properties(node_template)
self.check_unsupported_key(tosca_props,
ALLOWED_KUBERNETES_OBJECT_PROPS)
tosca_kube_obj = tosca_kube_object.ToscaKubeObject()
# tosca_kube_obj name is used for tracking Kubernetes resources
service_name = 'svc-' + node_template.name + '-' + \
uuidutils.generate_uuid()
tosca_kube_obj.name = service_name[:15]
tosca_kube_obj.namespace = tosca_props.get('namespace')
tosca_kube_obj.mapping_ports = tosca_props.get('mapping_ports')
tosca_kube_obj.labels = tosca_props.get('labels')
# Find config properties of VNFComponents in each VDU node
vnfc_config_props = tosca_props.get('vnfcs')
container_objects = self.vnfc_configurable_to_container_mapping(
vnfc_config_props)
tosca_kube_obj.containers = container_objects
# In labels, we define which type of Service VNF will be deployed
service_labels = dict()
if tosca_kube_obj.labels:
for label in tosca_kube_obj.labels:
label = label.replace(
WHITE_SPACE_CHARACTER,
NON_WHITE_SPACE_CHARACTER)
labels = label.split(COLON_CHARACTER)
service_labels.update({labels[0]: labels[1]})
tosca_kube_obj.labels = service_labels
tosca_kube_obj.service_type = tosca_props.get('service_type')
return tosca_kube_obj
@log.log
def vnfc_configurable_to_container_mapping(self, vnfc_config_properties):
"""Map VnfcConfigurableProperties to list of containers"""
containers = list()
for container_name, container_props in vnfc_config_properties.items():
container = tosca_kube_object.Container()
container.name = container_name
self.check_unsupported_key(container_props,
ALLOWED_CONTAINER_OBJECT_PROPS)
container.num_cpus = container_props.get('num_cpus')
memory_size = container_props.get('mem_size')
container.mem_size = self.process_memory(memory_size)
container.image = container_props.get('image')
container.config = container_props.get('config')
container.command = container_props.get('command')
container.args = container_props.get('args')
container.ports = container_props.get('ports')
containers.append(container)
return containers
@log.log
def process_memory(self, mem_value):
"""Translate memory size with unit to a number of byte memory"""
# Memory size has the pattern e.g. 512 MB, 1024 MB or 1 GB
parser_memory = mem_value.split(WHITE_SPACE_CHARACTER)
memory_value = parser_memory[0]
memory_unit = parser_memory[1]
memory_real_value = 0
# Translate memory's byte size based on SCALAR_UNIT_DICT
if memory_unit in SCALAR_UNIT_DICT:
memory_real_value = \
int(memory_value) * SCALAR_UNIT_DICT[memory_unit]
return memory_real_value
@log.log
def get_scaling_policy(self, tosca, vdu_name):
"""Find scaling policy which is used for VDU"""
if len(tosca.policies) == 0:
scaling_obj = None
else:
count = 0
scaling_obj = tosca_kube_object.ScalingObject()
for policy in tosca.policies:
if policy.type_definition.is_derived_from(SCALING) \
and vdu_name in policy.targets:
count = count + 1
policy_props = policy.properties
# NOTE(ueha): check_unsupported_key() is commented out to
# resolve vnf create error due to required
# parameters of policies.
# self.check_unsupported_key(policy_props,
# ALLOWED_SCALING_OBJECT_PROPS)
scaling_obj.scaling_name = policy.name
scaling_obj.target_cpu_utilization_percentage = \
policy_props.get(
'target_cpu_utilization_percentage')
scaling_obj.min_replicas = \
policy_props.get('min_instances')
scaling_obj.max_replicas = \
policy_props.get('max_instances')
if count > 1:
# Because in Kubernetes environment, we can attach only one
# scaling policy to Deployment. If user provides more than one
# policy this error will happen when count > 1
LOG.debug("Tacker only support one scaling policy per VDU")
raise vnfm.InvalidKubernetesScalingPolicyNumber
return scaling_obj
@log.log
def find_networks(self, tosca, vdu_name):
"""Find networks which VDU is attached based on vdu_name."""
networks = []
network_names = []
for node_template in tosca.nodetemplates:
if node_template.type_definition.is_derived_from(TACKER_CP):
match = False
links_to = None
binds_to = None
for rel, node in node_template.relationships.items():
if not links_to and rel.is_derived_from(TOSCA_LINKS_TO):
links_to = node
elif not binds_to and rel.is_derived_from(TOSCA_BINDS_TO):
binds_to = node
if binds_to.name == vdu_name:
match = True
if match:
networks.append(links_to.name)
for node_template in tosca.nodetemplates:
if node_template.type_definition.is_derived_from(TACKER_VL):
tosca_props = self.get_properties(node_template)
if node_template.name in networks:
for key, value in tosca_props.items():
if key == 'network_name':
network_names.append(value)
if network_names:
return network_names
@log.log
def check_mgmt_cp(self, tosca, vdu_name):
"""Check if management for connection point is enabled"""
mgmt_connection_point = False
for nt in tosca.nodetemplates:
if nt.type_definition.is_derived_from(TACKER_CP):
mgmt = nt.get_property_value('management') or None
if mgmt:
vdu = None
for rel, node in nt.relationships.items():
if rel.is_derived_from(TOSCA_BINDS_TO):
vdu = node.name
break
if vdu == vdu_name:
mgmt_connection_point = True
LOG.debug('mgmt_connection_point: %s', mgmt_connection_point)
return mgmt_connection_point
@log.log
def get_properties(self, node_template):
"""Return a list of property node template objects."""
tosca_props = {}
for prop in node_template.get_properties_objects():
if isinstance(prop.value, GetInput):
tosca_props[prop.name] = {'get_param': prop.value.input_name}
else:
tosca_props[prop.name] = prop.value
return tosca_props
def check_unsupported_key(self, input_values, support_key):
"""collect all unsupported keys"""
found_keys = []
for key in input_values:
if key not in support_key:
found_keys.append(key)
if len(found_keys) > 0:
raise vnfm.InvalidKubernetesInputParameter(found_keys=found_keys)