389 lines
14 KiB
Python
389 lines
14 KiB
Python
# Copyright 2016 - Nokia
|
|
# 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 sys
|
|
import yaml
|
|
|
|
from oslo_log import log as logging
|
|
from six import iteritems
|
|
from toscaparser import properties
|
|
from toscaparser.utils import yamlparser
|
|
|
|
from tacker.common import log
|
|
from tacker.common import utils
|
|
from tacker.extensions import vnfm
|
|
|
|
|
|
FAILURE = 'tosca.policies.tacker.Failure'
|
|
LOG = logging.getLogger(__name__)
|
|
MONITORING = 'tosca.policies.tacker.Monitoring'
|
|
PLACEMENT = 'tosca.policies.tacker.Placement'
|
|
TACKERCP = 'tosca.nodes.nfv.CP.Tacker'
|
|
TACKERVDU = 'tosca.nodes.nfv.VDU.Tacker'
|
|
TOSCA_BINDS_TO = 'tosca.relationships.network.BindsTo'
|
|
VDU = 'tosca.nodes.nfv.VDU'
|
|
IMAGE = 'tosca.artifacts.Deployment.Image.VM'
|
|
OS_RESOURCES = {
|
|
'flavor': 'get_flavor_dict',
|
|
'image': 'get_image_dict'
|
|
}
|
|
|
|
FLAVOR_PROPS = {
|
|
"num_cpus": ("vcpus", 1, None),
|
|
"disk_size": ("disk", 1, "GB"),
|
|
"mem_size": ("ram", 512, "MB")
|
|
}
|
|
|
|
CPU_PROP_MAP = (('hw:cpu_policy', 'cpu_affinity'),
|
|
('hw:cpu_threads_policy', 'thread_allocation'),
|
|
('hw:cpu_sockets', 'socket_count'),
|
|
('hw:cpu_threads', 'thread_count'),
|
|
('hw:cpu_cores', 'core_count'))
|
|
|
|
CPU_PROP_KEY_SET = {'cpu_affinity', 'thread_allocation', 'socket_count',
|
|
'thread_count', 'core_count'}
|
|
|
|
FLAVOR_EXTRA_SPECS_LIST = ('cpu_allocation',
|
|
'mem_page_size',
|
|
'numa_node_count',
|
|
'numa_nodes')
|
|
|
|
delpropmap = {TACKERVDU: ('mgmt_driver', 'config', 'service_type',
|
|
'placement_policy', 'monitoring_policy',
|
|
'metadata', 'failure_policy'),
|
|
TACKERCP: ('management',)}
|
|
|
|
convert_prop = {TACKERCP: {'anti_spoofing_protection':
|
|
'port_security_enabled',
|
|
'type':
|
|
'binding:vnic_type'}}
|
|
|
|
convert_prop_values = {TACKERCP: {'sriov': 'direct',
|
|
'vnic': 'normal'}}
|
|
|
|
deletenodes = (MONITORING, FAILURE, PLACEMENT)
|
|
|
|
HEAT_RESOURCE_MAP = {
|
|
"flavor": "OS::Nova::Flavor",
|
|
"image": "OS::Glance::Image"
|
|
}
|
|
|
|
|
|
@log.log
|
|
def updateimports(template):
|
|
path = os.path.dirname(os.path.abspath(__file__)) + '/lib/'
|
|
defsfile = path + 'tacker_defs.yaml'
|
|
|
|
if 'imports' in template:
|
|
template['imports'].append(defsfile)
|
|
else:
|
|
template['imports'] = [defsfile]
|
|
|
|
if 'nfv' in template['tosca_definitions_version']:
|
|
nfvfile = path + 'tacker_nfv_defs.yaml'
|
|
|
|
template['imports'].append(nfvfile)
|
|
|
|
LOG.debug(_("%s"), path)
|
|
|
|
|
|
@log.log
|
|
def get_vdu_monitoring(template):
|
|
monitoring_dict = {}
|
|
for nt in template.nodetemplates:
|
|
if nt.type_definition.is_derived_from(TACKERVDU):
|
|
mon_policy = nt.get_property_value('monitoring_policy') or 'noop'
|
|
# mon_data = {mon_policy['name']: {'actions': {'failure':
|
|
# 'respawn'}}}
|
|
if mon_policy != 'noop':
|
|
if 'parameters' in mon_policy:
|
|
mon_policy['monitoring_params'] = mon_policy['parameters']
|
|
monitoring_dict['vdus'] = {}
|
|
monitoring_dict['vdus'][nt.name] = {}
|
|
monitoring_dict['vdus'][nt.name][mon_policy['name']] = \
|
|
mon_policy
|
|
return monitoring_dict
|
|
|
|
|
|
@log.log
|
|
def get_vdu_metadata(template):
|
|
metadata = dict()
|
|
metadata.setdefault('vdus', {})
|
|
for nt in template.nodetemplates:
|
|
if nt.type_definition.is_derived_from(TACKERVDU):
|
|
metadata_dict = nt.get_property_value('metadata') or None
|
|
if metadata_dict:
|
|
metadata['vdus'][nt.name] = {}
|
|
metadata['vdus'][nt.name].update(metadata_dict)
|
|
return metadata
|
|
|
|
|
|
@log.log
|
|
def get_mgmt_ports(tosca):
|
|
mgmt_ports = {}
|
|
for nt in tosca.nodetemplates:
|
|
if nt.type_definition.is_derived_from(TACKERCP):
|
|
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 is not None:
|
|
name = 'mgmt_ip-%s' % vdu
|
|
mgmt_ports[name] = nt.name
|
|
LOG.debug('mgmt_ports: %s', mgmt_ports)
|
|
return mgmt_ports
|
|
|
|
|
|
@log.log
|
|
def add_resources_tpl(heat_dict, hot_res_tpl):
|
|
for res, res_dict in iteritems(hot_res_tpl):
|
|
for vdu, vdu_dict in iteritems(res_dict):
|
|
res_name = vdu + "_" + res
|
|
heat_dict["resources"][res_name] = {
|
|
"type": HEAT_RESOURCE_MAP[res],
|
|
"properties": {}
|
|
}
|
|
|
|
for prop, val in iteritems(vdu_dict):
|
|
heat_dict["resources"][res_name]["properties"][prop] = val
|
|
heat_dict["resources"][vdu]["properties"][res] = {
|
|
"get_resource": res_name
|
|
}
|
|
|
|
|
|
@log.log
|
|
def convert_unsupported_res_prop(heat_dict, unsupported_res_prop):
|
|
res_dict = heat_dict['resources']
|
|
|
|
for res, attr in iteritems(res_dict):
|
|
res_type = attr['type']
|
|
if res_type in unsupported_res_prop:
|
|
prop_dict = attr['properties']
|
|
unsupported_prop_dict = unsupported_res_prop[res_type]
|
|
unsupported_prop = set(prop_dict.keys()) & set(
|
|
unsupported_prop_dict.keys())
|
|
for prop in unsupported_prop:
|
|
# some properties are just punted to 'value_specs'
|
|
# property if they are incompatible
|
|
new_prop = unsupported_prop_dict[prop]
|
|
if new_prop == 'value_specs':
|
|
prop_dict.setdefault(new_prop, {})[
|
|
prop] = prop_dict.pop(prop)
|
|
else:
|
|
prop_dict[new_prop] = prop_dict.pop(prop)
|
|
|
|
|
|
@log.log
|
|
def post_process_heat_template(heat_tpl, mgmt_ports, metadata,
|
|
res_tpl, unsupported_res_prop=None):
|
|
#
|
|
# TODO(bobh) - remove when heat-translator can support literal strings.
|
|
#
|
|
def fix_user_data(user_data_string):
|
|
user_data_string = re.sub('user_data: #', 'user_data: |\n #',
|
|
user_data_string, re.MULTILINE)
|
|
return re.sub('\n\n', '\n', user_data_string, re.MULTILINE)
|
|
|
|
heat_tpl = fix_user_data(heat_tpl)
|
|
#
|
|
# End temporary workaround for heat-translator
|
|
#
|
|
heat_dict = yamlparser.simple_ordered_parse(heat_tpl)
|
|
for outputname, portname in mgmt_ports.items():
|
|
ipval = {'get_attr': [portname, 'fixed_ips', 0, 'ip_address']}
|
|
output = {outputname: {'value': ipval}}
|
|
if 'outputs' in heat_dict:
|
|
heat_dict['outputs'].update(output)
|
|
else:
|
|
heat_dict['outputs'] = output
|
|
LOG.debug(_('Added output for %s'), outputname)
|
|
if metadata:
|
|
for vdu_name, metadata_dict in metadata['vdus'].items():
|
|
heat_dict['resources'][vdu_name]['properties']['metadata'] =\
|
|
metadata_dict
|
|
|
|
add_resources_tpl(heat_dict, res_tpl)
|
|
if unsupported_res_prop:
|
|
convert_unsupported_res_prop(heat_dict, unsupported_res_prop)
|
|
return yaml.dump(heat_dict)
|
|
|
|
|
|
@log.log
|
|
def post_process_template(template):
|
|
for nt in template.nodetemplates:
|
|
if (nt.type_definition.is_derived_from(MONITORING) or
|
|
nt.type_definition.is_derived_from(FAILURE) or
|
|
nt.type_definition.is_derived_from(PLACEMENT)):
|
|
template.nodetemplates.remove(nt)
|
|
continue
|
|
|
|
if nt.type in delpropmap.keys():
|
|
for prop in delpropmap[nt.type]:
|
|
for p in nt.get_properties_objects():
|
|
if prop == p.name:
|
|
nt.get_properties_objects().remove(p)
|
|
|
|
if nt.type in convert_prop:
|
|
for prop in convert_prop[nt.type].keys():
|
|
for p in nt.get_properties_objects():
|
|
if prop == p.name:
|
|
schema_dict = {'type': p.type}
|
|
v = nt.get_property_value(p.name)
|
|
newprop = properties.Property(
|
|
convert_prop[nt.type][prop], v, schema_dict)
|
|
nt.get_properties_objects().append(newprop)
|
|
nt.get_properties_objects().remove(p)
|
|
|
|
if nt.type in convert_prop_values:
|
|
for key in convert_prop_values[nt.type].keys():
|
|
for p in nt.get_properties_objects():
|
|
if key == p.value:
|
|
v = convert_prop_values[nt.type][p.value]
|
|
p.value = v
|
|
|
|
|
|
@log.log
|
|
def get_mgmt_driver(template):
|
|
mgmt_driver = None
|
|
for nt in template.nodetemplates:
|
|
if nt.type_definition.is_derived_from(TACKERVDU):
|
|
if (mgmt_driver and nt.get_property_value('mgmt_driver') !=
|
|
mgmt_driver):
|
|
raise vnfm.MultipleMGMTDriversSpecified()
|
|
else:
|
|
mgmt_driver = nt.get_property_value('mgmt_driver')
|
|
|
|
return mgmt_driver
|
|
|
|
|
|
def findvdus(template):
|
|
vdus = []
|
|
for nt in template.nodetemplates:
|
|
if nt.type_definition.is_derived_from(TACKERVDU):
|
|
vdus.append(nt)
|
|
return vdus
|
|
|
|
|
|
def get_flavor_dict(template, flavor_extra_input=None):
|
|
flavor_dict = {}
|
|
vdus = findvdus(template)
|
|
for nt in vdus:
|
|
flavor_tmp = nt.get_properties().get('flavor')
|
|
if flavor_tmp:
|
|
continue
|
|
if nt.get_capabilities().get("nfv_compute"):
|
|
flavor_dict[nt.name] = {}
|
|
properties = nt.get_capabilities()["nfv_compute"].get_properties()
|
|
for prop, (hot_prop, default, unit) in \
|
|
iteritems(FLAVOR_PROPS):
|
|
hot_prop_val = (properties[prop].value
|
|
if properties.get(prop, None) else None)
|
|
if unit and hot_prop_val:
|
|
hot_prop_val = \
|
|
utils.change_memory_unit(hot_prop_val, unit)
|
|
flavor_dict[nt.name][hot_prop] = \
|
|
hot_prop_val if hot_prop_val else default
|
|
if any(p in properties for p in FLAVOR_EXTRA_SPECS_LIST):
|
|
flavor_dict[nt.name]['extra_specs'] = {}
|
|
es_dict = flavor_dict[nt.name]['extra_specs']
|
|
populate_flavor_extra_specs(es_dict, properties,
|
|
flavor_extra_input)
|
|
return flavor_dict
|
|
|
|
|
|
def populate_flavor_extra_specs(es_dict, properties, flavor_extra_input):
|
|
if 'mem_page_size' in properties:
|
|
mval = properties['mem_page_size'].value
|
|
if str(mval).isdigit():
|
|
mval = mval * 1024
|
|
elif mval not in ('small', 'large', 'any'):
|
|
raise vnfm.HugePageSizeInvalidInput(
|
|
error_msg_details=(mval + ":Invalid Input"))
|
|
es_dict['hw:mem_page_size'] = mval
|
|
if 'numa_nodes' in properties and 'numa_node_count' in properties:
|
|
LOG.warning(_('Both numa_nodes and numa_node_count have been'
|
|
'specified; numa_node definitions will be ignored and'
|
|
'numa_node_count will be applied'))
|
|
if 'numa_node_count' in properties:
|
|
es_dict['hw:numa_nodes'] = \
|
|
properties['numa_node_count'].value
|
|
if 'numa_nodes' in properties and 'numa_node_count' not in properties:
|
|
nodes_dict = dict(properties['numa_nodes'].value)
|
|
dval = list(nodes_dict.values())
|
|
ncount = 0
|
|
for ndict in dval:
|
|
invalid_input = set(ndict.keys()) - {'id', 'vcpus', 'mem_size'}
|
|
if invalid_input:
|
|
raise vnfm.NumaNodesInvalidKeys(
|
|
error_msg_details=(', '.join(invalid_input)),
|
|
valid_keys="id, vcpus and mem_size")
|
|
if 'id' in ndict and 'vcpus' in ndict:
|
|
vk = "hw:numa_cpus." + str(ndict['id'])
|
|
vval = ",".join([str(x) for x in ndict['vcpus']])
|
|
es_dict[vk] = vval
|
|
if 'id' in ndict and 'mem_size' in ndict:
|
|
mk = "hw:numa_mem." + str(ndict['id'])
|
|
es_dict[mk] = ndict['mem_size']
|
|
ncount += 1
|
|
es_dict['hw:numa_nodes'] = ncount
|
|
if 'cpu_allocation' in properties:
|
|
cpu_dict = dict(properties['cpu_allocation'].value)
|
|
invalid_input = set(cpu_dict.keys()) - CPU_PROP_KEY_SET
|
|
if invalid_input:
|
|
raise vnfm.CpuAllocationInvalidKeys(
|
|
error_msg_details=(', '.join(invalid_input)),
|
|
valid_keys=(', '.join(CPU_PROP_KEY_SET)))
|
|
for(k, v) in CPU_PROP_MAP:
|
|
if v in cpu_dict:
|
|
es_dict[k] = cpu_dict[v]
|
|
if flavor_extra_input:
|
|
es_dict.update(flavor_extra_input)
|
|
|
|
|
|
def get_image_dict(template):
|
|
image_dict = {}
|
|
vdus = findvdus(template)
|
|
for vdu in vdus:
|
|
if not vdu.entity_tpl.get("artifacts"):
|
|
continue
|
|
artifacts = vdu.entity_tpl["artifacts"]
|
|
for name, artifact in iteritems(artifacts):
|
|
if ('type' in artifact.keys() and
|
|
artifact["type"] == IMAGE):
|
|
if 'file' not in artifact.keys():
|
|
raise vnfm.FilePathMissing()
|
|
image_dict[vdu.name] = {
|
|
"location": artifact["file"],
|
|
"container_format": "bare",
|
|
"disk_format": "raw",
|
|
"name": name
|
|
}
|
|
return image_dict
|
|
|
|
|
|
def get_resources_dict(template, flavor_extra_input=None):
|
|
res_dict = dict()
|
|
for res, method in iteritems(OS_RESOURCES):
|
|
res_method = getattr(sys.modules[__name__], method)
|
|
if res is 'flavor':
|
|
res_dict[res] = res_method(template, flavor_extra_input)
|
|
else:
|
|
res_dict[res] = res_method(template)
|
|
return res_dict
|