diff --git a/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py b/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py index 3597a159a..4b3bea97e 100644 --- a/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py +++ b/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py @@ -66,7 +66,7 @@ class TestDeviceHeat(base.TestCase): fake_heat_client = mock.Mock() fake_heat_client.return_value = self.heat_client self._mock( - 'tacker.vnfm.infra_drivers.openstack.openstack.HeatClient', + 'tacker.vnfm.infra_drivers.openstack.heat_client.HeatClient', fake_heat_client) def _mock(self, target, new=mock.DEFAULT): diff --git a/tacker/tests/unit/vm/infra_drivers/openstack/test_openstack.py b/tacker/tests/unit/vm/infra_drivers/openstack/test_openstack.py index ea58e037a..90b6276b5 100644 --- a/tacker/tests/unit/vm/infra_drivers/openstack/test_openstack.py +++ b/tacker/tests/unit/vm/infra_drivers/openstack/test_openstack.py @@ -66,7 +66,7 @@ class TestOpenStack(base.TestCase): fake_heat_client = mock.Mock() fake_heat_client.return_value = self.heat_client self._mock( - 'tacker.vnfm.infra_drivers.openstack.openstack.HeatClient', + 'tacker.vnfm.infra_drivers.openstack.heat_client.HeatClient', fake_heat_client) def _mock(self, target, new=mock.DEFAULT): diff --git a/tacker/vnfm/infra_drivers/openstack/heat_client.py b/tacker/vnfm/infra_drivers/openstack/heat_client.py new file mode 100644 index 000000000..603c6b128 --- /dev/null +++ b/tacker/vnfm/infra_drivers/openstack/heat_client.py @@ -0,0 +1,74 @@ +# 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 sys + +from heatclient import exc as heatException +from oslo_log import log as logging + +from tacker.common import clients +from tacker.extensions import vnfm + +LOG = logging.getLogger(__name__) + + +class HeatClient(object): + def __init__(self, auth_attr, region_name=None): + # context, password are unused + self.heat = clients.OpenstackClients(auth_attr, region_name).heat + self.stacks = self.heat.stacks + self.resource_types = self.heat.resource_types + self.resources = self.heat.resources + + def create(self, fields): + fields = fields.copy() + fields.update({ + 'timeout_mins': 10, + 'disable_rollback': True}) + if 'password' in fields.get('template', {}): + fields['password'] = fields['template']['password'] + + try: + return self.stacks.create(**fields) + except heatException.HTTPException: + type_, value, tb = sys.exc_info() + raise vnfm.HeatClientException(msg=value) + + def delete(self, stack_id): + try: + self.stacks.delete(stack_id) + except heatException.HTTPNotFound: + LOG.warning(_("Stack %(stack)s created by service chain driver is " + "not found at cleanup"), {'stack': stack_id}) + + def get(self, stack_id): + return self.stacks.get(stack_id) + + def resource_attr_support(self, resource_name, property_name): + resource = self.resource_types.get(resource_name) + return property_name in resource['attributes'] + + def resource_get_list(self, stack_id, nested_depth=0): + return self.heat.resources.list(stack_id, + nested_depth=nested_depth) + + def resource_signal(self, stack_id, rsc_name): + return self.heat.resources.signal(stack_id, rsc_name) + + def resource_get(self, stack_id, rsc_name): + return self.heat.resources.get(stack_id, rsc_name) + + def resource_event_list(self, stack_id, rsc_name, **kwargs): + return self.heat.events.list(stack_id, rsc_name, **kwargs) + + def resource_metadata(self, stack_id, rsc_name): + return self.heat.resources.metadata(stack_id, rsc_name) diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index 767a10a26..d13e6e60a 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -13,26 +13,21 @@ # 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 copy -import sys + import time from heatclient import exc as heatException from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils -from six import iteritems -from toscaparser import tosca_template -from toscaparser.utils import yamlparser -from translator.hot import tosca_translator import yaml -from tacker.common import clients from tacker.common import log from tacker.extensions import vnfm from tacker.vnfm.infra_drivers import abstract_driver +from tacker.vnfm.infra_drivers.openstack import heat_client as hc +from tacker.vnfm.infra_drivers.openstack import translate_template from tacker.vnfm.infra_drivers import scale_driver -from tacker.vnfm.tosca import utils as toscautils LOG = logging.getLogger(__name__) @@ -47,9 +42,6 @@ OPTS = [ default=5, help=_("Wait time (in seconds) between consecutive stack" " create/delete retries")), - cfg.DictOpt('flavor_extra_specs', - default={}, - help=_("Flavor Extra Specs")), ] CONF.register_opts(OPTS, group='openstack_vim') @@ -74,6 +66,8 @@ heat_template_version: 2013-05-23 """ OUTPUT_PREFIX = 'mgmt_ip-' +ALARMING_POLICY = 'tosca.policies.tacker.Alarming' +SCALING_POLICY = 'tosca.policies.tacker.Scaling' def get_scaling_policy_name(action, policy_name): @@ -88,7 +82,6 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, super(OpenStack, self).__init__() self.STACK_RETRIES = cfg.CONF.openstack_vim.stack_retries self.STACK_RETRY_WAIT = cfg.CONF.openstack_vim.stack_retry_wait - self.STACK_FLAVOR_EXTRA = cfg.CONF.openstack_vim.flavor_extra_specs def get_type(self): return 'openstack' @@ -99,542 +92,49 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, def get_description(self): return 'Openstack infra driver' - @log.log - def _update_params(self, original, paramvalues, match=False): - for key, value in iteritems(original): - if not isinstance(value, dict) or 'get_input' not in str(value): - pass - elif isinstance(value, dict): - if not match: - if key in paramvalues and 'param' in paramvalues[key]: - self._update_params(value, paramvalues[key]['param'], - True) - elif key in paramvalues: - self._update_params(value, paramvalues[key], False) - else: - LOG.debug('Key missing Value: %s', key) - raise vnfm.InputValuesMissing(key=key) - elif 'get_input' in value: - if value['get_input'] in paramvalues: - original[key] = paramvalues[value['get_input']] - else: - LOG.debug('Key missing Value: %s', key) - raise vnfm.InputValuesMissing(key=key) - else: - self._update_params(value, paramvalues, True) - - @log.log - def _process_parameterized_input(self, dev_attrs, vnfd_dict): - param_vattrs_yaml = dev_attrs.pop('param_values', None) - if param_vattrs_yaml: - try: - param_vattrs_dict = yaml.load(param_vattrs_yaml) - LOG.debug('param_vattrs_yaml', param_vattrs_dict) - except Exception as e: - LOG.debug("Not Well Formed: %s", str(e)) - raise vnfm.ParamYAMLNotWellFormed( - error_msg_details=str(e)) - else: - self._update_params(vnfd_dict, param_vattrs_dict) - else: - raise vnfm.ParamYAMLInputMissing() - - @log.log - def _process_vdu_network_interfaces(self, vdu_id, vdu_dict, properties, - template_dict, - unsupported_res_prop=None): - - def make_port_dict(): - port_dict = {'type': 'OS::Neutron::Port'} - port_dict['properties'] = {'value_specs': { - 'port_security_enabled': False}} if unsupported_res_prop \ - else {'port_security_enabled': False} - port_dict['properties'].setdefault('fixed_ips', []) - return port_dict - - def make_mgmt_outputs_dict(port): - mgmt_ip = 'mgmt_ip-%s' % vdu_id - outputs_dict[mgmt_ip] = { - 'description': 'management ip address', - 'value': { - 'get_attr': [port, 'fixed_ips', - 0, 'ip_address'] - } - } - - def handle_port_creation(network_param, ip_list=None, - mgmt_port=False): - ip_list = ip_list or [] - port = '%s-%s-port' % (vdu_id, network_param['network']) - port_dict = make_port_dict() - if mgmt_port: - make_mgmt_outputs_dict(port) - for ip in ip_list: - port_dict['properties']['fixed_ips'].append({"ip_address": ip}) - port_dict['properties'].update(network_param) - template_dict['resources'][port] = port_dict - return port - - networks_list = [] - outputs_dict = template_dict['outputs'] - properties['networks'] = networks_list - for network_param in vdu_dict[ - 'network_interfaces'].values(): - port = None - if 'addresses' in network_param: - ip_list = network_param.pop('addresses', []) - if not isinstance(ip_list, list): - raise vnfm.IPAddrInvalidInput() - mgmt_flag = network_param.pop('management', False) - port = handle_port_creation(network_param, ip_list, mgmt_flag) - if network_param.pop('management', False): - port = handle_port_creation(network_param, [], True) - if port is not None: - network_param = { - 'port': {'get_resource': port} - } - networks_list.append(dict(network_param)) - - def fetch_unsupported_resource_prop(self, heat_client): - unsupported_resource_prop = {} - - for res, prop_dict in iteritems(HEAT_VERSION_INCOMPATIBILITY_MAP): - unsupported_prop = {} - for prop, val in iteritems(prop_dict): - if not heat_client.resource_attr_support(res, prop): - unsupported_prop.update(prop_dict) - if unsupported_prop: - unsupported_resource_prop[res] = unsupported_prop - return unsupported_resource_prop - @log.log def create(self, plugin, context, vnf, auth_attr): LOG.debug(_('vnf %s'), vnf) - attributes = vnf['vnfd']['attributes'].copy() - - vnfd_yaml = attributes.pop('vnfd', None) - if vnfd_yaml is None: - # TODO(kangaraj-manickam) raise user level exception - LOG.info(_("VNFD is not provided, so no vnf is created !!")) - return - - LOG.debug('vnfd_yaml %s', vnfd_yaml) - - def update_fields(): - fields = dict((key, attributes.pop(key)) for key - in ('stack_name', 'template_url', 'template') - if key in attributes) - for key in ('files', 'parameters'): - if key in attributes: - fields[key] = jsonutils.loads(attributes.pop(key)) - - # overwrite parameters with given dev_attrs for vnf creation - dev_attrs = vnf['attributes'].copy() - fields.update(dict((key, dev_attrs.pop(key)) for key - in ('stack_name', 'template_url', 'template') - if key in dev_attrs)) - for key in ('files', 'parameters'): - if key in dev_attrs: - fields.setdefault(key, {}).update( - jsonutils.loads(dev_attrs.pop(key))) - - return fields, dev_attrs - - fields, dev_attrs = update_fields() - region_name = vnf.get('placement_attr', {}).get('region_name', None) - heatclient_ = HeatClient(auth_attr, region_name) - unsupported_res_prop = self.fetch_unsupported_resource_prop( - heatclient_) + heatclient = hc.HeatClient(auth_attr, region_name) - def generate_hot_from_tosca(vnfd_dict): - parsed_params = {} - if ('param_values' in dev_attrs and - dev_attrs['param_values'] != ""): - try: - parsed_params = yaml.load(dev_attrs['param_values']) - except Exception as e: - LOG.debug("Params not Well Formed: %s", str(e)) - raise vnfm.ParamYAMLNotWellFormed( - error_msg_details=str(e)) - - toscautils.updateimports(vnfd_dict) - - try: - tosca = tosca_template.ToscaTemplate( - parsed_params=parsed_params, a_file=False, - yaml_dict_tpl=vnfd_dict) - - except Exception as e: - LOG.debug("tosca-parser error: %s", str(e)) - raise vnfm.ToscaParserFailed(error_msg_details=str(e)) - - monitoring_dict = toscautils.get_vdu_monitoring(tosca) - mgmt_ports = toscautils.get_mgmt_ports(tosca) - res_tpl = toscautils.get_resources_dict(tosca, - self.STACK_FLAVOR_EXTRA) - toscautils.post_process_template(tosca) - try: - translator = tosca_translator.TOSCATranslator(tosca, - parsed_params) - heat_template_yaml = translator.translate() - except Exception as e: - LOG.debug("heat-translator error: %s", str(e)) - raise vnfm.HeatTranslatorFailed(error_msg_details=str(e)) - heat_template_yaml = toscautils.post_process_heat_template( - heat_template_yaml, mgmt_ports, res_tpl, - unsupported_res_prop) - - return heat_template_yaml, monitoring_dict - - def generate_hot_scaling(vnfd_dict, - scale_resource_type="OS::Nova::Server"): - # Initialize the template - template_dict = yaml.load(HEAT_TEMPLATE_BASE) - template_dict['description'] = 'Tacker scaling template' - - parameters = {} - template_dict['parameters'] = parameters - - # Add scaling related resource defs - resources = {} - scaling_group_names = {} - - # TODO(kanagaraj-manickam) now only one group is supported, so name - # is hard-coded with G1 - def _get_scale_group_name(targets): - return 'G1' - - def _convert_to_heat_scaling_group(policy_prp, - scale_resource_type, - name): - group_hot = {'type': 'OS::Heat::AutoScalingGroup'} - properties = {} - properties['min_size'] = policy_prp['min_instances'] - properties['max_size'] = policy_prp['max_instances'] - properties['desired_capacity'] = policy_prp[ - 'default_instances'] - properties['cooldown'] = policy_prp['cooldown'] - properties['resource'] = {} - # TODO(kanagaraj-manickam) all VDU memebers are considered as 1 - # group now and make it to form the groups based on the VDU - # list mentioned in the policy's targets - # scale_resource_type is custome type mapped the HOT template - # generated for all VDUs in the tosca template - properties['resource']['type'] = scale_resource_type - # TODO(kanagraj-manickam) add custom type params here, to - # support parameterized template - group_hot['properties'] = properties - - return group_hot - - # tosca policies - # - # properties: - # adjust_by: 1 - # cooldown: 120 - # targets: [G1] - def _convert_to_heat_scaling_policy(policy_prp, name): - # Form the group - scale_grp = _get_scale_group_name(policy_prp['targets']) - scaling_group_names[name] = scale_grp - resources[scale_grp] = _convert_to_heat_scaling_group( - policy_prp, - scale_resource_type, - scale_grp) - - grp_id = {'get_resource': scale_grp} - - policy_hot = {'type': 'OS::Heat::ScalingPolicy'} - properties = {} - properties['adjustment_type'] = 'change_in_capacity' - properties['cooldown'] = policy_prp['cooldown'] - properties['scaling_adjustment'] = policy_prp['increment'] - properties['auto_scaling_group_id'] = grp_id - policy_hot['properties'] = properties - - # Add scale_out policy - policy_rsc_name = get_scaling_policy_name( - action='out', - policy_name=name - ) - resources[policy_rsc_name] = policy_hot - - # Add scale_in policy - in_value = '-%d' % int(policy_prp['increment']) - policy_hot_in = copy.deepcopy(policy_hot) - policy_hot_in['properties'][ - 'scaling_adjustment'] = in_value - policy_rsc_name = get_scaling_policy_name( - action='in', - policy_name=name - ) - resources[policy_rsc_name] = policy_hot_in - - # policies: - # - SP1: - # type: tosca.policies.tacker.Scaling - if 'policies' in vnfd_dict: - for policy_dict in vnfd_dict['policies']: - name, policy = list(policy_dict.items())[0] - if policy['type'] == 'tosca.policies.tacker.Scaling': - _convert_to_heat_scaling_policy(policy['properties'], - name) - # TODO(kanagaraj-manickam) only one policy is supported - # for all vdus. remove this break, once this limitation - # is addressed. - break - - template_dict['resources'] = resources - - # First return value helps to check if scaling resources exist - return ((len(template_dict['resources']) > 0), - scaling_group_names, - template_dict) - - def generate_hot_alarm_resource(topology_tpl_dict, heat_tpl): - alarm_resource = dict() - heat_dict = yamlparser.simple_ordered_parse(heat_tpl) - sub_heat_dict = copy.deepcopy(heat_dict) - is_enabled_alarm = False - - def _convert_to_heat_monitoring_prop(mon_policy): - name, mon_policy_dict = list(mon_policy.items())[0] - tpl_trigger_name = \ - mon_policy_dict['triggers']['resize_compute'] - tpl_condition = tpl_trigger_name['condition'] - properties = {} - properties['meter_name'] = tpl_trigger_name['metrics'] - properties['comparison_operator'] = \ - tpl_condition['comparison_operator'] - properties['period'] = tpl_condition['period'] - properties['evaluation_periods'] = tpl_condition['evaluations'] - properties['statistic'] = tpl_condition['method'] - properties['description'] = tpl_condition['constraint'] - properties['threshold'] = tpl_condition['threshold'] - # alarm url process here - alarm_url = str(vnf['attributes'].get('alarm_url')) - if alarm_url: - LOG.debug('Alarm url in heat %s', alarm_url) - properties['alarm_actions'] = [alarm_url] - return properties - - def _convert_to_heat_monitoring_resource(mon_policy): - mon_policy_hot = {'type': 'OS::Aodh::Alarm'} - mon_policy_hot['properties'] = \ - _convert_to_heat_monitoring_prop(mon_policy) - - if 'policies' in topology_tpl_dict: - for policies in topology_tpl_dict['policies']: - policy_name, policy_dt = list(policies.items())[0] - if policy_dt['type'] == \ - 'tosca.policies.tacker.Scaling': - # Fixed metadata. it will be fixed - # once targets are supported - metadata_dict = dict() - metadata_dict['metering.vnf_id'] = vnf['id'] - sub_heat_dict['resources']['VDU1']['properties']['metadata'] =\ - metadata_dict - matching_metadata_dict = dict() - matching_metadata_dict['metadata.user_metadata.vnf_id'] =\ - vnf['id'] - mon_policy_hot['properties']['matching_metadata'] =\ - matching_metadata_dict - break - return mon_policy_hot - - if 'policies' in topology_tpl_dict: - for policy_dict in topology_tpl_dict['policies']: - name, policy_tpl_dict = list(policy_dict.items())[0] - if policy_tpl_dict['type'] == \ - 'tosca.policies.tacker.Alarming': - is_enabled_alarm = True - alarm_resource[name] =\ - _convert_to_heat_monitoring_resource(policy_dict) - heat_dict['resources'].update(alarm_resource) - break - - heat_tpl_yaml = yaml.dump(heat_dict) - sub_heat_tpl_yaml = yaml.dump(sub_heat_dict) - return (is_enabled_alarm, - alarm_resource, - heat_tpl_yaml, - sub_heat_tpl_yaml) - - def generate_hot_from_legacy(vnfd_dict): - assert 'template' not in fields - assert 'template_url' not in fields - - monitoring_dict = {} - - template_dict = yaml.load(HEAT_TEMPLATE_BASE) - outputs_dict = {} - template_dict['outputs'] = outputs_dict - - if 'get_input' in vnfd_yaml: - self._process_parameterized_input(dev_attrs, vnfd_dict) - - KEY_LIST = (('description', 'description'), ) - for (key, vnfd_key) in KEY_LIST: - if vnfd_key in vnfd_dict: - template_dict[key] = vnfd_dict[vnfd_key] - - for vdu_id, vdu_dict in vnfd_dict.get('vdus', {}).items(): - template_dict.setdefault('resources', {})[vdu_id] = { - "type": "OS::Nova::Server" - } - resource_dict = template_dict['resources'][vdu_id] - KEY_LIST = (('image', 'vm_image'), - ('flavor', 'instance_type')) - resource_dict['properties'] = {} - properties = resource_dict['properties'] - for (key, vdu_key) in KEY_LIST: - properties[key] = vdu_dict[vdu_key] - if 'network_interfaces' in vdu_dict: - self._process_vdu_network_interfaces(vdu_id, - vdu_dict, properties, template_dict, - unsupported_res_prop) - if ('user_data' in vdu_dict and - 'user_data_format' in vdu_dict): - properties['user_data_format'] = vdu_dict[ - 'user_data_format'] - properties['user_data'] = vdu_dict['user_data'] - elif ('user_data' in vdu_dict or - 'user_data_format' in vdu_dict): - raise vnfm.UserDataFormatNotFound() - if 'placement_policy' in vdu_dict: - if 'availability_zone' in vdu_dict['placement_policy']: - properties['availability_zone'] = vdu_dict[ - 'placement_policy']['availability_zone'] - if 'config' in vdu_dict: - properties['config_drive'] = True - metadata = properties.setdefault('metadata', {}) - metadata.update(vdu_dict['config']) - for key, value in metadata.items(): - metadata[key] = value[:255] - if 'key_name' in vdu_dict: - properties['key_name'] = vdu_dict['key_name'] - - monitoring_policy = vdu_dict.get('monitoring_policy', - 'noop') - failure_policy = vdu_dict.get('failure_policy', 'noop') - - # Convert the old monitoring specification to the new - # network. This should be removed after Mitaka - if (monitoring_policy == 'ping' and - failure_policy == 'respawn'): - vdu_dict['monitoring_policy'] = { - 'ping': {'actions': {'failure': 'respawn'}}} - vdu_dict.pop('failure_policy') - - if monitoring_policy != 'noop': - monitoring_dict['vdus'] = {} - monitoring_dict['vdus'][vdu_id] = \ - vdu_dict['monitoring_policy'] - - # to pass necessary parameters to plugin upwards. - for key in ('service_type',): - if key in vdu_dict: - vnf.setdefault( - 'attributes', {})[vdu_id] = jsonutils.dumps( - {key: vdu_dict[key]}) - - heat_template_yaml = yaml.dump(template_dict) - - return heat_template_yaml, monitoring_dict - - def generate_hot(): - vnfd_dict = yamlparser.simple_ordered_parse(vnfd_yaml) - LOG.debug('vnfd_dict %s', vnfd_dict) - - is_tosca_format = False - if 'tosca_definitions_version' in vnfd_dict: - (heat_template_yaml, - monitoring_dict) = generate_hot_from_tosca(vnfd_dict) - is_tosca_format = True - else: - (heat_template_yaml, - monitoring_dict) = generate_hot_from_legacy(vnfd_dict) - - fields['template'] = heat_template_yaml - if is_tosca_format: - (is_scaling_needed, scaling_group_names, - main_dict) = generate_hot_scaling( - vnfd_dict['topology_template'], - 'scaling.yaml') - (is_enabled_alarm, alarm_resource, - heat_tpl_yaml, sub_heat_tpl_yaml) =\ - generate_hot_alarm_resource(vnfd_dict['topology_template'], - heat_template_yaml) - if is_enabled_alarm and not is_scaling_needed: - heat_template_yaml = heat_tpl_yaml - fields['template'] = heat_template_yaml - - if is_scaling_needed: - if is_enabled_alarm: - main_dict['resources'].update(alarm_resource) - main_yaml = yaml.dump(main_dict) - fields['template'] = main_yaml - fields['files'] = {'scaling.yaml': sub_heat_tpl_yaml}\ - if is_enabled_alarm else { - 'scaling.yaml': heat_template_yaml} - vnf['attributes']['heat_template'] = main_yaml - # TODO(kanagaraj-manickam) when multiple groups are - # supported, make this scaling atribute as - # scaling name vs scaling template map and remove - # scaling_group_names - vnf['attributes']['scaling.yaml'] = heat_template_yaml - vnf['attributes'][ - 'scaling_group_names'] = jsonutils.dumps( - scaling_group_names - ) - - elif not is_scaling_needed: - if not vnf['attributes'].get('heat_template'): - vnf['attributes'][ - 'heat_template'] = fields['template'] - - if monitoring_dict: - vnf['attributes']['monitoring_policy'] = \ - jsonutils.dumps(monitoring_dict) - - generate_hot() - - def create_stack(): - if 'stack_name' not in fields: - name = (__name__ + '_' + self.__class__.__name__ + '-' + - vnf['id']) - if vnf['attributes'].get('failure_count'): - name += ('-RESPAWN-%s') % str(vnf['attributes'][ - 'failure_count']) - fields['stack_name'] = name - - # service context is ignored - LOG.debug(_('service_context: %s'), - vnf.get('service_context', [])) - LOG.debug(_('fields: %s'), fields) - LOG.debug(_('template: %s'), fields['template']) - stack = heatclient_.create(fields) - - return stack - - stack = create_stack() + tth = translate_template.TOSCAToHOT(vnf, heatclient) + tth.generate_hot() + stack = self._create_stack(heatclient, tth.vnf, tth.fields) return stack['stack']['id'] + @log.log + def _create_stack(self, heatclient, vnf, fields): + if 'stack_name' not in fields: + name = __name__ + '_' + self.__class__.__name__ + '-' + vnf['id'] + if vnf['attributes'].get('failure_count'): + name += ('-RESPAWN-%s') % str(vnf['attributes'][ + 'failure_count']) + fields['stack_name'] = name + + # service context is ignored + LOG.debug(_('service_context: %s'), vnf.get('service_context', [])) + LOG.debug(_('fields: %s'), fields) + LOG.debug(_('template: %s'), fields['template']) + stack = heatclient.create(fields) + + return stack + + @log.log def create_wait(self, plugin, context, vnf_dict, vnf_id, auth_attr): region_name = vnf_dict.get('placement_attr', {}).get( 'region_name', None) - heatclient_ = HeatClient(auth_attr, region_name) + heatclient = hc.HeatClient(auth_attr, region_name) - stack = heatclient_.get(vnf_id) + stack = heatclient.get(vnf_id) status = stack.stack_status stack_retries = self.STACK_RETRIES error_reason = None while status == 'CREATE_IN_PROGRESS' and stack_retries > 0: time.sleep(self.STACK_RETRY_WAIT) try: - stack = heatclient_.get(vnf_id) + stack = heatclient.get(vnf_id) except Exception: LOG.exception(_("VNF Instance cleanup may not have " "happened because Heat API request failed " @@ -677,7 +177,7 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, if vnf_dict['attributes'].get('scaling_group_names'): group_names = jsonutils.loads( vnf_dict['attributes'].get('scaling_group_names')).values() - mgmt_ips = self._find_mgmt_ips_from_groups(heatclient_, + mgmt_ips = self._find_mgmt_ips_from_groups(heatclient, vnf_id, group_names) else: @@ -691,8 +191,8 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, auth_attr): region_name = vnf_dict.get('placement_attr', {}).get( 'region_name', None) - heatclient_ = HeatClient(auth_attr, region_name) - heatclient_.get(vnf_id) + heatclient = hc.HeatClient(auth_attr, region_name) + heatclient.get(vnf_id) # update config attribute config_yaml = vnf_dict.get('attributes', {}).get('config', '') @@ -728,29 +228,31 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, new_yaml = yaml.dump(config_dict) vnf_dict.setdefault('attributes', {})['config'] = new_yaml + @log.log def update_wait(self, plugin, context, vnf_id, auth_attr, region_name=None): # do nothing but checking if the stack exists at the moment - heatclient_ = HeatClient(auth_attr, region_name) - heatclient_.get(vnf_id) + heatclient = hc.HeatClient(auth_attr, region_name) + heatclient.get(vnf_id) + @log.log def delete(self, plugin, context, vnf_id, auth_attr, region_name=None): - heatclient_ = HeatClient(auth_attr, region_name) - heatclient_.delete(vnf_id) + heatclient = hc.HeatClient(auth_attr, region_name) + heatclient.delete(vnf_id) @log.log def delete_wait(self, plugin, context, vnf_id, auth_attr, region_name=None): - heatclient_ = HeatClient(auth_attr, region_name) + heatclient = hc.HeatClient(auth_attr, region_name) - stack = heatclient_.get(vnf_id) + stack = heatclient.get(vnf_id) status = stack.stack_status error_reason = None stack_retries = self.STACK_RETRIES while (status == 'DELETE_IN_PROGRESS' and stack_retries > 0): time.sleep(self.STACK_RETRY_WAIT) try: - stack = heatclient_.get(vnf_id) + stack = heatclient.get(vnf_id) except heatException.HTTPNotFound: return except Exception: @@ -781,10 +283,7 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, eason=error_reason) @classmethod - def _find_mgmt_ips_from_groups(cls, - heat_client, - instance_id, - group_names): + def _find_mgmt_ips_from_groups(cls, heat_client, instance_id, group_names): def _find_mgmt_ips(attributes): mgmt_ips = {} @@ -797,14 +296,11 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, mgmt_ips = {} for group_name in group_names: # Get scale group - grp = heat_client.resource_get(instance_id, - group_name) - for rsc in heat_client.resource_get_list( - grp.physical_resource_id): + grp = heat_client.resource_get(instance_id, group_name) + for rsc in heat_client.resource_get_list(grp.physical_resource_id): # Get list of resources in scale group - scale_rsc = heat_client.resource_get( - grp.physical_resource_id, - rsc.resource_name) + scale_rsc = heat_client.resource_get(grp.physical_resource_id, + rsc.resource_name) # findout the mgmt ips from attributes for k, v in _find_mgmt_ips(scale_rsc.attributes).items(): @@ -816,38 +312,22 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, return mgmt_ips @log.log - def scale(self, - context, - plugin, - auth_attr, - policy, - region_name): - heatclient_ = HeatClient(auth_attr, region_name) - policy_rsc = get_scaling_policy_name( - policy_name=policy['id'], - action=policy['action'] - ) - events = heatclient_.resource_event_list( - policy['instance_id'], - policy_rsc, - limit=1, - sort_dir='desc', - sort_keys='event_time' - ) + def scale(self, context, plugin, auth_attr, policy, region_name): + heatclient = hc.HeatClient(auth_attr, region_name) + policy_rsc = get_scaling_policy_name(policy_name=policy['id'], + action=policy['action']) + events = heatclient.resource_event_list(policy['instance_id'], + policy_rsc, limit=1, + sort_dir='desc', + sort_keys='event_time') - heatclient_.resource_signal(policy['instance_id'], - policy_rsc) + heatclient.resource_signal(policy['instance_id'], policy_rsc) return events[0].id @log.log - def scale_wait(self, - context, - plugin, - auth_attr, - policy, - region_name, + def scale_wait(self, context, plugin, auth_attr, policy, region_name, last_event_id): - heatclient_ = HeatClient(auth_attr, region_name) + heatclient = hc.HeatClient(auth_attr, region_name) # TODO(kanagaraj-manickam) make wait logic into separate utility method # and make use of it here and other actions like create and delete @@ -856,15 +336,12 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, try: time.sleep(self.STACK_RETRY_WAIT) stack_id = policy['instance_id'] - policy_name = get_scaling_policy_name( - policy_name=policy['id'], - action=policy['action']) - events = heatclient_.resource_event_list( - stack_id, - policy_name, - limit=1, - sort_dir='desc', - sort_keys='event_time') + policy_name = get_scaling_policy_name(policy_name=policy['id'], + action=policy['action']) + events = heatclient.resource_event_list(stack_id, policy_name, + limit=1, + sort_dir='desc', + sort_keys='event_time') if events[0].id != last_event_id: if events[0].resource_status == 'SIGNAL_COMPLETE': @@ -872,16 +349,14 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, except Exception as e: error_reason = _("VNF scaling failed for stack %(stack)s with " "error %(error)s") % { - 'stack': policy['instance_id'], - 'error': e.message - } + 'stack': policy['instance_id'], + 'error': e.message} LOG.warning(error_reason) - raise vnfm.VNFScaleWaitFailed( - vnf_id=policy['vnf']['id'], - reason=error_reason) + raise vnfm.VNFScaleWaitFailed(vnf_id=policy['vnf']['id'], + reason=error_reason) if stack_retries == 0: - metadata = heatclient_.resource_metadata(stack_id, policy_name) + metadata = heatclient.resource_metadata(stack_id, policy_name) if not metadata['scaling_in_progress']: error_reason = _('when signal occurred within cool down ' 'window, no events generated from heat, ' @@ -895,9 +370,8 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, 'wait': self.STACK_RETRIES * self.STACK_RETRY_WAIT} LOG.warning(error_reason) - raise vnfm.VNFScaleWaitFailed( - vnf_id=policy['vnf']['id'], - reason=error_reason) + raise vnfm.VNFScaleWaitFailed(vnf_id=policy['vnf']['id'], + reason=error_reason) stack_retries -= 1 def _fill_scaling_group_name(): @@ -908,19 +382,19 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, _fill_scaling_group_name() - mgmt_ips = self._find_mgmt_ips_from_groups( - heatclient_, - policy['instance_id'], - [policy['group_name']]) + mgmt_ips = self._find_mgmt_ips_from_groups(heatclient, + policy['instance_id'], + [policy['group_name']]) return jsonutils.dumps(mgmt_ips) + @log.log def get_resource_info(self, plugin, context, vnf_info, auth_attr, region_name=None): stack_id = vnf_info['instance_id'] - heatclient_ = HeatClient(auth_attr, region_name) + heatclient = hc.HeatClient(auth_attr, region_name) try: - resources_ids = heatclient_.resource_get_list(stack_id) + resources_ids = heatclient.resource_get_list(stack_id) details_dict = {resource.resource_name: {"id": resource.physical_resource_id, "type": resource.resource_type} @@ -929,56 +403,3 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, # Raise exception when Heat API service is not available except Exception: raise vnfm.InfraDriverUnreachable(service="Heat API service") - - -class HeatClient(object): - def __init__(self, auth_attr, region_name=None): - # context, password are unused - self.heat = clients.OpenstackClients(auth_attr, region_name).heat - self.stacks = self.heat.stacks - self.resource_types = self.heat.resource_types - self.resources = self.heat.resources - - def create(self, fields): - fields = fields.copy() - fields.update({ - 'timeout_mins': 10, - 'disable_rollback': True}) - if 'password' in fields.get('template', {}): - fields['password'] = fields['template']['password'] - - try: - return self.stacks.create(**fields) - except heatException.HTTPException: - type_, value, tb = sys.exc_info() - raise vnfm.HeatClientException(msg=value) - - def delete(self, stack_id): - try: - self.stacks.delete(stack_id) - except heatException.HTTPNotFound: - LOG.warning(_("Stack %(stack)s created by service chain driver is " - "not found at cleanup"), {'stack': stack_id}) - - def get(self, stack_id): - return self.stacks.get(stack_id) - - def resource_attr_support(self, resource_name, property_name): - resource = self.resource_types.get(resource_name) - return property_name in resource['attributes'] - - def resource_get_list(self, stack_id, nested_depth=0): - return self.heat.resources.list(stack_id, - nested_depth=nested_depth) - - def resource_signal(self, stack_id, rsc_name): - return self.heat.resources.signal(stack_id, rsc_name) - - def resource_get(self, stack_id, rsc_name): - return self.heat.resources.get(stack_id, rsc_name) - - def resource_event_list(self, stack_id, rsc_name, **kwargs): - return self.heat.events.list(stack_id, rsc_name, **kwargs) - - def resource_metadata(self, stack_id, rsc_name): - return self.heat.resources.metadata(stack_id, rsc_name) diff --git a/tacker/vnfm/infra_drivers/openstack/translate_template.py b/tacker/vnfm/infra_drivers/openstack/translate_template.py new file mode 100644 index 000000000..46b1da9f6 --- /dev/null +++ b/tacker/vnfm/infra_drivers/openstack/translate_template.py @@ -0,0 +1,579 @@ +# 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 copy + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_serialization import jsonutils +from six import iteritems +from toscaparser import tosca_template +from toscaparser.utils import yamlparser +from translator.hot import tosca_translator +import yaml + +from tacker.common import log +from tacker.extensions import vnfm +from tacker.vnfm.tosca import utils as toscautils + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + +OPTS = [ + cfg.DictOpt('flavor_extra_specs', + default={}, + help=_("Flavor Extra Specs")), +] + +CONF.register_opts(OPTS, group='openstack_vim') + +HEAT_VERSION_INCOMPATIBILITY_MAP = {'OS::Neutron::Port': { + 'port_security_enabled': 'value_specs', }, } + +HEAT_TEMPLATE_BASE = """ +heat_template_version: 2013-05-23 +""" + +ALARMING_POLICY = 'tosca.policies.tacker.Alarming' +SCALING_POLICY = 'tosca.policies.tacker.Scaling' + + +def get_scaling_policy_name(action, policy_name): + return '%s_scale_%s' % (policy_name, action) + + +class TOSCAToHOT(object): + """Convert TOSCA template to HOT template.""" + + def __init__(self, vnf, heatclient): + self.vnf = vnf + self.heatclient = heatclient + self.attributes = {} + self.vnfd_yaml = None + self.unsupported_props = {} + self.heat_template_yaml = None + self.monitoring_dict = None + self.fields = None + self.STACK_FLAVOR_EXTRA = cfg.CONF.openstack_vim.flavor_extra_specs + + @log.log + def generate_hot(self): + + self._get_vnfd() + dev_attrs = self._update_fields() + + vnfd_dict = yamlparser.simple_ordered_parse(self.vnfd_yaml) + LOG.debug('vnfd_dict %s', vnfd_dict) + self._get_unsupported_resource_props(self.heatclient) + + is_tosca_format = False + if 'tosca_definitions_version' in vnfd_dict: + self._generate_hot_from_tosca(vnfd_dict, dev_attrs) + is_tosca_format = True + else: + self._generate_hot_from_legacy(vnfd_dict, dev_attrs) + + self.fields['template'] = self.heat_template_yaml + if is_tosca_format: + self._handle_scaling(vnfd_dict) + if self.monitoring_dict: + self.vnf['attributes']['monitoring_policy'] = jsonutils.dumps( + self.monitoring_dict) + + @log.log + def _get_vnfd(self): + self.attributes = self.vnf['vnfd']['attributes'].copy() + self.vnfd_yaml = self.attributes.pop('vnfd', None) + if self.vnfd_yaml is None: + # TODO(kangaraj-manickam) raise user level exception + LOG.info(_("VNFD is not provided, so no vnf is created !!")) + return + LOG.debug('vnfd_yaml %s', self.vnfd_yaml) + + @log.log + def _update_fields(self): + attributes = self.attributes + fields = dict((key, attributes.pop(key)) for key + in ('stack_name', 'template_url', 'template') + if key in attributes) + for key in ('files', 'parameters'): + if key in attributes: + fields[key] = jsonutils.loads(attributes.pop(key)) + + # overwrite parameters with given dev_attrs for vnf creation + dev_attrs = self.vnf['attributes'].copy() + fields.update(dict((key, dev_attrs.pop(key)) for key + in ('stack_name', 'template_url', 'template') + if key in dev_attrs)) + for key in ('files', 'parameters'): + if key in dev_attrs: + fields.setdefault(key, {}).update( + jsonutils.loads(dev_attrs.pop(key))) + + self.attributes = attributes + self.fields = fields + return dev_attrs + + @log.log + def _handle_scaling(self, vnfd_dict): + + vnf = self.vnf + (is_scaling_needed, scaling_group_names, + main_dict) = self._generate_hot_scaling( + vnfd_dict['topology_template'], 'scaling.yaml') + (is_enabled_alarm, alarm_resource, sub_heat_tpl_yaml) =\ + self._generate_hot_alarm_resource(vnfd_dict['topology_template']) + if is_enabled_alarm and not is_scaling_needed: + self.fields['template'] = self.heat_template_yaml + + if is_scaling_needed: + if is_enabled_alarm: + main_dict['resources'].update(alarm_resource) + main_yaml = yaml.dump(main_dict) + self.fields['template'] = main_yaml + if is_enabled_alarm: + self.fields['files'] = { + 'scaling.yaml': sub_heat_tpl_yaml + } + else: + self.fields['files'] = { + 'scaling.yaml': self.heat_template_yaml + } + vnf['attributes']['heat_template'] = main_yaml + # TODO(kanagaraj-manickam) when multiple groups are + # supported, make this scaling atribute as + # scaling name vs scaling template map and remove + # scaling_group_names + vnf['attributes']['scaling.yaml'] = self.heat_template_yaml + vnf['attributes']['scaling_group_names'] = jsonutils.dumps( + scaling_group_names) + + elif not vnf['attributes'].get('heat_template'): + vnf['attributes']['heat_template'] = self.fields['template'] + self.vnf = vnf + + @log.log + def _update_params(self, original, paramvalues, match=False): + for key, value in iteritems(original): + if not isinstance(value, dict) or 'get_input' not in str(value): + pass + elif isinstance(value, dict): + if not match: + if key in paramvalues and 'param' in paramvalues[key]: + self._update_params(value, paramvalues[key]['param'], + True) + elif key in paramvalues: + self._update_params(value, paramvalues[key], False) + else: + LOG.debug('Key missing Value: %s', key) + raise vnfm.InputValuesMissing(key=key) + elif 'get_input' in value: + if value['get_input'] in paramvalues: + original[key] = paramvalues[value['get_input']] + else: + LOG.debug('Key missing Value: %s', key) + raise vnfm.InputValuesMissing(key=key) + else: + self._update_params(value, paramvalues, True) + + @log.log + def _process_parameterized_input(self, dev_attrs, vnfd_dict): + param_vattrs_yaml = dev_attrs.pop('param_values', None) + if param_vattrs_yaml: + try: + param_vattrs_dict = yaml.load(param_vattrs_yaml) + LOG.debug('param_vattrs_yaml', param_vattrs_dict) + except Exception as e: + LOG.debug("Not Well Formed: %s", str(e)) + raise vnfm.ParamYAMLNotWellFormed( + error_msg_details=str(e)) + else: + self._update_params(vnfd_dict, param_vattrs_dict) + else: + raise vnfm.ParamYAMLInputMissing() + + @log.log + def _process_vdu_network_interfaces(self, vdu_id, vdu_dict, properties, + template_dict): + + networks_list = [] + properties['networks'] = networks_list + for network_param in vdu_dict['network_interfaces'].values(): + port = None + if 'addresses' in network_param: + ip_list = network_param.pop('addresses', []) + if not isinstance(ip_list, list): + raise vnfm.IPAddrInvalidInput() + mgmt_flag = network_param.pop('management', False) + port, template_dict =\ + self._handle_port_creation(vdu_id, network_param, + template_dict, + ip_list, mgmt_flag) + if network_param.pop('management', False): + port, template_dict = self._handle_port_creation(vdu_id, + network_param, + template_dict, + [], True) + if port is not None: + network_param = { + 'port': {'get_resource': port} + } + networks_list.append(dict(network_param)) + return vdu_dict, template_dict + + @log.log + def _make_port_dict(self): + port_dict = {'type': 'OS::Neutron::Port'} + if self.unsupported_props: + port_dict['properties'] = { + 'value_specs': { + 'port_security_enabled': False + } + } + else: + port_dict['properties'] = { + 'port_security_enabled': False + } + port_dict['properties'].setdefault('fixed_ips', []) + return port_dict + + @log.log + def _make_mgmt_outputs_dict(self, vdu_id, port, template_dict): + mgmt_ip = 'mgmt_ip-%s' % vdu_id + outputs_dict = template_dict['outputs'] + outputs_dict[mgmt_ip] = { + 'description': 'management ip address', + 'value': { + 'get_attr': [port, 'fixed_ips', 0, 'ip_address'] + } + } + template_dict['outputs'] = outputs_dict + return template_dict + + @log.log + def _handle_port_creation(self, vdu_id, network_param, + template_dict, ip_list=None, + mgmt_flag=False): + ip_list = ip_list or [] + port = '%s-%s-port' % (vdu_id, network_param['network']) + port_dict = self._make_port_dict() + if mgmt_flag: + template_dict = self._make_mgmt_outputs_dict(vdu_id, port, + template_dict) + for ip in ip_list: + port_dict['properties']['fixed_ips'].append({"ip_address": ip}) + port_dict['properties'].update(network_param) + template_dict['resources'][port] = port_dict + return port, template_dict + + @log.log + def _get_unsupported_resource_props(self, heat_client): + unsupported_resource_props = {} + + for res, prop_dict in iteritems(HEAT_VERSION_INCOMPATIBILITY_MAP): + unsupported_props = {} + for prop, val in iteritems(prop_dict): + if not heat_client.resource_attr_support(res, prop): + unsupported_props.update(prop_dict) + if unsupported_props: + unsupported_resource_props[res] = unsupported_props + self.unsupported_props = unsupported_resource_props + + @log.log + def _generate_hot_from_tosca(self, vnfd_dict, dev_attrs): + parsed_params = {} + if 'param_values' in dev_attrs and dev_attrs['param_values'] != "": + try: + parsed_params = yaml.load(dev_attrs['param_values']) + except Exception as e: + LOG.debug("Params not Well Formed: %s", str(e)) + raise vnfm.ParamYAMLNotWellFormed(error_msg_details=str(e)) + + toscautils.updateimports(vnfd_dict) + + try: + tosca = tosca_template.ToscaTemplate(parsed_params=parsed_params, + a_file=False, + yaml_dict_tpl=vnfd_dict) + + except Exception as e: + LOG.debug("tosca-parser error: %s", str(e)) + raise vnfm.ToscaParserFailed(error_msg_details=str(e)) + + monitoring_dict = toscautils.get_vdu_monitoring(tosca) + mgmt_ports = toscautils.get_mgmt_ports(tosca) + res_tpl = toscautils.get_resources_dict(tosca, + self.STACK_FLAVOR_EXTRA) + toscautils.post_process_template(tosca) + try: + translator = tosca_translator.TOSCATranslator(tosca, + parsed_params) + heat_template_yaml = translator.translate() + except Exception as e: + LOG.debug("heat-translator error: %s", str(e)) + raise vnfm.HeatTranslatorFailed(error_msg_details=str(e)) + heat_template_yaml = toscautils.post_process_heat_template( + heat_template_yaml, mgmt_ports, res_tpl, self.unsupported_props) + + self.heat_template_yaml = heat_template_yaml + self.monitoring_dict = monitoring_dict + + @log.log + def _generate_hot_from_legacy(self, vnfd_dict, dev_attrs): + assert 'template' not in self.fields + assert 'template_url' not in self.fields + + monitoring_dict = {} + + template_dict = yaml.load(HEAT_TEMPLATE_BASE) + outputs_dict = {} + template_dict['outputs'] = outputs_dict + + if 'get_input' in self.vnfd_yaml: + self._process_parameterized_input(dev_attrs, vnfd_dict) + + KEY_LIST = (('description', 'description'), ) + for (key, vnfd_key) in KEY_LIST: + if vnfd_key in vnfd_dict: + template_dict[key] = vnfd_dict[vnfd_key] + + for vdu_id, vdu_dict in vnfd_dict.get('vdus', {}).items(): + template_dict.setdefault('resources', {})[vdu_id] = { + "type": "OS::Nova::Server" + } + resource_dict = template_dict['resources'][vdu_id] + KEY_LIST = (('image', 'vm_image'), + ('flavor', 'instance_type')) + resource_dict['properties'] = {} + properties = resource_dict['properties'] + for (key, vdu_key) in KEY_LIST: + properties[key] = vdu_dict[vdu_key] + if 'network_interfaces' in vdu_dict: + vdu_dict, template_dict =\ + self._process_vdu_network_interfaces(vdu_id, vdu_dict, + properties, template_dict) + if 'user_data' in vdu_dict and 'user_data_format' in vdu_dict: + properties['user_data_format'] = vdu_dict['user_data_format'] + properties['user_data'] = vdu_dict['user_data'] + elif 'user_data' in vdu_dict or 'user_data_format' in vdu_dict: + raise vnfm.UserDataFormatNotFound() + if 'placement_policy' in vdu_dict: + if 'availability_zone' in vdu_dict['placement_policy']: + properties['availability_zone'] = vdu_dict[ + 'placement_policy']['availability_zone'] + if 'config' in vdu_dict: + properties['config_drive'] = True + metadata = properties.setdefault('metadata', {}) + metadata.update(vdu_dict['config']) + for key, value in metadata.items(): + metadata[key] = value[:255] + if 'key_name' in vdu_dict: + properties['key_name'] = vdu_dict['key_name'] + + monitoring_policy = vdu_dict.get('monitoring_policy', 'noop') + failure_policy = vdu_dict.get('failure_policy', 'noop') + + # Convert the old monitoring specification to the new + # network. This should be removed after Mitaka + if monitoring_policy == 'ping' and failure_policy == 'respawn': + vdu_dict['monitoring_policy'] = { + 'ping': {'actions': {'failure': 'respawn'}}} + vdu_dict.pop('failure_policy') + + if monitoring_policy != 'noop': + monitoring_dict['vdus'] = {} + monitoring_dict['vdus'][vdu_id] = vdu_dict['monitoring_policy'] + + # to pass necessary parameters to plugin upwards. + for key in ('service_type',): + if key in vdu_dict: + self.vnf.setdefault('attributes', {})[vdu_id] =\ + jsonutils.dumps({key: vdu_dict[key]}) + + heat_template_yaml = yaml.dump(template_dict) + + self.heat_template_yaml = heat_template_yaml + self.monitoring_dict = monitoring_dict + + @log.log + def _generate_hot_scaling(self, vnfd_dict, + scale_resource_type="OS::Nova::Server"): + # Initialize the template + template_dict = yaml.load(HEAT_TEMPLATE_BASE) + template_dict['description'] = 'Tacker scaling template' + + parameters = {} + template_dict['parameters'] = parameters + + # Add scaling related resource defs + resources = {} + scaling_group_names = {} + # policies: + # - SP1: + # type: tosca.policies.tacker.Scaling + if 'policies' in vnfd_dict: + for policy_dict in vnfd_dict['policies']: + name, policy = list(policy_dict.items())[0] + if policy['type'] == SCALING_POLICY: + resources, scaling_group_names =\ + self._convert_to_heat_scaling_policy( + policy['properties'], scale_resource_type, name) + # TODO(kanagaraj-manickam) only one policy is supported + # for all vdus. remove this break, once this limitation + # is addressed. + break + + template_dict['resources'] = resources + + # First return value helps to check if scaling resources exist + return ((len(template_dict['resources']) > 0), scaling_group_names, + template_dict) + + @log.log + def _convert_to_heat_scaling_group(self, policy_prp, scale_resource_type, + name): + group_hot = {'type': 'OS::Heat::AutoScalingGroup'} + properties = {} + properties['min_size'] = policy_prp['min_instances'] + properties['max_size'] = policy_prp['max_instances'] + properties['desired_capacity'] = policy_prp['default_instances'] + properties['cooldown'] = policy_prp['cooldown'] + properties['resource'] = {} + # TODO(kanagaraj-manickam) all VDU memebers are considered as 1 + # group now and make it to form the groups based on the VDU + # list mentioned in the policy's targets + # scale_resource_type is custome type mapped the HOT template + # generated for all VDUs in the tosca template + properties['resource']['type'] = scale_resource_type + # TODO(kanagraj-manickam) add custom type params here, to + # support parameterized template + group_hot['properties'] = properties + + return group_hot + + # TODO(kanagaraj-manickam) now only one group is supported, so name + # is hard-coded with G1 + @log.log + def _get_scale_group_name(self, targets): + return 'G1' + + # tosca policies + # + # properties: + # adjust_by: 1 + # cooldown: 120 + # targets: [G1] + @log.log + def _convert_to_heat_scaling_policy(self, policy_prp, scale_resource_type, + name): + # Add scaling related resource defs + resources = {} + scaling_group_names = {} + + # Form the group + scale_grp = self._get_scale_group_name(policy_prp['targets']) + scaling_group_names[name] = scale_grp + resources[scale_grp] = self._convert_to_heat_scaling_group( + policy_prp, scale_resource_type, scale_grp) + + grp_id = {'get_resource': scale_grp} + + policy_hot = {'type': 'OS::Heat::ScalingPolicy'} + properties = {} + properties['adjustment_type'] = 'change_in_capacity' + properties['cooldown'] = policy_prp['cooldown'] + properties['scaling_adjustment'] = policy_prp['increment'] + properties['auto_scaling_group_id'] = grp_id + policy_hot['properties'] = properties + + # Add scale_out policy + policy_rsc_name = get_scaling_policy_name(action='out', + policy_name=name) + resources[policy_rsc_name] = policy_hot + + # Add scale_in policy + in_value = '-%d' % int(policy_prp['increment']) + policy_hot_in = copy.deepcopy(policy_hot) + policy_hot_in['properties']['scaling_adjustment'] = in_value + policy_rsc_name = get_scaling_policy_name(action='in', + policy_name=name) + resources[policy_rsc_name] = policy_hot_in + return resources, scaling_group_names + + @log.log + def _generate_hot_alarm_resource(self, topology_tpl_dict): + alarm_resource = dict() + heat_tpl = self.heat_template_yaml + heat_dict = yamlparser.simple_ordered_parse(heat_tpl) + sub_heat_dict = copy.deepcopy(heat_dict) + is_enabled_alarm = False + + if 'policies' in topology_tpl_dict: + for policy_dict in topology_tpl_dict['policies']: + name, policy_tpl_dict = list(policy_dict.items())[0] + if policy_tpl_dict['type'] == ALARMING_POLICY: + is_enabled_alarm = True + alarm_resource[name], sub_heat_dict =\ + self._convert_to_heat_monitoring_resource(policy_dict, + self.vnf, sub_heat_dict, topology_tpl_dict) + heat_dict['resources'].update(alarm_resource) + break + + self.heat_template_yaml = yaml.dump(heat_dict) + sub_heat_tpl_yaml = yaml.dump(sub_heat_dict) + return (is_enabled_alarm, alarm_resource, sub_heat_tpl_yaml) + + @log.log + def _convert_to_heat_monitoring_resource(self, mon_policy, vnf, + sub_heat_dict, topology_tpl_dict): + mon_policy_hot = {'type': 'OS::Aodh::Alarm'} + mon_policy_hot['properties'] = self._convert_to_heat_monitoring_prop( + mon_policy, vnf) + + if 'policies' in topology_tpl_dict: + for policies in topology_tpl_dict['policies']: + policy_name, policy_dt = list(policies.items())[0] + if policy_dt['type'] == SCALING_POLICY: + # Fixed metadata. it will be fixed + # once targets are supported + metadata_dict = dict() + metadata_dict['metering.vnf_id'] = vnf['id'] + sub_heat_dict['resources']['VDU1']['properties']['metadata'] =\ + metadata_dict + matching_metadata_dict = dict() + matching_metadata_dict['metadata.user_metadata.vnf_id'] =\ + vnf['id'] + mon_policy_hot['properties']['matching_metadata'] =\ + matching_metadata_dict + break + return mon_policy_hot, sub_heat_dict + + @log.log + def _convert_to_heat_monitoring_prop(self, mon_policy, vnf): + name, mon_policy_dict = list(mon_policy.items())[0] + tpl_trigger_name = mon_policy_dict['triggers']['resize_compute'] + tpl_condition = tpl_trigger_name['condition'] + properties = {} + properties['meter_name'] = tpl_trigger_name['metrics'] + properties['comparison_operator'] =\ + tpl_condition['comparison_operator'] + properties['period'] = tpl_condition['period'] + properties['evaluation_periods'] = tpl_condition['evaluations'] + properties['statistic'] = tpl_condition['method'] + properties['description'] = tpl_condition['constraint'] + properties['threshold'] = tpl_condition['threshold'] + # alarm url process here + alarm_url = str(vnf['attributes'].get('alarm_url')) + if alarm_url: + LOG.debug('Alarm url in heat %s', alarm_url) + properties['alarm_actions'] = [alarm_url] + return properties diff --git a/tacker/vnfm/monitor.py b/tacker/vnfm/monitor.py index 0f87ea5a4..0c485bd37 100644 --- a/tacker/vnfm/monitor.py +++ b/tacker/vnfm/monitor.py @@ -30,7 +30,7 @@ from tacker.common import driver_manager from tacker import context as t_context from tacker.db.common_services import common_services_db from tacker.plugins.common import constants -from tacker.vnfm.infra_drivers.openstack import openstack +from tacker.vnfm.infra_drivers.openstack import heat_client as hc from tacker.vnfm import vim_client LOG = logging.getLogger(__name__) @@ -359,8 +359,8 @@ class ActionRespawnHeat(ActionPolicy): def _delete_heat_stack(vim_auth): placement_attr = vnf_dict.get('placement_attr', {}) region_name = placement_attr.get('region_name') - heatclient = openstack.HeatClient(auth_attr=vim_auth, - region_name=region_name) + heatclient = hc.HeatClient(auth_attr=vim_auth, + region_name=region_name) heatclient.delete(vnf_dict['instance_id']) LOG.debug(_("Heat stack %s delete initiated"), vnf_dict[ 'instance_id'])