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.
 
 
tacker/tacker/vnfm/infra_drivers/openstack/openstack.py

1165 lines
50 KiB

# Copyright 2015 Intel Corporation.
# 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 copy
import eventlet
import importlib
import os
import sys
import time
import yaml
from collections import OrderedDict
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import excutils
from oslo_utils import uuidutils
from tacker._i18n import _
from tacker.common import exceptions
from tacker.common import log
from tacker.common import utils
from tacker.extensions import vnflcm
from tacker.extensions import vnfm
from tacker import objects
from tacker.objects import fields
from tacker.tosca.utils import represent_odict
from tacker.vnflcm import utils as vnflcm_utils
from tacker.vnfm.infra_drivers import abstract_driver
from tacker.vnfm.infra_drivers.openstack import constants as infra_cnst
from tacker.vnfm.infra_drivers.openstack import glance_client as gc
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.openstack import vdu
from tacker.vnfm.infra_drivers import scale_driver
from tacker.vnfm.lcm_user_data.constants import USER_DATA_TIMEOUT
eventlet.monkey_patch(time=True)
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
OPTS = [
cfg.IntOpt('stack_retries',
default=60,
help=_("Number of attempts to retry for stack"
" creation/deletion")),
cfg.IntOpt('stack_retry_wait',
default=10,
help=_("Wait time (in seconds) between consecutive stack"
" create/delete retries")),
]
CONF.register_opts(OPTS, group='openstack_vim')
def config_opts():
return [('openstack_vim', OPTS)]
# Global map of individual resource type and
# incompatible properties, alternate properties pair for
# upgrade/downgrade across all Heat template versions (starting Kilo)
#
# Maintains a dictionary of {"resource type": {dict of "incompatible
# property": "alternate_prop"}}
HEAT_VERSION_INCOMPATIBILITY_MAP = {'OS::Neutron::Port': {
'port_security_enabled': 'value_specs', }, }
HEAT_TEMPLATE_BASE = """
heat_template_version: 2013-05-23
"""
OUTPUT_PREFIX = 'mgmt_ip-'
ALARMING_POLICY = 'tosca.policies.tacker.Alarming'
SCALING_POLICY = 'tosca.policies.tacker.Scaling'
NOVA_SERVER_RESOURCE = "OS::Nova::Server"
def get_scaling_policy_name(action, policy_name):
return '%s_scale_%s' % (policy_name, action)
class OpenStack(abstract_driver.VnfAbstractDriver,
scale_driver.VnfScaleAbstractDriver):
"""Openstack infra driver for hosting vnfs"""
def __init__(self):
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.IMAGE_RETRIES = 10
self.IMAGE_RETRY_WAIT = 10
def get_type(self):
return 'openstack'
def get_name(self):
return 'openstack'
def get_description(self):
return 'Openstack infra driver'
@log.log
def create(self, plugin, context, vnf, auth_attr,
base_hot_dict=None, vnf_package_path=None,
inst_req_info=None, grant_info=None,
vnf_instance=None):
LOG.debug('vnf %s', vnf)
region_name = vnf.get('placement_attr', {}).get('region_name', None)
heatclient = hc.HeatClient(auth_attr, region_name)
additional_param = None
if inst_req_info is not None:
additional_param = inst_req_info.additional_params
user_data_path = None
user_data_class = None
if additional_param is not None:
LOG.debug('additional_param: %s', additional_param)
user_data_path = additional_param.get(
'lcm-operation-user-data')
user_data_class = additional_param.get(
'lcm-operation-user-data-class')
LOG.debug('UserData path: %s', user_data_path)
LOG.debug('UserData class: %s', user_data_class)
if user_data_path is not None and user_data_class is not None:
LOG.info('Execute user data and create heat-stack.')
base_hot_dict, nested_hot_dict = vnflcm_utils. \
get_base_nest_hot_dict(context,
inst_req_info.flavour_id,
vnf_instance.vnfd_id)
if base_hot_dict is None:
error_reason = _("failed to get Base HOT.")
raise vnfm.LCMUserDataFailed(reason=error_reason)
if base_hot_dict is None:
nested_hot_dict = {}
for name, hot in nested_hot_dict.items():
vnf['attributes'][name] = self._format_base_hot(hot)
vnfd_str = vnf['vnfd']['attributes']['vnfd']
vnfd_dict = yaml.safe_load(vnfd_str)
LOG.debug('VNFD: %s', vnfd_dict)
LOG.debug('VNF package path: %s', vnf_package_path)
sys.path.append(vnf_package_path)
user_data_module = os.path.splitext(
user_data_path.lstrip('./'))[0].replace('/', '.')
LOG.debug('UserData module: %s', user_data_module)
LOG.debug('Append sys.path: %s', sys.path)
try:
module = importlib.import_module(user_data_module)
LOG.debug('Append sys.modules: %s', sys.modules)
except Exception:
self._delete_user_data_module(user_data_module)
error_reason = _(
"failed to get UserData path based on "
"lcm-operation-user-data from additionalParams.")
raise vnfm.LCMUserDataFailed(reason=error_reason)
finally:
sys.path.remove(vnf_package_path)
LOG.debug('Remove sys.path: %s', sys.path)
try:
klass = getattr(module, user_data_class)
except Exception:
self._delete_user_data_module(user_data_module)
error_reason = _(
"failed to get UserData class based on "
"lcm-operation-user-data-class from additionalParams.")
raise vnfm.LCMUserDataFailed(reason=error_reason)
# Set the timeout and execute the UserData script.
hot_param_dict = None
param_base_hot_dict = copy.deepcopy(nested_hot_dict)
param_base_hot_dict['heat_template'] = base_hot_dict
with eventlet.timeout.Timeout(USER_DATA_TIMEOUT, False):
try:
hot_param_dict = klass.instantiate(
param_base_hot_dict, vnfd_dict,
inst_req_info, grant_info)
except Exception:
raise
finally:
self._delete_user_data_module(user_data_module)
if hot_param_dict is not None:
LOG.info('HOT input parameter: %s', hot_param_dict)
else:
error_reason = _(
"fails due to timeout[sec]: %s") % USER_DATA_TIMEOUT
raise vnfm.LCMUserDataFailed(reason=error_reason)
if not isinstance(hot_param_dict, dict):
error_reason = _(
"return value as HOT parameter from UserData "
"is not in dict format.")
raise vnfm.LCMUserDataFailed(reason=error_reason)
# Add stack param to vnf_attributes
vnf['attributes'].update({'stack_param': str(hot_param_dict)})
# Create heat-stack with BaseHOT and parameters
stack = self._create_stack_with_user_data(
heatclient, vnf, base_hot_dict,
nested_hot_dict, hot_param_dict)
elif user_data_path is None and user_data_class is None:
LOG.info('Execute heat-translator and create heat-stack.')
tth = translate_template.TOSCAToHOT(vnf, heatclient,
inst_req_info, grant_info)
tth.generate_hot()
stack = self._create_stack(heatclient, tth.vnf, tth.fields)
else:
error_reason = _(
"failed to get lcm-operation-user-data or "
"lcm-operation-user-data-class from additionalParams.")
raise vnfm.LCMUserDataFailed(reason=error_reason)
return stack['stack']['id']
@log.log
def _delete_user_data_module(self, user_data_module):
# Delete module recursively.
mp_list = user_data_module.split('.')
while True:
del_module = '.'.join(mp_list)
print(del_module)
if del_module in sys.modules:
del sys.modules[del_module]
if len(mp_list) == 1:
break
mp_list = mp_list[0:-1]
LOG.debug('Remove sys.modules: %s', sys.modules)
@log.log
def _create_stack_with_user_data(self, heatclient, vnf,
base_hot_dict, nested_hot_dict,
hot_param_dict):
fields = {}
fields['stack_name'] = ("vnflcm_" + vnf["id"])
fields['template'] = self._format_base_hot(base_hot_dict)
fields['parameters'] = hot_param_dict
fields['timeout_mins'] = (
self.STACK_RETRIES * self.STACK_RETRY_WAIT // 60)
if nested_hot_dict:
fields['files'] = {}
for name, value in nested_hot_dict.items():
fields['files'][name] = self._format_base_hot(value)
LOG.debug('fields: %s', fields)
LOG.debug('template: %s', fields['template'])
stack = heatclient.create(fields)
return stack
@log.log
def _format_base_hot(self, base_hot_dict):
yaml.SafeDumper.add_representer(OrderedDict,
lambda dumper, value: represent_odict(dumper,
u'tag:yaml.org,2002:map', value))
return yaml.safe_dump(base_hot_dict)
@log.log
def _create_stack(self, heatclient, vnf, fields):
if 'stack_name' not in fields:
name = vnf['name'].replace(' ', '_') + '_' + vnf['id']
if vnf['attributes'].get('failure_count'):
name += ('-RESPAWN-%s') % str(vnf['attributes'][
'failure_count'])
fields['stack_name'] = name
fields['timeout_mins'] = (
self.STACK_RETRIES * self.STACK_RETRY_WAIT // 60)
# 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 = hc.HeatClient(auth_attr, region_name)
stack = self._wait_until_stack_ready(
vnf_id, auth_attr, infra_cnst.STACK_CREATE_IN_PROGRESS,
infra_cnst.STACK_CREATE_COMPLETE,
vnfm.VNFCreateWaitFailed, region_name=region_name)
# scaling enabled
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,
vnf_id,
group_names)
else:
mgmt_ips = self._find_mgmt_ips(stack.outputs)
if mgmt_ips:
vnf_dict['mgmt_ip_address'] = jsonutils.dump_as_bytes(mgmt_ips)
def _wait_until_stack_ready(self, vnf_id, auth_attr, wait_status,
expected_status, exception_class,
region_name=None):
heatclient = hc.HeatClient(auth_attr, region_name)
stack_retries = self.STACK_RETRIES
status = wait_status
stack = None
while stack_retries > 0:
try:
stack_retries = stack_retries - 1
stack = heatclient.get(vnf_id)
status = stack.stack_status
if status == expected_status:
LOG.debug('stack status: %(stack)s %(status)s',
{'stack': str(stack), 'status': status})
return stack
time.sleep(self.STACK_RETRY_WAIT)
LOG.debug('status: %s', status)
except Exception:
LOG.warning("VNF Instance setup may not have "
"happened because Heat API request failed "
"while waiting for the stack %(stack)s to be "
"created", {'stack': vnf_id})
# continue to avoid temporary connection error to target
# VIM
if stack_retries == 0 and status != expected_status:
error_reason = _("action is not completed within {wait} "
"seconds on stack {stack}").format(
wait=(self.STACK_RETRIES *
self.STACK_RETRY_WAIT),
stack=vnf_id)
raise exception_class(reason=error_reason)
elif stack_retries != 0 and status != wait_status:
error_reason = stack.stack_status_reason
LOG.warning(error_reason)
raise exception_class(reason=error_reason)
def _find_mgmt_ips(self, outputs):
LOG.debug('outputs %s', outputs)
mgmt_ips = dict((output['output_key'][len(OUTPUT_PREFIX):],
output['output_value'])
for output in outputs
if output.get('output_key',
'').startswith(OUTPUT_PREFIX))
return mgmt_ips
@log.log
def update(self, plugin, context, vnf_id, vnf_dict, vnf,
auth_attr):
region_name = vnf_dict.get('placement_attr', {}).get(
'region_name', None)
heatclient = hc.HeatClient(auth_attr, region_name)
heatclient.get(vnf_id)
update_param_yaml = vnf['vnf'].get('attributes', {}).get(
'param_values', '')
update_config_yaml = vnf['vnf'].get('attributes', {}).get(
'config', '')
if update_param_yaml:
# conversion param_values
param_yaml = vnf_dict.get('attributes', {}).get('param_values', '')
param_dict = yaml.safe_load(param_yaml)
update_param_dict = yaml.safe_load(update_param_yaml)
# check update values
update_values = {}
for key, value in update_param_dict.items():
if key not in param_dict or\
update_param_dict[key] != param_dict[key]:
update_values[key] = value
if not update_values:
error_reason = _("at vnf_id {} because all parameters "
"match the existing one.").format(vnf_id)
LOG.warning(error_reason)
raise vnfm.VNFUpdateInvalidInput(reason=error_reason)
# update vnf_dict
utils.deep_update(param_dict, update_param_dict)
new_param_yaml = yaml.safe_dump(param_dict)
vnf_dict.setdefault(
'attributes', {})['param_values'] = new_param_yaml
# run stack update
stack_update_param = {
'parameters': update_values,
'existing': True}
heatclient.update(vnf_id, **stack_update_param)
elif not update_param_yaml and not update_config_yaml:
error_reason = _("at vnf_id {} because the target "
"yaml is empty.").format(vnf_id)
LOG.warning(error_reason)
raise vnfm.VNFUpdateInvalidInput(reason=error_reason)
# update config attribute
config_yaml = vnf_dict.get('attributes', {}).get('config', '')
LOG.debug('yaml orig %(orig)s update %(update)s',
{'orig': config_yaml, 'update': update_config_yaml})
# If config_yaml is None, yaml.safe_load() will raise Attribute Error.
# So set config_yaml to {}, if it is None.
if not config_yaml:
config_dict = {}
else:
config_dict = yaml.safe_load(config_yaml) or {}
update_dict = yaml.safe_load(update_config_yaml)
if not update_dict:
return
LOG.debug('dict orig %(orig)s update %(update)s',
{'orig': config_dict, 'update': update_dict})
utils.deep_update(config_dict, update_dict)
LOG.debug('dict new %(new)s update %(update)s',
{'new': config_dict, 'update': update_dict})
new_yaml = yaml.safe_dump(config_dict)
vnf_dict.setdefault('attributes', {})['config'] = new_yaml
@log.log
def update_wait(self, plugin, context, vnf_dict, auth_attr,
region_name=None):
# do nothing but checking if the stack exists at the moment
heatclient = hc.HeatClient(auth_attr, region_name)
stack = heatclient.get(vnf_dict['instance_id'])
mgmt_ips = self._find_mgmt_ips(stack.outputs)
if mgmt_ips:
vnf_dict['mgmt_ip_address'] = jsonutils.dump_as_bytes(mgmt_ips)
@log.log
def heal_wait(self, plugin, context, vnf_dict, auth_attr,
region_name=None):
region_name = vnf_dict.get('placement_attr', {}).get(
'region_name', None)
heatclient = hc.HeatClient(auth_attr, region_name)
stack_id = vnf_dict.get('heal_stack_id', vnf_dict['instance_id'])
stack = self._wait_until_stack_ready(stack_id,
auth_attr, infra_cnst.STACK_UPDATE_IN_PROGRESS,
infra_cnst.STACK_UPDATE_COMPLETE,
vnfm.VNFHealWaitFailed, region_name=region_name)
# scaling enabled
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,
vnf_dict['instance_id'],
group_names)
else:
mgmt_ips = self._find_mgmt_ips(stack.outputs)
if mgmt_ips:
vnf_dict['mgmt_ip_address'] = jsonutils.dump_as_bytes(mgmt_ips)
@log.log
def delete(self, plugin, context, vnf_id, auth_attr, region_name=None,
vnf_instance=None, terminate_vnf_req=None):
if terminate_vnf_req:
if (terminate_vnf_req.termination_type == 'GRACEFUL' and
terminate_vnf_req.graceful_termination_timeout > 0):
time.sleep(terminate_vnf_req.graceful_termination_timeout)
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, vnf_instance=None):
self._wait_until_stack_ready(vnf_id, auth_attr,
infra_cnst.STACK_DELETE_IN_PROGRESS,
infra_cnst.STACK_DELETE_COMPLETE, vnfm.VNFDeleteWaitFailed,
region_name=region_name)
@classmethod
def _find_mgmt_ips_from_groups(cls, heat_client, instance_id, group_names):
def _find_mgmt_ips(attributes):
mgmt_ips = {}
for k, v in attributes.items():
if k.startswith(OUTPUT_PREFIX):
mgmt_ips[k.replace(OUTPUT_PREFIX, '')] = v
return mgmt_ips
mgmt_ips = {}
ignore_status = ['DELETE_COMPLETE', 'DELETE_IN_PROGRESS']
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):
if rsc.resource_status in ignore_status:
continue
# Get list of resources in scale group
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():
if k not in mgmt_ips:
mgmt_ips[k] = [v]
else:
mgmt_ips[k].append(v)
return mgmt_ips
@log.log
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['name'],
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)
return events[0].id
@log.log
def scale_wait(self, context, plugin, auth_attr, policy, region_name,
last_event_id):
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
stack_retries = self.STACK_RETRIES
while (True):
try:
time.sleep(self.STACK_RETRY_WAIT)
stack_id = policy['instance_id']
policy_name = get_scaling_policy_name(
policy_name=policy['name'], 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':
break
else:
# When the number of instance reaches min or max, the below
# comparision will let VNF status turn into ACTIVE state.
if events[0].resource_status == 'CREATE_COMPLETE' or \
events[0].resource_status == 'SIGNAL_COMPLETE':
break
except Exception as e:
error_reason = _("VNF scaling failed for stack %(stack)s with "
"error %(error)s") % {
'stack': policy['instance_id'],
'error': str(e)}
LOG.warning(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)
if not metadata['scaling_in_progress']:
error_reason = _('When signal occurred within cool down '
'window, no events generated from heat, '
'so ignore it')
LOG.warning(error_reason)
break
error_reason = _(
"VNF scaling failed to complete within %(wait)s seconds "
"while waiting for the stack %(stack)s to be "
"scaled.") % {'stack': stack_id,
'wait': self.STACK_RETRIES *
self.STACK_RETRY_WAIT}
LOG.warning(error_reason)
raise vnfm.VNFScaleWaitFailed(vnf_id=policy['vnf']['id'],
reason=error_reason)
stack_retries -= 1
def _fill_scaling_group_name():
vnf = policy['vnf']
scaling_group_names = vnf['attributes']['scaling_group_names']
policy['group_name'] = jsonutils.loads(
scaling_group_names)[policy['name']]
_fill_scaling_group_name()
mgmt_ips = self._find_mgmt_ips_from_groups(heatclient,
policy['instance_id'],
[policy['group_name']])
return jsonutils.dump_as_bytes(mgmt_ips)
@log.log
def get_resource_info(self, plugin, context, vnf_info, auth_attr,
region_name=None):
instance_id = vnf_info['instance_id']
heatclient = hc.HeatClient(auth_attr, region_name)
try:
# nested_depth=2 is used to get VDU resources
# in case of nested template
resources_ids =\
heatclient.resource_get_list(instance_id, nested_depth=2)
details_dict = {resource.resource_name:
{"id": resource.physical_resource_id,
"type": resource.resource_type}
for resource in resources_ids}
return details_dict
# Raise exception when Heat API service is not available
except Exception:
raise vnfm.InfraDriverUnreachable(service="Heat API service")
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data_obj):
try:
heal_vdu = vdu.Vdu(context, vnf_dict, heal_request_data_obj)
heal_vdu.heal_vdu()
except Exception:
LOG.error("VNF '%s' failed to heal", vnf_dict['id'])
raise vnfm.VNFHealFailed(vnf_id=vnf_dict['id'])
@log.log
def pre_instantiation_vnf(
self, context, vnf_instance, vim_connection_info,
vnf_software_images, instantiate_vnf_req=None,
vnf_package_path=None):
glance_client = gc.GlanceClient(vim_connection_info)
vnf_resources = {}
def _roll_back_images():
# Delete all previously created images for vnf
for key, resources in vnf_resources.items():
for vnf_resource in resources:
try:
glance_client.delete(vnf_resource.resource_identifier)
except Exception:
LOG.error("Failed to delete image %(uuid)s "
"for vnf %(id)s",
{"uuid": vnf_resource.resource_identifier,
"id": vnf_instance.id})
for node_name, vnf_sw_image in vnf_software_images.items():
name = vnf_sw_image.name
image_path = vnf_sw_image.image_path
is_url = utils.is_url(image_path)
if not is_url:
filename = image_path
else:
filename = None
try:
LOG.info("Creating image %(name)s for vnf %(id)s",
{"name": name, "id": vnf_instance.id})
image_data = {"min_disk": vnf_sw_image.min_disk,
"min_ram": vnf_sw_image.min_ram,
"disk_format": vnf_sw_image.disk_format,
"container_format": vnf_sw_image.container_format,
"visibility": "private"}
if filename:
image_data.update({"filename": filename})
image = glance_client.create(name, **image_data)
LOG.info("Image %(name)s created successfully for vnf %(id)s",
{"name": name, "id": vnf_instance.id})
except Exception as exp:
with excutils.save_and_reraise_exception():
exp.reraise = False
LOG.error("Failed to create image %(name)s for vnf %(id)s "
"due to error: %(error)s",
{"name": name, "id": vnf_instance.id,
"error": encodeutils.exception_to_unicode(exp)})
# Delete previously created images
_roll_back_images()
raise exceptions.VnfPreInstantiationFailed(
id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
try:
if is_url:
glance_client.import_image(image, image_path)
self._image_create_wait(image.id, vnf_sw_image, glance_client,
'active', vnflcm.ImageCreateWaitFailed)
vnf_resource = objects.VnfResource(context=context,
vnf_instance_id=vnf_instance.id,
resource_name=name, resource_type="image",
resource_status="CREATED", resource_identifier=image.id)
vnf_resources[node_name] = [vnf_resource]
except Exception as exp:
with excutils.save_and_reraise_exception():
exp.reraise = False
LOG.error("Image %(name)s not active for vnf %(id)s "
"error: %(error)s",
{"name": name, "id": vnf_instance.id,
"error": encodeutils.exception_to_unicode(exp)})
err_msg = "Failed to delete image %(uuid)s for vnf %(id)s"
# Delete the image
try:
glance_client.delete(image.id)
except Exception:
LOG.error(err_msg, {"uuid": image.id,
"id": vnf_instance.id})
# Delete all previously created images for vnf
_roll_back_images()
raise exceptions.VnfPreInstantiationFailed(
id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
return vnf_resources
def _image_create_wait(self, image_uuid, vnf_software_image, glance_client,
expected_status, exception_class):
retries = self.IMAGE_RETRIES
while retries > 0:
retries = retries - 1
image = glance_client.get(image_uuid)
status = image.status
if status == expected_status:
# NOTE(tpatil): If image is uploaded using import_image
# ,sdk doesn't validate checksum. So, verify checksum/hash
# for both scenarios upload from file and URL here.
if vnf_software_image.hash != image.hash_value:
msg = ("Image %(image_uuid)s checksum verification failed."
" Glance calculated checksum is %(hash_algo)s:"
"%(hash_value)s. Checksum in VNFD is "
"%(image_hash_algo)s:%(image_hash_value)s.")
raise Exception(msg % {"image_uuid": image_uuid,
"hash_algo": image.hash_algo,
"hash_value": image.hash_value,
"image_hash_algo": vnf_software_image.algorithm,
"image_hash_value": vnf_software_image.hash})
LOG.debug('Image status: %(image_uuid)s %(status)s',
{'image_uuid': image_uuid, 'status': status})
return True
time.sleep(self.IMAGE_RETRY_WAIT)
LOG.debug('Image %(image_uuid)s status: %(status)s',
{"image_uuid": image_uuid, "status": status})
if retries == 0 and image.status != expected_status:
error_reason = ("Image {image_uuid} could not get active "
"within {wait} seconds").format(
wait=(self.IMAGE_RETRIES *
self.IMAGE_RETRY_WAIT),
image_uuid=image_uuid)
raise exception_class(reason=error_reason)
@log.log
def delete_vnf_instance_resource(self, context, vnf_instance,
vim_connection_info, vnf_resource):
LOG.info("Deleting resource '%(name)s' of type ' %(type)s' for vnf "
"%(id)s", {"type": vnf_resource.resource_type,
"name": vnf_resource.resource_name,
"id": vnf_instance.id})
glance_client = gc.GlanceClient(vim_connection_info)
try:
glance_client.delete(vnf_resource.resource_identifier)
LOG.info("Deleted resource '%(name)s' of type ' %(type)s' for vnf "
"%(id)s", {"type": vnf_resource.resource_type,
"name": vnf_resource.resource_name,
"id": vnf_instance.id})
except Exception:
LOG.info("Failed to delete resource '%(name)s' of type"
" %(type)s' for vnf %(id)s",
{"type": vnf_resource.resource_type,
"name": vnf_resource.resource_name,
"id": vnf_instance.id})
def instantiate_vnf(self, context, vnf_instance, vnfd_dict,
vim_connection_info, instantiate_vnf_req,
grant_response, plugin,
base_hot_dict=None, vnf_package_path=None):
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
placement_attr = vnfd_dict.get('placement_attr', {})
placement_attr.update({'region_name': region_name})
vnfd_dict['placement_attr'] = placement_attr
instance_id = self.create(plugin, context, vnfd_dict,
access_info, base_hot_dict, vnf_package_path,
inst_req_info=instantiate_vnf_req,
grant_info=grant_response,
vnf_instance=vnf_instance)
vnfd_dict['instance_id'] = instance_id
return instance_id
@log.log
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
inst_vnf_info = vnf_instance.instantiated_vnf_info
access_info = vim_connection_info.access_info
heatclient = hc.HeatClient(access_info,
region_name=access_info.get('region'))
stack_resources = self._get_stack_resources(
inst_vnf_info.instance_id, heatclient)
self._update_vnfc_resources(vnf_instance, stack_resources)
self._update_vnfc_info(vnf_instance)
def _update_resource_handle(self, vnf_instance, resource_handle,
stack_resources, resource_name):
if not stack_resources:
LOG.warning("Failed to set resource handle for resource "
"%(resource)s for vnf %(id)s", {"resource": resource_name,
"id": vnf_instance.id})
return
resource_data = stack_resources.pop(resource_name, None)
if not resource_data:
LOG.warning("Failed to set resource handle for resource "
"%(resource)s for vnf %(id)s",
{"resource": resource_name, "id": vnf_instance.id})
return
resource_handle.resource_id = resource_data.get(
'physical_resource_id')
resource_handle.vim_level_resource_type = resource_data.get(
'resource_type')
def _update_vnfc_resource_info(self, vnf_instance, vnfc_res_info,
stack_resources, update_network_resource=True):
inst_vnf_info = vnf_instance.instantiated_vnf_info
def _pop_stack_resources(resource_name):
for stack_id, resources in stack_resources.items():
if resource_name in resources.keys():
return stack_id, resources
return None, {}
def _populate_virtual_link_resource_info(vnf_virtual_link_desc_id,
pop_resources):
vnf_virtual_link_resource_info = \
inst_vnf_info.vnf_virtual_link_resource_info
for vnf_vl_resource_info in vnf_virtual_link_resource_info:
if (vnf_vl_resource_info.vnf_virtual_link_desc_id !=
vnf_virtual_link_desc_id):
continue
vl_resource_data = pop_resources.pop(
vnf_virtual_link_desc_id, None)
if not vl_resource_data:
_, resources = _pop_stack_resources(
vnf_virtual_link_desc_id)
if not resources:
# NOTE(tpatil): network_resource is already set
# from the instantiatevnfrequest during instantiation.
continue
vl_resource_data = resources.get(
vnf_virtual_link_desc_id)
resource_handle = vnf_vl_resource_info.network_resource
resource_handle.resource_id = \
vl_resource_data.get('physical_resource_id')
resource_handle.vim_level_resource_type = \
vl_resource_data.get('resource_type')
def _populate_virtual_link_port(vnfc_cp_info, pop_resources):
vnf_virtual_link_resource_info = \
inst_vnf_info.vnf_virtual_link_resource_info
for vnf_vl_resource_info in vnf_virtual_link_resource_info:
vl_link_port_found = False
for vl_link_port in vnf_vl_resource_info.vnf_link_ports:
if vl_link_port.cp_instance_id == vnfc_cp_info.id:
vl_link_port_found = True
self._update_resource_handle(vnf_instance,
vl_link_port.resource_handle, pop_resources,
vnfc_cp_info.cpd_id)
if vl_link_port_found:
yield vnf_vl_resource_info.vnf_virtual_link_desc_id
def _populate_virtual_storage(vnfc_resource_info, pop_resources):
virtual_storage_resource_info = inst_vnf_info. \
virtual_storage_resource_info
for storage_id in vnfc_resource_info.storage_resource_ids:
for vir_storage_res_info in virtual_storage_resource_info:
if vir_storage_res_info.id == storage_id:
self._update_resource_handle(vnf_instance,
vir_storage_res_info.storage_resource,
pop_resources,
vir_storage_res_info.virtual_storage_desc_id)
break
stack_id, pop_resources = _pop_stack_resources(
vnfc_res_info.vdu_id)
self._update_resource_handle(vnf_instance,
vnfc_res_info.compute_resource, pop_resources,
vnfc_res_info.vdu_id)
vnfc_res_info.metadata.update({"stack_id": stack_id})
_populate_virtual_storage(vnfc_res_info, pop_resources)
# Find out associated VLs, and CP used by vdu_id
virtual_links = set()
for vnfc_cp_info in vnfc_res_info.vnfc_cp_info:
for vl_desc_id in _populate_virtual_link_port(vnfc_cp_info,
pop_resources):
virtual_links.add(vl_desc_id)
if update_network_resource:
for vl_desc_id in virtual_links:
_populate_virtual_link_resource_info(vl_desc_id,
pop_resources)
def _update_ext_managed_virtual_link_ports(self, inst_vnf_info,
ext_managed_vl_info):
vnf_virtual_link_resource_info = \
inst_vnf_info.vnf_virtual_link_resource_info
def _update_link_port(vl_port):
for ext_vl_port in ext_managed_vl_info.vnf_link_ports:
if vl_port.id == ext_vl_port.id:
# Update the resource_id
ext_vl_port.resource_handle.resource_id =\
vl_port.resource_handle.resource_id
ext_vl_port.resource_handle.vim_level_resource_type =\
vl_port.resource_handle.vim_level_resource_type
break
for vnf_vl_resource_info in vnf_virtual_link_resource_info:
if (vnf_vl_resource_info.vnf_virtual_link_desc_id !=
ext_managed_vl_info.vnf_virtual_link_desc_id):
continue
for vl_port in vnf_vl_resource_info.vnf_link_ports:
_update_link_port(vl_port)
def _update_vnfc_info(self, vnf_instance):
inst_vnf_info = vnf_instance.instantiated_vnf_info
vnfc_info = []
for vnfc_res_info in inst_vnf_info.vnfc_resource_info:
vnfc = objects.VnfcInfo(id=uuidutils.generate_uuid(),
vdu_id=vnfc_res_info.vdu_id,
vnfc_state=fields.VnfcState.STARTED)
vnfc_info.append(vnfc)
inst_vnf_info.vnfc_info = vnfc_info
def _update_vnfc_resources(self, vnf_instance, stack_resources):
inst_vnf_info = vnf_instance.instantiated_vnf_info
for vnfc_res_info in inst_vnf_info.vnfc_resource_info:
self._update_vnfc_resource_info(vnf_instance, vnfc_res_info,
stack_resources)
# update vnf_link_ports of ext_managed_virtual_link_info using already
# populated vnf_link_ports from vnf_virtual_link_resource_info.
for ext_mng_vl_info in inst_vnf_info.ext_managed_virtual_link_info:
self._update_ext_managed_virtual_link_ports(inst_vnf_info,
ext_mng_vl_info)
def _get_stack_resources(self, stack_id, heatclient):
def _stack_ids(stack_id):
filters = {
"owner_id": stack_id,
"show_nested": True
}
yield stack_id
for stack in heatclient.stacks.list(**{"filters": filters}):
if stack.parent and stack.parent == stack_id:
for x in _stack_ids(stack.id):
yield x
resource_details = {}
for id in _stack_ids(stack_id):
resources = {}
child_stack = False if id == stack_id else True
for stack_resource in heatclient.resources.list(id):
resource_data = {"resource_type":
stack_resource.resource_type,
"physical_resource_id":
stack_resource.physical_resource_id}
resources[stack_resource.resource_name] = resource_data
resource_details[id] = resources
resource_details[id].update({'child_stack': child_stack})
return resource_details
def _get_vnfc_resources_from_heal_request(self, inst_vnf_info,
heal_vnf_request):
if not heal_vnf_request.vnfc_instance_id:
# include all vnfc resources
return [resource for resource in inst_vnf_info.vnfc_resource_info]
vnfc_resources = []
for vnfc_resource in inst_vnf_info.vnfc_resource_info:
if vnfc_resource.id in heal_vnf_request.vnfc_instance_id:
vnfc_resources.append(vnfc_resource)
return vnfc_resources
@log.log
def heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
inst_vnf_info = vnf_instance.instantiated_vnf_info
heatclient = hc.HeatClient(access_info, region_name=region_name)
def _get_storage_resources(vnfc_resource):
# Prepare list of storage resources to be marked unhealthy
for storage_id in vnfc_resource.storage_resource_ids:
for storage_res_info in inst_vnf_info. \
virtual_storage_resource_info:
if storage_res_info.id == storage_id:
yield storage_res_info.virtual_storage_desc_id, \
storage_res_info.storage_resource.resource_id
def _get_vdu_resources(vnfc_resources):
# Prepare list of vdu resources to be marked unhealthy
vdu_resources = []
for vnfc_resource in vnfc_resources:
resource_details = {"resource_name": vnfc_resource.vdu_id,
"physical_resource_id":
vnfc_resource.compute_resource.resource_id}
vdu_resources.append(resource_details)
# Get storage resources
for resource_name, resource_id in \
_get_storage_resources(vnfc_resource):
resource_details = {"resource_name": resource_name,
"physical_resource_id": resource_id}
vdu_resources.append(resource_details)
return vdu_resources
def _prepare_stack_resources_for_updation(vdu_resources,
stack_resources):
for resource in vdu_resources:
for stack_uuid, resources in stack_resources.items():
res_details = resources.get(resource['resource_name'])
if (res_details and res_details['physical_resource_id'] ==
resource['physical_resource_id']):
yield stack_uuid, resource['resource_name']
def _resource_mark_unhealthy():
vnfc_resources = self._get_vnfc_resources_from_heal_request(
inst_vnf_info, heal_vnf_request)
vdu_resources = _get_vdu_resources(vnfc_resources)
stack_resources = self._get_stack_resources(
inst_vnf_info.instance_id, heatclient)
cause = heal_vnf_request.cause or "Healing"
for stack_uuid, resource_name in \
_prepare_stack_resources_for_updation(
vdu_resources, stack_resources):
try:
LOG.info("Marking resource %(resource)s as unhealthy for "
"stack %(stack)s for vnf instance %(id)s",
{"resource": resource_name,
"stack": stack_uuid,
"id": vnf_instance.id})
heatclient.resource_mark_unhealthy(
stack_id=stack_uuid,
resource_name=resource_name, mark_unhealthy=True,
resource_status_reason=cause)
except Exception as exp:
msg = ("Failed to mark stack '%(stack_id)s' resource as "
"unhealthy for resource '%(resource)s', "
"Error: %(error)s")
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=msg % {"stack_id": inst_vnf_info.instance_id,
"resource": resource_name,
"error": str(exp)})
def _get_stack_status():
stack_statuses = ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
stack = heatclient.get(inst_vnf_info.instance_id)
if stack.stack_status not in stack_statuses:
error = ("Healing of vnf instance %(id)s is possible only "
"when stack %(stack_id)s status is %(statuses)s, "
"current stack status is %(status)s")
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=error % {"id": vnf_instance.id,
"stack_id": inst_vnf_info.instance_id,
"statuses": ",".join(stack_statuses),
"status": stack.stack_status})
_get_stack_status()
_resource_mark_unhealthy()
LOG.info("Updating stack %(stack)s for vnf instance %(id)s",
{"stack": inst_vnf_info.instance_id, "id": vnf_instance.id})
heatclient.update(stack_id=inst_vnf_info.instance_id, existing=True)
@log.log
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
"""Check vnf is healed successfully"""
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
inst_vnf_info = vnf_instance.instantiated_vnf_info
stack = self._wait_until_stack_ready(inst_vnf_info.instance_id,
access_info, infra_cnst.STACK_UPDATE_IN_PROGRESS,
infra_cnst.STACK_UPDATE_COMPLETE, vnfm.VNFHealWaitFailed,
region_name=region_name)
return stack
def post_heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
"""Update resource_id for each vnfc resources
:param context: A RequestContext
:param vnf_instance: tacker.objects.VnfInstance to be healed
:vim_info: Credentials to initialize Vim connection
:heal_vnf_request: tacker.objects.HealVnfRequest object containing
parameters passed in the heal request
"""
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
heatclient = hc.HeatClient(access_info, region_name)
inst_vnf_info = vnf_instance.instantiated_vnf_info
stack_resources = self._get_stack_resources(inst_vnf_info.instance_id,
heatclient)
vnfc_resources = self._get_vnfc_resources_from_heal_request(
inst_vnf_info, heal_vnf_request)
for vnfc_res_info in vnfc_resources:
stack_id = vnfc_res_info.metadata.get("stack_id")
resources = stack_resources.get(stack_id)
if not resources:
# NOTE(tpatil): This could happen when heat child stacks
# and the stack_id stored in metadata of vnfc_res_info are
# not in sync. There is no point in syncing inconsistent
# resources information so exit with an error,
error = "Failed to find stack_id %s" % stack_id
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=error)
self._update_vnfc_resource_info(vnf_instance, vnfc_res_info,
{stack_id: resources}, update_network_resource=False)