754 lines
32 KiB
Python
754 lines
32 KiB
Python
# Copyright 2016 Brocade Communications System, Inc.
|
|
# 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 os
|
|
import time
|
|
import yaml
|
|
|
|
from cryptography import fernet
|
|
import eventlet
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import strutils
|
|
from oslo_utils import uuidutils
|
|
from tempfile import mkstemp
|
|
from toscaparser.tosca_template import ToscaTemplate
|
|
|
|
from apmec._i18n import _
|
|
from apmec.common import driver_manager
|
|
from apmec.common import log
|
|
from apmec.common import utils
|
|
from apmec.db.meo import meca_db
|
|
from apmec.db.meo import meo_db_plugin
|
|
from apmec.extensions import common_services as cs
|
|
from apmec.extensions import meo
|
|
from apmec.keymgr import API as KEYMGR_API
|
|
from apmec import manager
|
|
from apmec.mem import vim_client
|
|
from apmec.meo.workflows.vim_monitor import vim_monitor_utils
|
|
from apmec.plugins.common import constants
|
|
|
|
from apmec.catalogs.tosca import utils as toscautils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
MISTRAL_RETRIES = 30
|
|
MISTRAL_RETRY_WAIT = 6
|
|
|
|
|
|
def config_opts():
|
|
return [('meo_vim', MeoPlugin.OPTS)]
|
|
|
|
|
|
class MeoPlugin(meo_db_plugin.MeoPluginDb, meca_db.MECAPluginDb):
|
|
"""MEO reference plugin for MEO extension
|
|
|
|
Implements the MEO extension and defines public facing APIs for VIM
|
|
operations. MEO internally invokes the appropriate VIM driver in
|
|
backend based on configured VIM types. Plugin also interacts with MEM
|
|
extension for providing the specified VIM information
|
|
"""
|
|
supported_extension_aliases = ['meo']
|
|
|
|
OPTS = [
|
|
cfg.ListOpt(
|
|
'vim_drivers', default=['openstack'],
|
|
help=_('VIM driver for launching MEAs')),
|
|
cfg.IntOpt(
|
|
'monitor_interval', default=30,
|
|
help=_('Interval to check for VIM health')),
|
|
]
|
|
cfg.CONF.register_opts(OPTS, 'meo_vim')
|
|
|
|
def __init__(self):
|
|
super(MeoPlugin, self).__init__()
|
|
self._pool = eventlet.GreenPool()
|
|
self._vim_drivers = driver_manager.DriverManager(
|
|
'apmec.meo.vim.drivers',
|
|
cfg.CONF.meo_vim.vim_drivers)
|
|
self.vim_client = vim_client.VimClient()
|
|
|
|
def get_auth_dict(self, context):
|
|
auth = CONF.keystone_authtoken
|
|
return {
|
|
'auth_url': auth.auth_url + '/v3',
|
|
'token': context.auth_token,
|
|
'project_domain_name': auth.project_domain_name or context.domain,
|
|
'project_name': context.tenant_name
|
|
}
|
|
|
|
def spawn_n(self, function, *args, **kwargs):
|
|
self._pool.spawn_n(function, *args, **kwargs)
|
|
|
|
@log.log
|
|
def create_vim(self, context, vim):
|
|
LOG.debug('Create vim called with parameters %s',
|
|
strutils.mask_password(vim))
|
|
vim_obj = vim['vim']
|
|
vim_type = vim_obj['type']
|
|
vim_obj['id'] = uuidutils.generate_uuid()
|
|
vim_obj['status'] = 'PENDING'
|
|
try:
|
|
self._vim_drivers.invoke(vim_type,
|
|
'register_vim',
|
|
context=context,
|
|
vim_obj=vim_obj)
|
|
res = super(MeoPlugin, self).create_vim(context, vim_obj)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self._vim_drivers.invoke(vim_type,
|
|
'delete_vim_auth',
|
|
context=context,
|
|
vim_id=vim_obj['id'],
|
|
auth=vim_obj['auth_cred'])
|
|
|
|
try:
|
|
self.monitor_vim(context, vim_obj)
|
|
except Exception:
|
|
LOG.warning("Failed to set up vim monitoring")
|
|
return res
|
|
|
|
def _get_vim(self, context, vim_id):
|
|
if not self.is_vim_still_in_use(context, vim_id):
|
|
return self.get_vim(context, vim_id, mask_password=False)
|
|
|
|
@log.log
|
|
def update_vim(self, context, vim_id, vim):
|
|
vim_obj = self._get_vim(context, vim_id)
|
|
old_vim_obj = copy.deepcopy(vim_obj)
|
|
utils.deep_update(vim_obj, vim['vim'])
|
|
vim_type = vim_obj['type']
|
|
update_args = vim['vim']
|
|
old_auth_need_delete = False
|
|
new_auth_created = False
|
|
try:
|
|
# re-register the VIM only if there is a change in password.
|
|
# auth_url of auth_cred is from vim object which
|
|
# is not updatable. so no need to consider it
|
|
if 'auth_cred' in update_args:
|
|
auth_cred = update_args['auth_cred']
|
|
if 'password' in auth_cred:
|
|
vim_obj['auth_cred']['password'] = auth_cred['password']
|
|
# Notice: vim_obj may be updated in vim driver's
|
|
self._vim_drivers.invoke(vim_type,
|
|
'register_vim',
|
|
context=context,
|
|
vim_obj=vim_obj)
|
|
new_auth_created = True
|
|
|
|
# Check whether old vim's auth need to be deleted
|
|
old_key_type = old_vim_obj['auth_cred'].get('key_type')
|
|
if old_key_type == 'barbican_key':
|
|
old_auth_need_delete = True
|
|
|
|
vim_obj = super(MeoPlugin, self).update_vim(
|
|
context, vim_id, vim_obj)
|
|
if old_auth_need_delete:
|
|
try:
|
|
self._vim_drivers.invoke(vim_type,
|
|
'delete_vim_auth',
|
|
context=context,
|
|
vim_id=old_vim_obj['id'],
|
|
auth=old_vim_obj['auth_cred'])
|
|
except Exception as ex:
|
|
LOG.warning("Fail to delete old auth for vim %s due to %s",
|
|
vim_id, ex)
|
|
return vim_obj
|
|
except Exception as ex:
|
|
LOG.debug("Got exception when update_vim %s due to %s",
|
|
vim_id, ex)
|
|
with excutils.save_and_reraise_exception():
|
|
if new_auth_created:
|
|
# delete new-created vim auth, old auth is still used.
|
|
self._vim_drivers.invoke(vim_type,
|
|
'delete_vim_auth',
|
|
context=context,
|
|
vim_id=vim_obj['id'],
|
|
auth=vim_obj['auth_cred'])
|
|
|
|
@log.log
|
|
def delete_vim(self, context, vim_id):
|
|
vim_obj = self._get_vim(context, vim_id)
|
|
self._vim_drivers.invoke(vim_obj['type'],
|
|
'deregister_vim',
|
|
context=context,
|
|
vim_obj=vim_obj)
|
|
try:
|
|
auth_dict = self.get_auth_dict(context)
|
|
vim_monitor_utils.delete_vim_monitor(context, auth_dict, vim_obj)
|
|
except Exception:
|
|
LOG.exception("Failed to remove vim monitor")
|
|
super(MeoPlugin, self).delete_vim(context, vim_id)
|
|
|
|
@log.log
|
|
def monitor_vim(self, context, vim_obj):
|
|
auth_dict = self.get_auth_dict(context)
|
|
vim_monitor_utils.monitor_vim(auth_dict, vim_obj)
|
|
|
|
def _get_vim_from_mea(self, context, mea_id):
|
|
"""Figures out VIM based on a MEA
|
|
|
|
:param context: SQL Session Context
|
|
:param mea_id: MEA ID
|
|
:return: VIM or VIM properties if fields are provided
|
|
"""
|
|
mem_plugin = manager.ApmecManager.get_service_plugins()['MEM']
|
|
vim_id = mem_plugin.get_mea(context, mea_id, fields=['vim_id'])
|
|
vim_obj = self.get_vim(context, vim_id['vim_id'], mask_password=False)
|
|
if vim_obj is None:
|
|
raise meo.VimFromMeaNotFoundException(mea_id=mea_id)
|
|
self._build_vim_auth(context, vim_obj)
|
|
return vim_obj
|
|
|
|
def _build_vim_auth(self, context, vim_info):
|
|
LOG.debug('VIM id is %s', vim_info['id'])
|
|
vim_auth = vim_info['auth_cred']
|
|
vim_auth['password'] = self._decode_vim_auth(context,
|
|
vim_info['id'],
|
|
vim_auth)
|
|
vim_auth['auth_url'] = vim_info['auth_url']
|
|
|
|
# These attributes are needless for authentication
|
|
# from keystone, so we remove them.
|
|
needless_attrs = ['key_type', 'secret_uuid']
|
|
for attr in needless_attrs:
|
|
if attr in vim_auth:
|
|
vim_auth.pop(attr, None)
|
|
return vim_auth
|
|
|
|
def _decode_vim_auth(self, context, vim_id, auth):
|
|
"""Decode Vim credentials
|
|
|
|
Decrypt VIM cred, get fernet Key from local_file_system or
|
|
barbican.
|
|
"""
|
|
cred = auth['password'].encode('utf-8')
|
|
if auth.get('key_type') == 'barbican_key':
|
|
keystone_conf = CONF.keystone_authtoken
|
|
secret_uuid = auth['secret_uuid']
|
|
keymgr_api = KEYMGR_API(keystone_conf.auth_url)
|
|
secret_obj = keymgr_api.get(context, secret_uuid)
|
|
vim_key = secret_obj.payload
|
|
else:
|
|
vim_key = self._find_vim_key(vim_id)
|
|
|
|
f = fernet.Fernet(vim_key)
|
|
if not f:
|
|
LOG.warning('Unable to decode VIM auth')
|
|
raise meo.VimNotFoundException(
|
|
'Unable to decode VIM auth key')
|
|
return f.decrypt(cred)
|
|
|
|
@staticmethod
|
|
def _find_vim_key(vim_id):
|
|
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
|
|
LOG.debug('Attempting to open key file for vim id %s', vim_id)
|
|
with open(key_file, 'r') as f:
|
|
return f.read()
|
|
LOG.warning('VIM id invalid or key not found for %s', vim_id)
|
|
|
|
def _vim_resource_name_to_id(self, context, resource, name, mea_id):
|
|
"""Converts a VIM resource name to its ID
|
|
|
|
:param resource: resource type to find (network, subnet, etc)
|
|
:param name: name of the resource to find its ID
|
|
:param mea_id: A MEA instance ID that is part of the chain to which
|
|
the classifier will apply to
|
|
:return: ID of the resource name
|
|
"""
|
|
vim_obj = self._get_vim_from_mea(context, mea_id)
|
|
driver_type = vim_obj['type']
|
|
return self._vim_drivers.invoke(driver_type,
|
|
'get_vim_resource_id',
|
|
vim_obj=vim_obj,
|
|
resource_type=resource,
|
|
resource_name=name)
|
|
|
|
@log.log
|
|
def create_mecad(self, context, mecad):
|
|
mecad_data = mecad['mecad']
|
|
template = mecad_data['attributes'].get('mecad')
|
|
if isinstance(template, dict):
|
|
mecad_data['attributes']['mecad'] = yaml.safe_dump(
|
|
template)
|
|
LOG.debug('mecad %s', mecad_data)
|
|
|
|
if 'template_source' in mecad_data:
|
|
template_source = mecad_data.get('template_source')
|
|
else:
|
|
template_source = "onboarded"
|
|
mecad['mecad']['template_source'] = template_source
|
|
|
|
self._parse_template_input(context, mecad)
|
|
return super(MeoPlugin, self).create_mecad(
|
|
context, mecad)
|
|
|
|
def _parse_template_input(self, context, mecad):
|
|
mecad_dict = mecad['mecad']
|
|
mecad_yaml = mecad_dict['attributes'].get('mecad')
|
|
inner_mecad_dict = yaml.safe_load(mecad_yaml)
|
|
mecad['meads'] = dict()
|
|
LOG.debug('mecad_dict: %s', inner_mecad_dict)
|
|
# From import we can deploy both NS and MEC Application
|
|
|
|
# Deploy MEC applications
|
|
mem_plugin = manager.ApmecManager.get_service_plugins()['MEM']
|
|
mead_imports = inner_mecad_dict['imports']
|
|
inner_mecad_dict['imports'] = []
|
|
new_files = []
|
|
for mead_name in mead_imports:
|
|
mead = mem_plugin.get_mead(context, mead_name)
|
|
# Copy MEA types and MEA names
|
|
sm_dict = yaml.safe_load(mead['attributes']['mead'])[
|
|
'topology_template'][
|
|
'substitution_mappings']
|
|
mecad['meads'][sm_dict['node_type']] = mead['name']
|
|
# Ugly Hack to validate the child templates
|
|
# TODO(tbh): add support in tosca-parser to pass child
|
|
# templates as dict
|
|
fd, temp_path = mkstemp()
|
|
with open(temp_path, 'w') as fp:
|
|
fp.write(mead['attributes']['mead'])
|
|
os.close(fd)
|
|
new_files.append(temp_path)
|
|
inner_mecad_dict['imports'].append(temp_path)
|
|
# Prepend the apmec_defs.yaml import file with the full
|
|
# path to the file
|
|
toscautils.updateimports(inner_mecad_dict)
|
|
|
|
try:
|
|
ToscaTemplate(a_file=False,
|
|
yaml_dict_tpl=inner_mecad_dict)
|
|
except Exception as e:
|
|
LOG.exception("tosca-parser error: %s", str(e))
|
|
raise meo.ToscaParserFailed(error_msg_details=str(e))
|
|
finally:
|
|
for file_path in new_files:
|
|
os.remove(file_path)
|
|
inner_mecad_dict['imports'] = mead_imports
|
|
|
|
if ('description' not in mecad_dict or
|
|
mecad_dict['description'] == ''):
|
|
mecad_dict['description'] = inner_mecad_dict.get(
|
|
'description', '')
|
|
if (('name' not in mecad_dict or
|
|
not len(mecad_dict['name'])) and
|
|
'metadata' in inner_mecad_dict):
|
|
mecad_dict['name'] = inner_mecad_dict['metadata'].get(
|
|
'template_name', '')
|
|
|
|
LOG.debug('mecad %s', mecad)
|
|
|
|
def _get_mead_id(self, mead_name, onboarded_meads):
|
|
for mead in onboarded_meads:
|
|
if mead_name == mead['name']:
|
|
return mead['id']
|
|
|
|
@log.log
|
|
def create_meca(self, context, meca):
|
|
"""Create MECA and corresponding MEAs.
|
|
|
|
:param meca: meca dict which contains mecad_id and attributes
|
|
This method has 3 steps:
|
|
step-1: substitute all get_input params to its corresponding values
|
|
step-2: Build params dict for substitution mappings case through which
|
|
MEAs will actually substitute their requirements.
|
|
step-3: Create mistral workflow and execute the workflow
|
|
"""
|
|
meca_info = meca['meca']
|
|
name = meca_info['name']
|
|
|
|
if meca_info.get('mecad_template'):
|
|
mecad_name = utils.generate_resource_name(name, 'inline')
|
|
mecad = {'mecad': {
|
|
'attributes': {'mecad': meca_info['mecad_template']},
|
|
'description': meca_info['description'],
|
|
'name': mecad_name,
|
|
'template_source': 'inline',
|
|
'tenant_id': meca_info['tenant_id']}}
|
|
meca_info['mecad_id'] = self.create_mecad(context, mecad).get('id')
|
|
|
|
mecad = self.get_mecad(context, meca['meca']['mecad_id'])
|
|
mecad_dict = yaml.safe_load(mecad['attributes']['mecad'])
|
|
mem_plugin = manager.ApmecManager.get_service_plugins()['MEM']
|
|
onboarded_meads = mem_plugin.get_meads(context, [])
|
|
region_name = meca.setdefault('placement_attr', {}).get(
|
|
'region_name', None)
|
|
vim_res = self.vim_client.get_vim(context, meca['meca']['vim_id'],
|
|
region_name)
|
|
driver_type = vim_res['vim_type']
|
|
if not meca['meca'].get('vim_id'):
|
|
meca['meca']['vim_id'] = vim_res['vim_id']
|
|
|
|
# Step-1
|
|
param_values = meca['meca']['attributes'].get('param_values', {})
|
|
if 'get_input' in str(mecad_dict):
|
|
self._process_parameterized_input(meca['meca']['attributes'],
|
|
mecad_dict)
|
|
# Step-2
|
|
meads = mecad['meads']
|
|
# mead_dict is used while generating workflow
|
|
mead_dict = dict()
|
|
for node_name, node_val in \
|
|
(mecad_dict['topology_template']['node_templates']).items():
|
|
if node_val.get('type') not in meads.keys():
|
|
continue
|
|
mead_name = meads[node_val.get('type')]
|
|
if not mead_dict.get(mead_name):
|
|
mead_dict[mead_name] = {
|
|
'id': self._get_mead_id(mead_name, onboarded_meads),
|
|
'instances': [node_name]
|
|
}
|
|
else:
|
|
mead_dict[mead_name]['instances'].append(node_name)
|
|
if not node_val.get('requirements'):
|
|
continue
|
|
if not param_values.get(mead_name):
|
|
param_values[mead_name] = {}
|
|
param_values[mead_name]['substitution_mappings'] = dict()
|
|
req_dict = dict()
|
|
requirements = node_val.get('requirements')
|
|
for requirement in requirements:
|
|
req_name = list(requirement.keys())[0]
|
|
req_val = list(requirement.values())[0]
|
|
res_name = req_val + meca['meca']['mecad_id'][:11]
|
|
req_dict[req_name] = res_name
|
|
if req_val in mecad_dict['topology_template']['node_templates']: # noqa
|
|
param_values[mead_name]['substitution_mappings'][
|
|
res_name] = mecad_dict['topology_template'][
|
|
'node_templates'][req_val]
|
|
|
|
param_values[mead_name]['substitution_mappings'][
|
|
'requirements'] = req_dict
|
|
meca['mead_details'] = mead_dict
|
|
# Step-3
|
|
kwargs = {'meca': meca, 'params': param_values}
|
|
|
|
# NOTE NoTasksException is raised if no tasks.
|
|
workflow = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'prepare_and_create_workflow',
|
|
resource='mea',
|
|
action='create',
|
|
auth_dict=self.get_auth_dict(context),
|
|
kwargs=kwargs)
|
|
try:
|
|
mistral_execution = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'execute_workflow',
|
|
workflow=workflow,
|
|
auth_dict=self.get_auth_dict(context))
|
|
except Exception as ex:
|
|
LOG.error('Error while executing workflow: %s', ex)
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_workflow',
|
|
workflow_id=workflow['id'],
|
|
auth_dict=self.get_auth_dict(context))
|
|
raise ex
|
|
meca_dict = super(MeoPlugin, self).create_meca(context, meca)
|
|
|
|
def _create_meca_wait(self_obj, meca_id, execution_id):
|
|
exec_state = "RUNNING"
|
|
mistral_retries = MISTRAL_RETRIES
|
|
while exec_state == "RUNNING" and mistral_retries > 0:
|
|
time.sleep(MISTRAL_RETRY_WAIT)
|
|
exec_state = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'get_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context)).state
|
|
LOG.debug('status: %s', exec_state)
|
|
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
|
|
break
|
|
mistral_retries = mistral_retries - 1
|
|
error_reason = None
|
|
if mistral_retries == 0 and exec_state == 'RUNNING':
|
|
error_reason = _(
|
|
"MECA creation is not completed within"
|
|
" {wait} seconds as creation of mistral"
|
|
" execution {mistral} is not completed").format(
|
|
wait=MISTRAL_RETRIES * MISTRAL_RETRY_WAIT,
|
|
mistral=execution_id)
|
|
exec_obj = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'get_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context))
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context))
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_workflow',
|
|
workflow_id=workflow['id'],
|
|
auth_dict=self.get_auth_dict(context))
|
|
super(MeoPlugin, self).create_meca_post(context, meca_id, exec_obj,
|
|
mead_dict, error_reason)
|
|
|
|
self.spawn_n(_create_meca_wait, self, meca_dict['id'],
|
|
mistral_execution.id)
|
|
return meca_dict
|
|
|
|
@log.log
|
|
def _update_params(self, original, paramvalues):
|
|
for key, value in (original).items():
|
|
if not isinstance(value, dict) or 'get_input' not in str(value):
|
|
pass
|
|
elif isinstance(value, dict):
|
|
if '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 cs.InputValuesMissing(key=key)
|
|
else:
|
|
self._update_params(value, paramvalues)
|
|
|
|
@log.log
|
|
def _process_parameterized_input(self, attrs, mecad_dict):
|
|
param_vattrs_dict = attrs.pop('param_values', None)
|
|
if param_vattrs_dict:
|
|
for node in \
|
|
mecad_dict['topology_template']['node_templates'].values():
|
|
if 'get_input' in str(node):
|
|
self._update_params(node, param_vattrs_dict['mecad'])
|
|
else:
|
|
raise cs.ParamYAMLInputMissing()
|
|
|
|
@log.log
|
|
def delete_meca(self, context, meca_id):
|
|
meca = super(MeoPlugin, self).get_meca(context, meca_id)
|
|
vim_res = self.vim_client.get_vim(context, meca['vim_id'])
|
|
driver_type = vim_res['vim_type']
|
|
workflow = None
|
|
try:
|
|
workflow = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'prepare_and_create_workflow',
|
|
resource='mea',
|
|
action='delete',
|
|
auth_dict=self.get_auth_dict(context),
|
|
kwargs={
|
|
'meca': meca})
|
|
except meo.NoTasksException:
|
|
LOG.warning("No MEA deletion task(s).")
|
|
if workflow:
|
|
try:
|
|
mistral_execution = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'execute_workflow',
|
|
workflow=workflow,
|
|
auth_dict=self.get_auth_dict(context))
|
|
|
|
except Exception as ex:
|
|
LOG.error('Error while executing workflow: %s', ex)
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_workflow',
|
|
workflow_id=workflow['id'],
|
|
auth_dict=self.get_auth_dict(context))
|
|
|
|
raise ex
|
|
super(MeoPlugin, self).delete_meca(context, meca_id)
|
|
|
|
def _delete_meca_wait(meca_id, execution_id):
|
|
exec_state = "RUNNING"
|
|
mistral_retries = MISTRAL_RETRIES
|
|
while exec_state == "RUNNING" and mistral_retries > 0:
|
|
time.sleep(MISTRAL_RETRY_WAIT)
|
|
exec_state = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'get_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context)).state
|
|
LOG.debug('status: %s', exec_state)
|
|
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
|
|
break
|
|
mistral_retries -= 1
|
|
error_reason = None
|
|
if mistral_retries == 0 and exec_state == 'RUNNING':
|
|
error_reason = _(
|
|
"MECA deletion is not completed within"
|
|
" {wait} seconds as deletion of mistral"
|
|
" execution {mistral} is not completed").format(
|
|
wait=MISTRAL_RETRIES * MISTRAL_RETRY_WAIT,
|
|
mistral=execution_id)
|
|
exec_obj = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'get_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context))
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context))
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_workflow',
|
|
workflow_id=workflow['id'],
|
|
auth_dict=self.get_auth_dict(context))
|
|
super(MeoPlugin, self).delete_meca_post(context, meca_id, exec_obj,
|
|
error_reason)
|
|
if workflow:
|
|
self.spawn_n(_delete_meca_wait, meca['id'], mistral_execution.id)
|
|
else:
|
|
super(MeoPlugin, self).delete_meca_post(
|
|
context, meca_id, None, None)
|
|
return meca['id']
|
|
|
|
@log.log
|
|
def update_meca(self, context, meca_id, meca):
|
|
meca_info = meca['meca']
|
|
meca_old = super(MeoPlugin, self).get_meca(context, meca_id)
|
|
name = meca_old['name']
|
|
# create inline meafgd if given by user
|
|
if meca_info.get('mecad_template'):
|
|
meca_name = utils.generate_resource_name(name, 'inline')
|
|
mecad = {'mecad': {'tenant_id': meca_old['tenant_id'],
|
|
'name': meca_name,
|
|
'attributes': {
|
|
'mecad': meca_info['mecad_template']},
|
|
'template_source': 'inline',
|
|
'description': meca_old['description']}}
|
|
try:
|
|
meca_info['mecad_id'] = \
|
|
self.create_mecad(context, mecad).get('id')
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
super(MeoPlugin, self)._update_meca_status(
|
|
context, meca_id, constants.ACTIVE)
|
|
|
|
mecad = self.get_mecad(context, meca_info['mecad_id'])
|
|
mecad_dict = yaml.safe_load(mecad['attributes']['mecad'])
|
|
mem_plugin = manager.ApmecManager.get_service_plugins()['MEM']
|
|
onboarded_meads = mem_plugin.get_meads(context, [])
|
|
region_name = meca.setdefault('placement_attr', {}).get(
|
|
'region_name', None)
|
|
vim_res = self.vim_client.get_vim(context, meca_old['vim_id'],
|
|
region_name)
|
|
driver_type = vim_res['vim_type']
|
|
|
|
# Step-1
|
|
param_values = dict()
|
|
if 'get_input' in str(mecad_dict):
|
|
self._process_parameterized_input(meca['meca']['attributes'],
|
|
mecad_dict)
|
|
|
|
# Step-2
|
|
meads = mecad['meads']
|
|
# mead_dict is used while generating workflow
|
|
mead_dict = dict()
|
|
for node_name, node_val in \
|
|
(mecad_dict['topology_template']['node_templates']).items():
|
|
if node_val.get('type') not in meads.keys():
|
|
continue
|
|
mead_name = meads[node_val.get('type')]
|
|
if not mead_dict.get(mead_name):
|
|
mead_dict[mead_name] = {
|
|
'id': self._get_mead_id(mead_name, onboarded_meads),
|
|
'instances': [node_name]
|
|
}
|
|
else:
|
|
mead_dict[mead_name]['instances'].append(node_name)
|
|
if not node_val.get('requirements'):
|
|
continue
|
|
if not param_values.get(mead_name):
|
|
param_values[mead_name] = {}
|
|
param_values[mead_name]['substitution_mappings'] = dict()
|
|
req_dict = dict()
|
|
requirements = node_val.get('requirements')
|
|
for requirement in requirements:
|
|
req_name = list(requirement.keys())[0]
|
|
req_val = list(requirement.values())[0]
|
|
res_name = req_val + meca['meca']['mecad_id'][:11]
|
|
req_dict[req_name] = res_name
|
|
if req_val in mecad_dict['topology_template']['node_templates']: # noqa
|
|
param_values[mead_name]['substitution_mappings'][
|
|
res_name] = mecad_dict['topology_template'][
|
|
'node_templates'][req_val]
|
|
|
|
param_values[mead_name]['substitution_mappings'][
|
|
'requirements'] = req_dict
|
|
meca['mead_details'] = mead_dict
|
|
# Step-3
|
|
kwargs = {'meca': meca, 'params': param_values}
|
|
|
|
# NOTE NoTasksException is raised if no tasks.
|
|
workflow = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'prepare_and_create_workflow',
|
|
resource='mea',
|
|
action='create',
|
|
auth_dict=self.get_auth_dict(context),
|
|
kwargs=kwargs)
|
|
try:
|
|
mistral_execution = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'execute_workflow',
|
|
workflow=workflow,
|
|
auth_dict=self.get_auth_dict(context))
|
|
except Exception as ex:
|
|
LOG.error('Error while executing workflow: %s', ex)
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_workflow',
|
|
workflow_id=workflow['id'],
|
|
auth_dict=self.get_auth_dict(context))
|
|
raise ex
|
|
meca_dict = super(MeoPlugin, self)._update_meca_pre(context, meca_id)
|
|
|
|
def _update_meca_wait(self_obj, meca_id, execution_id):
|
|
exec_state = "RUNNING"
|
|
mistral_retries = MISTRAL_RETRIES
|
|
while exec_state == "RUNNING" and mistral_retries > 0:
|
|
time.sleep(MISTRAL_RETRY_WAIT)
|
|
exec_state = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'get_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context)).state
|
|
LOG.debug('status: %s', exec_state)
|
|
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
|
|
break
|
|
mistral_retries = mistral_retries - 1
|
|
error_reason = None
|
|
if mistral_retries == 0 and exec_state == 'RUNNING':
|
|
error_reason = _(
|
|
"MECA update is not completed within"
|
|
" {wait} seconds as creation of mistral"
|
|
" execution {mistral} is not completed").format(
|
|
wait=MISTRAL_RETRIES * MISTRAL_RETRY_WAIT,
|
|
mistral=execution_id)
|
|
exec_obj = self._vim_drivers.invoke(
|
|
driver_type,
|
|
'get_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context))
|
|
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_execution',
|
|
execution_id=execution_id,
|
|
auth_dict=self.get_auth_dict(context))
|
|
self._vim_drivers.invoke(driver_type,
|
|
'delete_workflow',
|
|
workflow_id=workflow['id'],
|
|
auth_dict=self.get_auth_dict(context))
|
|
super(MeoPlugin, self)._update_meca_post(
|
|
context, meca_id, exec_obj, mead_dict, error_reason)
|
|
|
|
self.spawn_n(_update_meca_wait, self, meca_dict['id'],
|
|
mistral_execution.id)
|
|
return meca_dict
|