Add MESO component that allows:

- Apmec to collaborate with other orchestration frameworks (e.g, NFV framework)

- for oprators to excecute smart placement decisions of MEC applications

Change-Id: I28fe23029f11632f1909967238b9fd440aa6f936
This commit is contained in:
Tung Doan 2018-08-31 06:42:54 -07:00
parent c41e2d080d
commit bff90fa0b1
12 changed files with 1949 additions and 62 deletions

356
apmec/db/meso/meso_db.py Normal file
View File

@ -0,0 +1,356 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from datetime import datetime
from oslo_db.exception import DBDuplicateEntry
from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy import schema
from apmec.common import exceptions
from apmec.db.common_services import common_services_db_plugin
from apmec.db import db_base
from apmec.db import model_base
from apmec.db import models_v1
from apmec.db import types
from apmec.extensions import meso
from apmec.plugins.common import constants
LOG = logging.getLogger(__name__)
_ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE)
_ACTIVE_UPDATE_ERROR_DEAD = (
constants.PENDING_CREATE, constants.ACTIVE, constants.PENDING_UPDATE,
constants.ERROR, constants.DEAD)
CREATE_STATES = (constants.PENDING_CREATE, constants.DEAD)
###########################################################################
# db tables
class MESD(model_base.BASE, models_v1.HasId, models_v1.HasTenant,
models_v1.Audit):
"""Represents MESD to create MES."""
__tablename__ = 'mesd'
# Descriptive name
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text)
# Mesd template source - onboarded
template_source = sa.Column(sa.String(255), server_default='onboarded')
mesd_mapping = sa.Column(types.Json, nullable=True)
# (key, value) pair to spin up
attributes = orm.relationship('MESDAttribute',
backref='mesd')
__table_args__ = (
schema.UniqueConstraint(
"tenant_id",
"name",
name="uniq_mesd0tenant_id0name"),
)
class MESDAttribute(model_base.BASE, models_v1.HasId):
"""Represents attributes necessary for creation of mes in (key, value) pair
"""
__tablename__ = 'mesd_attribute'
mesd_id = sa.Column(types.Uuid, sa.ForeignKey('mesd.id'),
nullable=False)
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.TEXT(65535), nullable=True)
class MES(model_base.BASE, models_v1.HasId, models_v1.HasTenant,
models_v1.Audit):
"""Represents network services that deploys services.
"""
__tablename__ = 'mes'
mesd_id = sa.Column(types.Uuid, sa.ForeignKey('mesd.id'))
mesd = orm.relationship('MESD')
mes_mapping = sa.Column(types.Json, nullable=True)
reused = sa.Column(types.Json, nullable=True)
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text, nullable=True)
# Dict of MEA details that network service launches
mea_ids = sa.Column(sa.TEXT(65535), nullable=True)
# Dict of mgmt urls that network servic launches
mgmt_urls = sa.Column(sa.TEXT(65535), nullable=True)
status = sa.Column(sa.String(64), nullable=False)
vim_id = sa.Column(types.Uuid, sa.ForeignKey('vims.id'), nullable=False)
error_reason = sa.Column(sa.Text, nullable=True)
__table_args__ = (
schema.UniqueConstraint(
"tenant_id",
"name",
name="uniq_mes0tenant_id0name"),
)
class MESOPluginDb(meso.MESOPluginBase, db_base.CommonDbMixin):
def __init__(self):
super(MESOPluginDb, self).__init__()
self._cos_db_plg = common_services_db_plugin.CommonServicesPluginDb()
def _get_resource(self, context, model, id):
try:
return self._get_by_id(context, model, id)
except orm_exc.NoResultFound:
if issubclass(model, MESD):
raise meso.MESDNotFound(mesd_id=id)
if issubclass(model, MES):
raise meso.MESNotFound(mes_id=id)
else:
raise
def _get_mes_db(self, context, mes_id, current_statuses, new_status):
try:
mes_db = (
self._model_query(context, MES).
filter(MES.id == mes_id).
filter(MES.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise meso.MESNotFound(mes_id=mes_id)
mes_db.update({'status': new_status})
return mes_db
def _make_attributes_dict(self, attributes_db):
return dict((attr.key, attr.value) for attr in attributes_db)
def _make_mesd_dict(self, mesd, fields=None):
res = {
'attributes': self._make_attributes_dict(mesd['attributes']),
}
key_list = ('id', 'tenant_id', 'name', 'description', 'mesd_mapping',
'created_at', 'updated_at', 'template_source')
res.update((key, mesd[key]) for key in key_list)
return self._fields(res, fields)
def _make_dev_attrs_dict(self, dev_attrs_db):
return dict((arg.key, arg.value) for arg in dev_attrs_db)
def _make_mes_dict(self, mes_db, fields=None):
LOG.debug('mes_db %s', mes_db)
res = {}
key_list = ('id', 'tenant_id', 'mesd_id', 'name', 'description', 'mes_mapping', # noqa
'mea_ids', 'status', 'mgmt_urls', 'error_reason', 'reused',
'vim_id', 'created_at', 'updated_at')
res.update((key, mes_db[key]) for key in key_list)
return self._fields(res, fields)
def create_mesd(self, context, mesd):
mesd = mesd['mesd']
LOG.debug('mesd %s', mesd)
tenant_id = self._get_tenant_id_for_create(context, mesd)
template_source = mesd.get('template_source')
try:
with context.session.begin(subtransactions=True):
mesd_id = uuidutils.generate_uuid()
mesd_db = MESD(
id=mesd_id,
tenant_id=tenant_id,
name=mesd.get('name'),
description=mesd.get('description'),
mesd_mapping=mesd.get('mesd_mapping'),
deleted_at=datetime.min,
template_source=template_source)
context.session.add(mesd_db)
for (key, value) in mesd.get('attributes', {}).items():
attribute_db = MESDAttribute(
id=uuidutils.generate_uuid(),
mesd_id=mesd_id,
key=key,
value=value)
context.session.add(attribute_db)
except DBDuplicateEntry as e:
raise exceptions.DuplicateEntity(
_type="mesd",
entry=e.columns)
LOG.debug('mesd_db %(mesd_db)s %(attributes)s ',
{'mesd_db': mesd_db,
'attributes': mesd_db.attributes})
mesd_dict = self._make_mesd_dict(mesd_db)
LOG.debug('mesd_dict %s', mesd_dict)
self._cos_db_plg.create_event(
context, res_id=mesd_dict['id'],
res_type=constants.RES_TYPE_MESD,
res_state=constants.RES_EVT_ONBOARDED,
evt_type=constants.RES_EVT_CREATE,
tstamp=mesd_dict[constants.RES_EVT_CREATED_FLD])
return mesd_dict
def delete_mesd(self,
context,
mesd_id,
soft_delete=True):
with context.session.begin(subtransactions=True):
mess_db = context.session.query(MES).filter_by(
mesd_id=mesd_id).first()
if mess_db is not None and mess_db.deleted_at is None:
raise meso.MESDInUse(mesd_id=mesd_id)
mesd_db = self._get_resource(context, MESD,
mesd_id)
if soft_delete:
mesd_db.update({'deleted_at': timeutils.utcnow()})
self._cos_db_plg.create_event(
context, res_id=mesd_db['id'],
res_type=constants.RES_TYPE_MESD,
res_state=constants.RES_EVT_NA_STATE,
evt_type=constants.RES_EVT_DELETE,
tstamp=mesd_db[constants.RES_EVT_DELETED_FLD])
else:
context.session.query(MESDAttribute).filter_by(
mesd_id=mesd_id).delete()
context.session.delete(mesd_db)
def get_mesd(self, context, mesd_id, fields=None):
mesd_db = self._get_resource(context, MESD, mesd_id)
return self._make_mesd_dict(mesd_db)
def get_mesds(self, context, filters, fields=None):
if ('template_source' in filters) and \
(filters['template_source'][0] == 'all'):
filters.pop('template_source')
return self._get_collection(context, MESD,
self._make_mesd_dict,
filters=filters, fields=fields)
# reference implementation. needs to be overrided by subclass
def create_mes(self, context, mes):
LOG.debug('mes %s', mes)
mes = mes['mes']
tenant_id = self._get_tenant_id_for_create(context, mes)
mesd_id = mes['mesd_id']
vim_id = mes['vim_id']
name = mes.get('name')
mes_mapping = mes['mes_mapping']
mes_id = uuidutils.generate_uuid()
try:
with context.session.begin(subtransactions=True):
mesd_db = self._get_resource(context, MESD,
mesd_id)
mes_db = MES(id=mes_id,
tenant_id=tenant_id,
name=name,
description=mesd_db.description,
mea_ids=None,
status=constants.PENDING_CREATE,
mes_mapping=mes_mapping,
reused=None,
mgmt_urls=None,
mesd_id=mesd_id,
vim_id=vim_id,
error_reason=None,
deleted_at=datetime.min)
context.session.add(mes_db)
except DBDuplicateEntry as e:
raise exceptions.DuplicateEntity(
_type="mes",
entry=e.columns)
return self._make_mes_dict(mes_db)
def create_mes_post(self, context, mes_id,
mes_status, error_reason, args):
LOG.debug('mes ID %s', mes_id)
with context.session.begin(subtransactions=True):
mes_db = self._get_resource(context, MES,
mes_id)
mes_db.update({'status': mes_status})
mes_db.update({'error_reason': error_reason})
mes_db.update({'updated_at': timeutils.utcnow()})
mes_db.update({'reused': args.get("NS")})
mes_dict = self._make_mes_dict(mes_db)
return mes_dict
# reference implementation. needs to be overrided by subclass
def delete_mes(self, context, mes_id):
with context.session.begin(subtransactions=True):
mes_db = self._get_mes_db(
context, mes_id, _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
deleted_mes_db = self._make_mes_dict(mes_db)
return deleted_mes_db
def delete_mes_post(self, context, mes_id,
error_reason, soft_delete=True, error=False):
mes = self.get_mes(context, mes_id)
mesd_id = mes.get('mesd_id')
with context.session.begin(subtransactions=True):
query = (
self._model_query(context, MES).
filter(MES.id == mes_id).
filter(MES.status == constants.PENDING_DELETE))
if error:
query.update({'status': constants.ERROR})
if error_reason:
query.update({'error_reason': error_reason})
else:
if soft_delete:
deleted_time_stamp = timeutils.utcnow()
query.update({'deleted_at': deleted_time_stamp})
else:
query.delete()
template_db = self._get_resource(context, MESD, mesd_id)
if template_db.get('template_source') == 'inline':
self.delete_mesd(context, mesd_id)
def get_mes(self, context, mes_id, fields=None):
mes_db = self._get_resource(context, MES, mes_id)
return self._make_mes_dict(mes_db)
def get_mess(self, context, filters=None, fields=None):
return self._get_collection(context, MES,
self._make_mes_dict,
filters=filters, fields=fields)
def _update_mes_pre(self, context, mes_id):
with context.session.begin(subtransactions=True):
mes_db = self._get_mes_db(context, mes_id, _ACTIVE_UPDATE, constants.PENDING_UPDATE) # noqa
return self._make_mes_dict(mes_db)
def _update_mes_post(self, context, mes_id, error_reason, mes_status, args): # noqa
with context.session.begin(subtransactions=True):
mes_db = self._get_resource(context, MES, mes_id)
mes_db.update({'status': mes_status})
mes_db.update({'error_reason': error_reason})
mes_db.update({'updated_at': timeutils.utcnow()})
mes_db.update({'reused': args.get('NS')})
mes_dict = self._make_mes_dict(mes_db)
return mes_dict
def _update_mes_status(self, context, mes_id, new_status):
with context.session.begin(subtransactions=True):
mes_db = self._get_mes_db(context, mes_id, _ACTIVE_UPDATE, new_status) # noqa
return self._make_mes_dict(mes_db)
def update_mes(self, context, mes_id, mes):
self._update_mes_pre(context, mes_id)
self._update_mes_post(context, mes_id, constants.ACTIVE, None)

View File

@ -1,61 +0,0 @@
# 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 abc
import six
from apmec.common import exceptions
from apmec.services import service_base
@six.add_metaclass(abc.ABCMeta)
class MESPluginBase(service_base.MECPluginBase):
@abc.abstractmethod
def create_mesd(self, context, mesd):
pass
@abc.abstractmethod
def delete_mesd(self, context, mesd_id):
pass
@abc.abstractmethod
def get_mesd(self, context, mesd_id, fields=None):
pass
@abc.abstractmethod
def get_mesds(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def create_mes(self, context, mes):
pass
@abc.abstractmethod
def get_mess(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_mes(self, context, mes_id, fields=None):
pass
@abc.abstractmethod
def delete_mes(self, context, mes_id):
pass
class MESDNotFound(exceptions.NotFound):
message = _('MESD %(mesd_id)s could not be found')
class MESNotFound(exceptions.NotFound):
message = _('MES %(mes_id)s could not be found')

320
apmec/extensions/meso.py Normal file
View File

@ -0,0 +1,320 @@
# Copyright 2016 Brocade Communications Systems 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 abc
import six
from apmec._i18n import _
from apmec.api import extensions
from apmec.api.v1 import attributes as attr
from apmec.api.v1 import resource_helper
from apmec.common import exceptions
from apmec.plugins.common import constants
from apmec.services import service_base
class MESDInUse(exceptions.InUse):
message = _('MESD %(mesd_id)s is still in use')
class MESInUse(exceptions.InUse):
message = _('MES %(mes_id)s is still in use')
class NoTasksException(exceptions.ApmecException):
message = _('No tasks to run for %(action)s on %(resource)s')
class MESDNotFound(exceptions.NotFound):
message = _('MESD %(mesd_id)s could not be found')
class MESNotFound(exceptions.NotFound):
message = _('MES %(mes_id)s could not be found')
class ToscaParserFailed(exceptions.InvalidInput):
message = _("tosca-parser failed: - %(error_msg_details)s")
class NSDNotFound(exceptions.NotFound):
message = _('NSD template(s) not existed for MESD %(mesd_name)')
class VNFFGDNotFound(exceptions.NotFound):
message = _('VNFFGD template(s) not existed for MESD %(mesd_name)')
class MECDriverNotfound(exceptions.NotFound):
message = _('MEC driver not specified for the MESD %(mesd_name)')
class NFVDriverNotFound(exceptions.NotFound):
message = _('NFV driver is not specified for the MESD %(mesd_name)')
RESOURCE_ATTRIBUTE_MAP = {
'mesds': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'mesd_mapping': {
'allow_post': False,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': '',
},
'created_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'updated_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'attributes': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
'template_source': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
'default': 'onboarded'
},
},
'mess': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'created_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'updated_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'mes_mapping': {
'allow_post': False,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': '',
},
'reused': {
'allow_post': False,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': '',
},
'mesd_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'default': None,
},
'vim_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'error_reason': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'attributes': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
'mesd_template': {
'allow_post': True,
'allow_put': True,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
},
}
class Meso(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return 'MEC Service Orchestrator'
@classmethod
def get_alias(cls):
return 'MESO'
@classmethod
def get_description(cls):
return "Extension for MEC Service Orchestrator"
@classmethod
def get_namespace(cls):
return 'http://wiki.openstack.org/Apmec'
@classmethod
def get_updated(cls):
return "2015-12-21T10:00:00-00:00"
@classmethod
def get_resources(cls):
special_mappings = {}
plural_mappings = resource_helper.build_plural_mappings(
special_mappings, RESOURCE_ATTRIBUTE_MAP)
attr.PLURALS.update(plural_mappings)
return resource_helper.build_resource_info(
plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.MESO,
translate_name=True)
@classmethod
def get_plugin_interface(cls):
return MESOPluginBase
def update_attributes_map(self, attributes):
super(Meso, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
version_map = {'1.0': RESOURCE_ATTRIBUTE_MAP}
return version_map.get(version, {})
@six.add_metaclass(abc.ABCMeta)
class MESOPluginBase(service_base.MECPluginBase):
def get_plugin_name(self):
return constants.MESO
def get_plugin_type(self):
return constants.MESO
def get_plugin_description(self):
return 'Apmec MEC service Orchestrator plugin'
@abc.abstractmethod
def create_mesd(self, context, mesd):
pass
@abc.abstractmethod
def delete_mesd(self, context, mesd_id):
pass
@abc.abstractmethod
def get_mesd(self, context, mesd_id, fields=None):
pass
@abc.abstractmethod
def get_mesds(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def create_mes(self, context, mes):
pass
@abc.abstractmethod
def get_mess(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_mes(self, context, mes_id, fields=None):
pass
@abc.abstractmethod
def delete_mes(self, context, mes_id):
pass
@abc.abstractmethod
def update_mes(self, context, mes_id, mes):
pass

0
apmec/meso/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,44 @@
# 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 abc
import six
from apmec.api import extensions
@six.add_metaclass(abc.ABCMeta)
class NfvAbstractDriver(extensions.PluginInterface):
@abc.abstractmethod
def get_type(self):
"""Get NFV Driver type
Return one of predefined types of NFV drivers.
"""
pass
@abc.abstractmethod
def get_name(self):
"""Get NFV driver name
Return a symbolic name for the NFV driver.
"""
pass
@abc.abstractmethod
def get_description(self):
pass

View File

@ -0,0 +1,278 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import identity
from keystoneauth1 import session
from oslo_config import cfg
from oslo_log import log as logging
from apmec.meso.drivers.nfv_drivers import abstract_driver
from tackerclient.v1_0 import client as tacker_client
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class Tacker_Driver(abstract_driver.NfvAbstractDriver):
"""Driver for NFV
OpenStack driver handles interactions with local as well as
remote OpenStack instances. The driver invokes keystone service for VIM
authorization and validation. The driver is also responsible for
discovering placement attributes such as regions, availability zones
"""
def get_type(self):
return 'tacker'
def get_name(self):
return 'OpenStack Tacker Driver'
def get_description(self):
return 'OpenStack Tacker Driver'
def nsd_create(self, auth_attr, nsd_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.nsd_create(nsd_dict)
def nsd_get_by_name(self, auth_attr, nsd_name):
tacker_client = TackerClient(auth_attr)
return tacker_client.nsd_get_by_name(nsd_name)
def nsd_get(self, auth_attr, nsd_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.nsd_get(nsd_id)
def ns_create(self, auth_attr, ns_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_create(ns_dict)
def ns_get_by_name(self, auth_attr, ns_name):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_get(ns_name)
def ns_get(self, auth_attr, ns_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_get(ns_id)
def ns_check(self, auth_attr, ns_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_check(ns_id)
def ns_delete_by_name(self, auth_attr, ns_name):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_delete(ns_name)
def ns_delete(self, auth_attr, ns_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_delete(ns_id)
def ns_update(self, auth_attr, ns_id, ns_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_update(ns_id, ns_dict)
def vnfd_create(self, auth_attr, vnfd_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnfd_create(vnfd_dict)
def vnf_create(self, auth_attr, vnf_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnf_create(vnf_dict)
def vnf_get(self, auth_attr, vnf_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnf_get(vnf_id)
def vnfd_get(self, auth_attr, vnfd_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnfd_get(vnfd_id)
def vnffgd_get_by_name(self, auth_attr, vnffgd_name):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffgd_get(vnffgd_name)
def vnffgd_get(self, auth_attr, vnffgd_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffgd_get(vnffgd_id)
def vnffg_create(self, auth_attr, vnffg_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffg_create(vnffg_dict)
def vnffg_get_by_name(self, auth_attr, vnffg_name):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffg_get(vnffg_name)
def vnffg_get(self, auth_attr, vnffg_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffg_get(vnffg_id)
def vnffg_delete_by_name(self, auth_attr, vnffg_name):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffg_delete(vnffg_name)
def vnffg_delete(self, auth_attr, vnffg_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffg_delete(vnffg_id)
def vnffg_check(self, auth_attr, vnffg_id):
tacker_client = TackerClient(auth_attr)
return tacker_client.vnffg_check(vnffg_id)
def vnffg_update(self, auth_attr, vnffg_id, vnffg_dict):
tacker_client = TackerClient(auth_attr)
return tacker_client.ns_update(vnffg_id, vnffg_dict)
class TackerClient(object):
"""Tacker Client class for VNFM and NFVO negotiation"""
def __init__(self, auth_attr):
auth = identity.Password(**auth_attr)
sess = session.Session(auth=auth)
self.client = tacker_client.Client(session=sess)
def nsd_create(self, nsd_dict):
nsd_instance = self.client.create_nsd(body=nsd_dict)
if nsd_instance:
return nsd_instance['nsd']['id']
else:
return None
def nsd_get_by_name(self, nsd_name):
nsd_dict = self.client.list_nsds()
nsd_list = nsd_dict['nsds']
nsd_dict = None
for nsd in nsd_list:
if nsd['name'] == nsd_name:
nsd_dict = nsd
return nsd_dict
def nsd_get(self, nsd_id):
nsd_dict = self.client.show_nsd(nsd_id)
return nsd_dict['nsd']
def ns_create(self, ns_dict):
ns_instance = self.client.create_ns(body=ns_dict)
if ns_instance:
return ns_instance['ns']['id']
else:
return None
def ns_get_by_name(self, ns_name):
ns_dict = self.client.list_nsds()
ns_list = ns_dict['nss']
ns_id = None
for ns in ns_list:
if ns['name'] == ns_name:
ns_id = ns['id']
return ns_id
def ns_get(self, ns_id):
ns_instance = self.client.show_ns(ns_id)
return ns_instance['ns']
def ns_delete_by_name(self, ns_name):
ns_id = self.ns_get_by_name(ns_name)
if ns_id:
self.client.delete_ns(ns_id)
def ns_check(self, ns_id):
ns_dict = self.client.list_nss()
ns_list = ns_dict['nss']
check = False
for ns in ns_list:
if ns['id'] == ns_id:
check = True
return check
def ns_delete(self, ns_id):
return self.client.delete_ns(ns_id)
def ns_update(self, ns_id, ns_dict):
return self.client.update_ns(ns_id, ns_dict)
def vnfd_create(self, vnfd_dict):
vnfd_instance = self.client.create_vnfd(body=vnfd_dict)
if vnfd_instance:
return vnfd_instance['vnf']['id']
else:
return None
def vnf_create(self, vnf_dict):
vnf_instance = self.client.create_vnf(body=vnf_dict)
if vnf_instance:
return vnf_instance['vnf']['id']
else:
return None
def vnf_get(self, vnf_id):
vnf_instance = self.client.show_vnf(vnf_id)
return vnf_instance['vnf']
def vnfd_get(self, vnfd_id):
vnfd_instance = self.client.show_vnfd(vnfd_id)
return vnfd_instance['vnfd']
def vnffgd_get_by_name(self, vnffgd_name):
vnffgd_dict = self.client.list_vnffgds()
vnffgd_list = vnffgd_dict['vnffgds']
vnffgd_dict = None
for vnffgd in vnffgd_list:
if vnffgd['name'] == vnffgd_name:
vnffgd_dict = vnffgd
return vnffgd_dict
def vnffgd_get(self, vnffgd_id):
vnffgd_instance = self.client.show_vnffgd(vnffgd_id)
return vnffgd_instance['vnffgd']
def vnffg_create(self, vnffgd_dict):
vnffg_instance = self.client.create_vnffg(body=vnffgd_dict)
if vnffg_instance:
return vnffg_instance['vnffg']['id']
else:
return None
def vnffg_get_by_name(self, vnffg_name):
vnffg_dict = self.client.list_vnffgs()
vnffg_list = vnffg_dict['vnffgs']
vnffg_id = None
for vnffg in vnffg_list:
if vnffg['name'] == vnffg_name:
vnffg_id = vnffg['id']
return vnffg_id
def vnffg_get(self, vnffg_id):
vnffg_instance = self.client.show_vnffg(vnffg_id)
return vnffg_instance['vnffg']
def vnffg_delete_by_name(self, vnffg_name):
vnffg_id = self.vnffg_get_by_name(vnffg_name)
if vnffg_id:
self.client.delete_vnffg(vnffg_id)
def vnffg_delete(self, vnffg_id):
return self.client.delete_vnffg(vnffg_id)
def vnffg_check(self, vnffg_id):
vnffg_dict = self.client.list_vnffgs()
vnffg_list = vnffg_dict['vnffgs']
check = False
for vnffg in vnffg_list:
if vnffg['id'] == vnffg_id:
check = True
return check
def vnffg_update(self, vnffg_id, vnffg_dict):
return self.client.update_ns(vnffg_id, vnffg_dict)

943
apmec/meso/meso_plugin.py Normal file
View File

@ -0,0 +1,943 @@
# 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 ast
import copy
import eventlet
import random
import time
import yaml
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import uuidutils
from apmec._i18n import _
from apmec.common import driver_manager
from apmec.common import log
from apmec.common import utils
from apmec.db.meso import meso_db
from apmec.extensions import common_services as cs
from apmec.extensions import meso
from apmec import manager
from apmec.mem import vim_client
from apmec.plugins.common import constants
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
NS_RETRIES = 30
NS_RETRY_WAIT = 6
MEC_RETRIES = 30
MEC_RETRY_WAIT = 6
VNFFG_RETRIES = 30
VNFFG_RETRY_WAIT = 6
def config_opts():
return [('meso', MesoPlugin.OPTS)]
NF_CAP_MAX = 3
VNF_LIST = ['VNF1', 'VNF2', 'VNF2', 'VNF3', 'VNF4', 'VNF5', 'VNF6']
VM_CAPA = dict()
for vnf_name in VNF_LIST:
VM_CAPA[vnf_name] = random.randint(1, NF_CAP_MAX)
class MesoPlugin(meso_db.MESOPluginDb):
"""MESO reference plugin for MESO extension
Implements the MESO extension and defines public facing APIs for VIM
operations. MESO 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 = ['meso']
OPTS = [
cfg.ListOpt(
'nfv_drivers', default=['tacker'],
help=_('NFV drivers for launching NSs')),
]
cfg.CONF.register_opts(OPTS, 'meso')
def __init__(self):
super(MesoPlugin, self).__init__()
self._pool = eventlet.GreenPool()
self._nfv_drivers = driver_manager.DriverManager(
'apmec.meso.drivers',
cfg.CONF.meso.nfv_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)
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
@log.log
def create_mesd(self, context, mesd):
mesd_data = mesd['mesd']
template = mesd_data['attributes'].get('mesd')
if isinstance(template, dict):
mesd_data['attributes']['mesd'] = yaml.safe_dump(
template)
LOG.debug('mesd %s', mesd_data)
if 'template_source' in mesd_data:
template_source = mesd_data.get('template_source')
else:
template_source = "onboarded"
mesd['mesd']['template_source'] = template_source
self._parse_template_input(context, mesd)
return super(MesoPlugin, self).create_mesd(
context, mesd)
def _parse_template_input(self, context, mesd):
mesd_dict = mesd['mesd']
mesd_yaml = mesd_dict['attributes'].get('mesd')
inner_mesd_dict = yaml.safe_load(mesd_yaml)
mesd_dict['mesd_mapping'] = dict()
LOG.debug('mesd_dict: %s', inner_mesd_dict)
# From import we can deploy both NS and MEC Application
nsd_imports = inner_mesd_dict['imports'].get('nsds')
vnffg_imports = inner_mesd_dict['imports'].get('vnffgds')
if nsd_imports:
nsd_tpls = nsd_imports.get('nsd_templates')
nfv_driver = nsd_imports.get('nfv_driver')
if not nsd_tpls:
raise meso.NSDNotFound(mesd_name=mesd_dict['name'])
if nfv_driver.lower() not in\
[driver.lower() for driver in constants.NFV_DRIVER]:
raise meso.NFVDriverNotFound(mesd_name=mesd_dict['name'])
if isinstance(nsd_tpls, list):
mesd_dict['attributes']['nsds'] = '-'.join(nsd_tpls)
mesd_dict['mesd_mapping']['NSD'] = nsd_tpls
if vnffg_imports:
vnffgd_tpls = vnffg_imports.get('vnffgd_templates')
nfv_driver = vnffg_imports.get('nfv_driver')
if not vnffgd_tpls:
raise meso.VNFFGDNotFound(mesd_name=mesd_dict['name'])
if nfv_driver.lower() not in \
[driver.lower() for driver in constants.NFV_DRIVER]:
raise meso.NFVDriverNotFound(mesd_name=mesd_dict['name'])
if isinstance(vnffgd_tpls, list):
mesd_dict['mesd_mapping']['VNFFGD'] = vnffgd_tpls
mesd_dict['attributes']['vnffgds'] = '-'.join(vnffgd_tpls)
if ('description' not in mesd_dict or
mesd_dict['description'] == ''):
mesd_dict['description'] = inner_mesd_dict.get(
'description', '')
if (('name' not in mesd_dict or
not len(mesd_dict['name'])) and
'metadata' in inner_mesd_dict):
mesd_dict['name'] = inner_mesd_dict['metadata'].get(
'template_name', '')
LOG.debug('mesd %s', mesd)
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_mes(self, context, mes):
"""Create MES and corresponding MEAs.
:param mes: mes dict which contains mesd_id and attributes
This method has 2 steps:
step-1: Call MEO API to create MEAs
step-2: Call Tacker drivers to create NSs
"""
mes_info = mes['mes']
name = mes_info['name']
mes_info['mes_mapping'] = dict()
if mes_info.get('mesd_template'):
mesd_name = utils.generate_resource_name(name, 'inline')
mesd = {'mesd': {
'attributes': {'mesd': mes_info['mesd_template']},
'description': mes_info['description'],
'name': mesd_name,
'template_source': 'inline',
'tenant_id': mes_info['tenant_id']}}
mes_info['mesd_id'] = self.create_mesd(context, mesd).get('id')
mesd = self.get_mesd(context, mes['mes']['mesd_id'])
mesd_dict = yaml.safe_load(mesd['attributes']['mesd'])
meo_plugin = manager.ApmecManager.get_service_plugins()['MEO']
region_name = mes.setdefault('placement_attr', {}).get(
'region_name', None)
vim_res = self.vim_client.get_vim(context, mes['mes']['vim_id'],
region_name)
# driver_type = vim_res['vim_type']
if not mes['mes']['vim_id']:
mes['mes']['vim_id'] = vim_res['vim_id']
##########################################
# Detect MANO driver here:
# Defined in the Tosca template
nfv_driver = None
if mesd_dict['imports'].get('nsds'):
nfv_driver = mesd_dict['imports']['nsds']['nfv_driver']
nfv_driver = nfv_driver.lower()
if mesd_dict['imports'].get('vnffgds'):
nfv_driver = mesd_dict['imports']['vnffgds']['nfv_driver']
nfv_driver = nfv_driver.lower()
##########################################
def _find_vnf_ins(cd_mes):
al_ns_id_list = cd_mes['mes_mapping'].get('NS')
if not al_ns_id_list:
return None, None
al_ns_id = al_ns_id_list[0]
try:
ns_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_get',
ns_id=al_ns_id,
auth_attr=vim_res['vim_auth'], )
except Exception:
return None, None
if ns_instance['status'] != 'ACTIVE':
return None, None
al_vnf = ns_instance['vnf_ids']
al_vnf_dict = ast.literal_eval(al_vnf)
return ns_instance['id'], al_vnf_dict
def _run_meso_algorithm(req_vnf_list):
is_accepted = False
al_mes_list = self.get_mess(context)
ns_candidate = dict()
for al_mes in al_mes_list:
ns_candidate[al_mes['id']] = dict()
if al_mes['status'] != "ACTIVE":
continue
al_ns_id, al_vnf_dict = _find_vnf_ins(al_mes)
if not al_ns_id:
continue
ns_candidate[al_mes['id']][al_ns_id] = dict()
for req_vnf_dict in req_vnf_list:
for vnf_name, al_vnf_id in al_vnf_dict.items():
if req_vnf_dict['name'] == vnf_name:
# Todo: remember to change this with VM capacity
len_diff =\
len([lend for lend in
al_mes['reused'][vnf_name]
if lend > 0])
avail = len_diff - req_vnf_dict['nf_ins']
ns_candidate[al_mes['id']][al_ns_id].\
update({vnf_name: avail})
ns_cds = dict()
deep_ns = dict()
for mesid, ns_data_dict in ns_candidate.items():
for nsid, resev_dict in ns_data_dict.items():
if len(resev_dict) == len(req_vnf_list):
nf_ins_list =\
[nf_ins for nf_name, nf_ins in
resev_dict.items() if nf_ins >= 0]
if len(nf_ins_list) == len(req_vnf_list):
total_ins = sum(nf_ins_list)
ns_cds[mesid] = total_ins
else:
extra_nf_ins_list =\
[-nf_ins for nf_name, nf_ins in
resev_dict.items() if nf_ins < 0]
total_ins = sum(extra_nf_ins_list)
deep_ns[mesid] = total_ins
if ns_cds:
selected_mes1 = min(ns_cds, key=ns_cds.get)
is_accepted = True
return is_accepted, selected_mes1, None
if deep_ns:
selected_mes2 = min(deep_ns, key=deep_ns.get)
is_accepted = True
return is_accepted, selected_mes2, ns_candidate[selected_mes2]
return is_accepted, None, None
build_nsd_dict = dict()
if mesd_dict['imports'].get('nsds'):
# For framework evaluation
nsd_template = mesd_dict['imports']['nsds']['nsd_templates']
if isinstance(nsd_template, dict):
if nsd_template.get('requirements'):
req_nf_dict = nsd_template['requirements']
req_nf_list = list()
for vnf_dict in req_nf_dict:
# Todo: make the requests more natural
req_nf_list.append(
{'name': vnf_dict['name'],
'nf_ins': int(vnf_dict['vnfd_template'][5])})
is_accepted, cd_mes_id, cd_vnf_dict =\
_run_meso_algorithm(req_nf_list)
if is_accepted:
new_mesd_dict = dict()
ref_mesd_dict = copy.deepcopy(mesd_dict)
ref_mesd_dict['imports']['nsds']['nsd_templates']['requirements'] = \
req_nf_list
new_mesd_dict['mes'] = dict()
new_mesd_dict['mes'] =\
{'mesd_template': yaml.safe_dump(ref_mesd_dict)}
self.update_mes(context, cd_mes_id, new_mesd_dict)
return cd_mes_id
else:
# Create the inline NS with the following template
import_list = list()
node_dict = dict()
for vnfd in req_nf_dict:
import_list.append(vnfd['vnfd_template'])
node = 'tosca.nodes.nfv.' + vnfd['name']
node_dict[vnfd['name']] = {'type': node}
build_nsd_dict['tosca_definitions_version'] =\
'tosca_simple_profile_for_nfv_1_0_0'
build_nsd_dict['description'] = mes_info['description']
build_nsd_dict['imports'] = import_list
build_nsd_dict['topology_template'] = dict()
build_nsd_dict['topology_template']['node_templates'] =\
node_dict
nsds = mesd['attributes'].get('nsds')
mes_info['mes_mapping']['NS'] = list()
if nsds:
nsds_list = nsds.split('-')
for nsd in nsds_list:
ns_name = nsd + '-' + name + '-' + uuidutils.generate_uuid() # noqa
nsd_instance = self._nfv_drivers.invoke(
nfv_driver,
'nsd_get_by_name',
nsd_name=nsd,
auth_attr=vim_res['vim_auth'],)
if nsd_instance:
ns_arg = {'ns': {'nsd_id': nsd_instance['id'],
'name': ns_name}}
ns_id = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_create',
ns_dict=ns_arg,
auth_attr=vim_res['vim_auth'], )
mes_info['mes_mapping']['NS'].append(ns_id)
if build_nsd_dict:
ns_name = 'nsd' + name + '-' + uuidutils.generate_uuid()
ns_arg = {'ns': {'nsd_template': build_nsd_dict,
'name': ns_name,
'description': mes_info['description'],
'vim_id': '',
'tenant_id': mes_info['tenant_id'],
'attributes': {}}}
ns_id = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_create',
ns_dict=ns_arg,
auth_attr=vim_res['vim_auth'], )
mes_info['mes_mapping']['NS'].append(ns_id)
vnffgds = mesd['attributes'].get('vnffgds')
if mesd_dict['imports'].get('vnffgds'):
vnffgds_list = vnffgds.split('-')
mes_info['mes_mapping']['VNFFG'] = list()
for vnffgd in vnffgds_list:
vnffg_name = vnffgds + '-' + name + '-' + uuidutils.generate_uuid() # noqa
vnffgd_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffgd_get_by_name',
vnffgd_name=vnffgd,
auth_attr=vim_res['vim_auth'], )
if vnffgd_instance:
vnffg_arg = {'vnffg': {'vnffgd_id': vnffgd_instance['id'], 'name': vnffg_name}} # noqa
vnffg_id = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffg_create',
vnffg_dict=vnffg_arg,
auth_attr=vim_res['vim_auth'], )
mes_info['mes_mapping']['VNFFG'].append(vnffg_id)
# meca_id = dict()
# Create MEAs using MEO APIs
try:
meca_name = 'meca' + '-' + name + '-' + uuidutils.generate_uuid()
# Separate the imports out from template
mead_tpl_dict = dict()
mead_tpl_dict['imports'] =\
mesd_dict['imports']['meads']['mead_templates']
mecad_dict = copy.deepcopy(mesd_dict)
mecad_dict.pop('imports')
mecad_dict.update(mead_tpl_dict)
LOG.debug('mesd %s', mecad_dict)
meca_arg = {'meca': {'mecad_template': mecad_dict, 'name': meca_name, # noqa
'description': mes_info['description'],
'tenant_id': mes_info['tenant_id'],
'vim_id': mes_info['vim_id'],
'attributes': {}}}
meca_dict = meo_plugin.create_meca(context, meca_arg)
mes_info['mes_mapping']['MECA'] = meca_dict['id']
except Exception as e:
LOG.error('Error while creating the MECAs: %s', e)
# Call Tacker client driver
mes_dict = super(MesoPlugin, self).create_mes(context, mes)
def _create_mes_wait(self_obj, mes_id):
args = dict()
mes_status = "ACTIVE"
ns_status = "PENDING_CREATE"
vnffg_status = "PENDING_CREATE"
mec_status = "PENDING_CREATE"
ns_retries = NS_RETRIES
mec_retries = MEC_RETRIES
vnffg_retries = VNFFG_RETRIES
mes_mapping = self.get_mes(context, mes_id)['mes_mapping']
# Check MECA
meca_id = mes_mapping['MECA']
while mec_status == "PENDING_CREATE" and mec_retries > 0:
time.sleep(MEC_RETRY_WAIT)
mec_status = meo_plugin.get_meca(context, meca_id)['status']
LOG.debug('status: %s', mec_status)
if mec_status == 'ACTIVE' or mec_status == 'ERROR':
break
mec_retries = mec_retries - 1
error_reason = None
if mec_retries == 0 and mec_status == 'PENDING_CREATE':
error_reason = _(
"MES creation is not completed within"
" {wait} seconds as creation of MECA").format(
wait=MEC_RETRIES * MEC_RETRY_WAIT)
# Check NS/VNFFG status
if mes_mapping.get('NS'):
ns_list = mes_mapping['NS']
while ns_status == "PENDING_CREATE" and ns_retries > 0:
time.sleep(NS_RETRY_WAIT)
# Todo: support multiple NSs
ns_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_get',
ns_id=ns_list[0],
auth_attr=vim_res['vim_auth'], )
ns_status = ns_instance['status']
LOG.debug('status: %s', ns_status)
if ns_status == 'ACTIVE' or ns_status == 'ERROR':
break
ns_retries = ns_retries - 1
error_reason = None
if ns_retries == 0 and ns_status == 'PENDING_CREATE':
error_reason = _(
"MES creation is not completed within"
" {wait} seconds as creation of NS(s)").format(
wait=NS_RETRIES * NS_RETRY_WAIT)
# Determine args
ns_cd = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_get',
ns_id=ns_list[0],
auth_attr=vim_res['vim_auth'], )
ns_instance_dict = ns_cd['mgmt_urls']
ns_instance_list = ast.literal_eval(ns_instance_dict)
args['NS'] = dict()
for vnf_name, mgmt_url_list in ns_instance_list.items():
# Todo: remember to change this with VM capacity
vm_capacity = VM_CAPA[vnf_name]
orig = [vm_capacity] * len(mgmt_url_list)
args['NS'][vnf_name] = [(val - 1) for val in orig]
if mes_mapping.get('VNFFG'):
while vnffg_status == "PENDING_CREATE" and vnffg_retries > 0:
time.sleep(VNFFG_RETRY_WAIT)
vnffg_list = mes_mapping['VNFFG']
# Todo: support multiple VNFFGs
vnffg_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffg_get',
vnffg_id=vnffg_list[0],
auth_attr=vim_res['vim_auth'], )
vnffg_status = vnffg_instance['status']
LOG.debug('status: %s', vnffg_status)
if vnffg_status == 'ACTIVE' or vnffg_status == 'ERROR':
break
vnffg_retries = vnffg_retries - 1
error_reason = None
if vnffg_retries == 0 and vnffg_status == 'PENDING_CREATE':
error_reason = _(
"MES creation is not completed within"
" {wait} seconds as creation of VNFFG(s)").format(
wait=VNFFG_RETRIES * VNFFG_RETRY_WAIT)
if mec_status == "ERROR" or ns_status == "ERROR" or vnffg_status == "ERROR": # noqa
mes_status = "ERROR"
if error_reason:
mes_status = "PENDING_CREATE"
super(MesoPlugin, self).create_mes_post(context, mes_id, mes_status, error_reason, args) # noqa
self.spawn_n(_create_mes_wait, self, mes_dict['id'])
return mes_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, mesd_dict):
param_vattrs_dict = attrs.pop('param_values', None)
if param_vattrs_dict:
for node in \
mesd_dict['topology_template']['node_templates'].values():
if 'get_input' in str(node):
self._update_params(node, param_vattrs_dict['mesd'])
else:
raise cs.ParamYAMLInputMissing()
@log.log
def delete_mes(self, context, mes_id):
mes = super(MesoPlugin, self).get_mes(context, mes_id)
mesd = self.get_mesd(context, mes['mesd_id'])
mesd_dict = yaml.safe_load(mesd['attributes']['mesd'])
vim_res = self.vim_client.get_vim(context, mes['vim_id'])
mes_mapping = mes['mes_mapping']
meca_id = mes_mapping['MECA']
meo_plugin = manager.ApmecManager.get_service_plugins()['MEO']
try:
meca_id = meo_plugin.delete_meca(context, meca_id)
except Exception as e:
LOG.error('Error while deleting the MECA(s): %s', e)
if mes_mapping.get('NS'):
# Todo: support multiple NSs
ns_id = mes_mapping['NS'][0]
nfv_driver = None
if mesd_dict['imports'].get('nsds'):
nfv_driver = mesd_dict['imports']['nsds']['nfv_driver']
nfv_driver = nfv_driver.lower()
if not nfv_driver:
raise meso.NFVDriverNotFound(mesd_name=mesd_dict['name'])
try:
self._nfv_drivers.invoke(
nfv_driver,
'ns_delete',
ns_id=ns_id,
auth_attr=vim_res['vim_auth'])
except Exception as e:
LOG.error('Error while deleting the NS(s): %s', e)
if mes_mapping.get('VNFFG'):
# Todo: support multiple VNFFGs
vnffg_id = mes_mapping['VNFFG'][0]
nfv_driver = None
if mesd_dict['imports'].get('vnffgds'):
nfv_driver = mesd_dict['imports']['vnffgds']['nfv_driver']
nfv_driver = nfv_driver.lower()
if not nfv_driver:
raise meso.NFVDriverNotFound(mesd_name=mesd_dict['name'])
try:
self._nfv_drivers.invoke(
nfv_driver,
'vnffg_delete',
vnffg_id=vnffg_id,
auth_attr=vim_res['vim_auth'])
except Exception as e:
LOG.error('Error while deleting the VNFFG(s): %s', e)
super(MesoPlugin, self).delete_mes(context, mes_id)
def _delete_mes_wait(mes_id):
ns_status = "PENDING_DELETE"
vnffg_status = "PENDING_DELETE"
mec_status = "PENDING_DELETE"
ns_retries = NS_RETRIES
mec_retries = MEC_RETRIES
vnffg_retries = VNFFG_RETRIES
error_reason_meca = None
error_reason_ns = None
error_reason_vnffg = None
# Check MECA
while mec_status == "PENDING_DELETE" and mec_retries > 0:
time.sleep(MEC_RETRY_WAIT)
meca_id = mes_mapping['MECA']
meca_list = meo_plugin.get_mecas(context)
is_deleted = True
for meca in meca_list:
if meca_id in meca['id']:
is_deleted = False
if is_deleted:
break
mec_status = meo_plugin.get_meca(context, meca_id)['status']
LOG.debug('status: %s', mec_status)
if mec_status == 'ERROR':
break
mec_retries = mec_retries - 1
if mec_retries == 0 and mec_status == 'PENDING_DELETE':
error_reason_meca = _(
"MES deletion is not completed within"
" {wait} seconds as deletion of MECA").format(
wait=MEC_RETRIES * MEC_RETRY_WAIT)
# Check NS/VNFFG status
if mes_mapping.get('NS'):
while ns_status == "PENDING_DELETE" and ns_retries > 0:
time.sleep(NS_RETRY_WAIT)
ns_list = mes_mapping['NS']
# Todo: support multiple NSs
is_existed = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_check',
ns_id=ns_list[0],
auth_attr=vim_res['vim_auth'], )
if not is_existed:
break
ns_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_get',
ns_id=ns_list[0],
auth_attr=vim_res['vim_auth'], )
ns_status = ns_instance['status']
LOG.debug('status: %s', ns_status)
if ns_status == 'ERROR':
break
ns_retries = ns_retries - 1
if ns_retries == 0 and ns_status == 'PENDING_DELETE':
error_reason_ns = _(
"MES deletion is not completed within"
" {wait} seconds as deletion of NS(s)").format(
wait=NS_RETRIES * NS_RETRY_WAIT)
if mes_mapping.get('VNFFG'):
while vnffg_status == "PENDING_DELETE" and vnffg_retries > 0:
time.sleep(VNFFG_RETRY_WAIT)
vnffg_list = mes_mapping['VNFFG']
# Todo: support multiple VNFFGs
is_existed = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffg_check',
vnffg_id=vnffg_list[0],
auth_attr=vim_res['vim_auth'], )
if not is_existed:
break
vnffg_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffg_get',
vnffg_id=vnffg_list[0],
auth_attr=vim_res['vim_auth'], )
vnffg_status = vnffg_instance['status']
LOG.debug('status: %s', vnffg_status)
if vnffg_status == 'ERROR':
break
vnffg_retries = vnffg_retries - 1
if vnffg_retries == 0 and vnffg_status == 'PENDING_DELETE':
error_reason_vnffg = _(
"MES deletion is not completed within"
" {wait} seconds as deletion of VNFFG(s)").format(
wait=VNFFG_RETRIES * VNFFG_RETRY_WAIT)
error = False
if mec_status == "ERROR" or ns_status == "ERROR" or vnffg_status == "ERROR": # noqa
error = True
error_reason = None
for reason in [error_reason_meca, error_reason_ns, error_reason_vnffg]: # noqa
error_reason = reason if reason else None
super(MesoPlugin, self).delete_mes_post(
context, mes_id, error_reason=error_reason, error=error)
self.spawn_n(_delete_mes_wait, mes['id'])
return mes['id']
def update_mes(self, context, mes_id, mes):
args = dict()
mes_info = mes['mes']
old_mes = super(MesoPlugin, self).get_mes(context, mes_id)
name = old_mes['name']
lftover = dict()
# vm_capacity = 3
# create inline mesd if given by user
def _update_nsd_template(req_mesd_dict):
build_nsd_dict = dict()
if req_mesd_dict['imports'].get('nsds'):
args['NS'] = dict()
# Todo: Support multiple NSs
# For framework evaluation
nsd_templates = req_mesd_dict['imports']['nsds']['nsd_templates'] # noqa
if isinstance(nsd_templates, dict):
if nsd_templates.get('requirements'):
old_reused = old_mes['reused']
vnf_mapping_list = nsd_templates['requirements']
for vnf_mapping_dict in vnf_mapping_list:
for old_vnf_name, old_nfins_list in old_reused.items(): # noqa
if vnf_mapping_dict['name'] == old_vnf_name:
len_diff = len([lend for lend in old_nfins_list if lend > 0]) # noqa
diff = len_diff - vnf_mapping_dict['nf_ins'] # noqa
if diff < 0:
lftover.update({old_vnf_name: -diff})
vm_capacity = VM_CAPA[old_vnf_name]
old_reused[old_vnf_name].extend([vm_capacity] * (-diff)) # noqa
# old_reused[old_vnf_name] = diff
temp = vnf_mapping_dict['nf_ins']
for index, nfins in enumerate(old_nfins_list): # noqa
if nfins > 0:
old_nfins_list[index] = old_nfins_list[index] - 1 # noqa
temp = temp - 1
if temp == 0:
break
formal_req = list()
for nf_name, nf_ins in lftover.items():
vnfd_name = 'vnfd' + nf_name[3] + str(nf_ins)
formal_req.append(vnfd_name)
if formal_req:
build_nsd_dict['tosca_definitions_version'] = 'tosca_simple_profile_for_nfv_1_0_0' # noqa
build_nsd_dict['description'] = old_mes['description']
build_nsd_dict['imports'] = formal_req
build_nsd_dict['topology_template'] = dict()
build_nsd_dict['topology_template']['node_templates'] = dict() # noqa
for nf_name, nf_ins in lftover.items():
node = 'tosca.nodes.nfv.' + nf_name
node_dict = dict()
node_dict['type'] = node
build_nsd_dict['topology_template']['node_templates'].update({nf_name: node_dict}) # noqa
return build_nsd_dict
if mes_info.get('mesd_template'):
# Build vnf_dict here
mes_name = utils.generate_resource_name(name, 'inline')
mesd = {'mesd': {'tenant_id': old_mes['tenant_id'],
'name': mes_name,
'attributes': {
'mesd': mes_info['mesd_template']},
'template_source': 'inline',
'description': old_mes['description']}}
try:
mes_info['mesd_id'] = \
self.create_mesd(context, mesd).get('id')
except Exception:
with excutils.save_and_reraise_exception():
super(MesoPlugin, self)._update_mes_status(context, mes_id, constants.ACTIVE) # noqa
mesd = self.get_mesd(context, mes_info['mesd_id'])
mesd_dict = yaml.safe_load(mesd['attributes']['mesd'])
new_mesd_mapping = mesd['mesd_mapping']
region_name = mes.setdefault('placement_attr', {}).get(
'region_name', None)
vim_res = self.vim_client.get_vim(context, old_mes['vim_id'],
region_name)
if mesd_dict['imports'].get('meads'):
# Update MECA
meo_plugin = manager.ApmecManager.get_service_plugins()['MEO']
# Build the MECA template here
mead_tpl_dict = dict()
mead_tpl_dict['imports'] =\
mesd_dict['imports']['meads']['mead_templates']
mecad_dict = copy.deepcopy(mesd_dict)
mecad_dict.pop('imports')
mecad_dict.update(mead_tpl_dict)
mecad_arg = {'meca': {'mecad_template': mecad_dict}}
old_meca_id = old_mes['mes_mapping']['MECA']
meca_id = meo_plugin.update_meca(context, old_meca_id, mecad_arg) # noqa
if mesd_dict['imports'].get('nsds'):
nfv_driver = None
nfv_driver = mesd_dict['imports']['nsds'].get('nfv_driver')
if not nfv_driver:
raise meso.NFVDriverNotFound(mesd_name=mesd_dict['name'])
nfv_driver = nfv_driver.lower()
req_mesd_dict = yaml.safe_load(mes_info['mesd_template'])
new_nsd_template = _update_nsd_template(req_mesd_dict)
nsd_template = None
if isinstance(new_mesd_mapping.get('NSD'), list):
nsd_name = new_mesd_mapping['NSD'][0]
nsd_dict = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'nsd_get_by_name',
nsd_name=nsd_name,
auth_attr=vim_res['vim_auth'], )
nsd_template = yaml.safe_load(nsd_dict['attributes']['nsd'])
actual_nsd_template = new_nsd_template if new_nsd_template else nsd_template # noqa
if actual_nsd_template:
old_ns_id = old_mes['mes_mapping']['NS'][0]
ns_arg = {'ns': {'nsd_template': actual_nsd_template}}
self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_update',
ns_id=old_ns_id,
ns_dict=ns_arg,
auth_attr=vim_res['vim_auth'], )
if mesd_dict['imports'].get('vnffgds'):
# Todo: Support multiple VNFFGs
nfv_driver = None
nfv_driver = mesd_dict['imports']['nsds'].get('nfv_driver')
if not nfv_driver:
raise meso.NFVDriverNotFound(mesd_name=mesd_dict['name'])
nfv_driver = nfv_driver.lower()
vnffgd_name = new_mesd_mapping['VNFFGD'][0]
vnffgd_dict = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffgd_get_by_name',
vnffgd_name=vnffgd_name,
auth_attr=vim_res['vim_auth'], )
vnffgd_template = yaml.safe_load(
vnffgd_dict['attributes']['vnffgd'])
old_vnffg_id = old_mes['mes_mapping']['VNFFG'][0]
vnffg_arg = {'vnffg': {'vnffgd_template': vnffgd_template}}
self._nfv_drivers.invoke(
nfv_driver,
'vnffg_update',
vnffg_id=old_vnffg_id,
vnffg_dict=vnffg_arg,
auth_attr=vim_res['vim_auth'], )
mes_dict = super(MesoPlugin, self)._update_mes_pre(context, mes_id)
def _update_mes_wait(self_obj, mes_id):
args = dict()
mes_status = "ACTIVE"
ns_status = "PENDING_UPDATE"
vnffg_status = "PENDING_UPDATE"
mec_status = "PENDING_UPDATE"
ns_retries = NS_RETRIES
mec_retries = MEC_RETRIES
vnffg_retries = VNFFG_RETRIES
error_reason_meca = None
error_reason_ns = None
error_reason_vnffg = None
# Check MECA
if mesd_dict['imports'].get('meads'):
while mec_status == "PENDING_UPDATE" and mec_retries > 0:
time.sleep(MEC_RETRY_WAIT)
meca_id = old_mes['mes_mapping']['MECA']
meca_list = meo_plugin.get_mecas(context)
is_deleted = True
for meca in meca_list:
if meca_id in meca['id']:
is_deleted = False
if is_deleted:
break
mec_status = meo_plugin.get_meca(context, meca_id)['status'] # noqa
LOG.debug('status: %s', mec_status)
if mec_status == 'ERROR':
break
mec_retries = mec_retries - 1
if mec_retries == 0 and mec_status == 'PENDING_UPDATE':
error_reason_meca = _(
"MES update is not completed within"
" {wait} seconds as update of MECA").format(
wait=MEC_RETRIES * MEC_RETRY_WAIT)
# Check NS/VNFFG status
if mesd_dict['imports'].get('nsds'):
while ns_status == "PENDING_UPDATE" and ns_retries > 0:
time.sleep(NS_RETRY_WAIT)
ns_list = old_mes['mes_mapping']['NS']
# Todo: support multiple NSs
is_existed = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_check',
ns_id=ns_list[0],
auth_attr=vim_res['vim_auth'], )
if not is_existed:
break
ns_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'ns_get',
ns_id=ns_list[0],
auth_attr=vim_res['vim_auth'], )
ns_status = ns_instance['status']
LOG.debug('status: %s', ns_status)
if ns_status == 'ERROR':
break
ns_retries = ns_retries - 1
if ns_retries == 0 and ns_status == 'PENDING_UPDATE':
error_reason_ns = _(
"MES update is not completed within"
" {wait} seconds as update of NS(s)").format(
wait=NS_RETRIES * NS_RETRY_WAIT)
if mesd_dict['imports'].get('vnffgds'):
while vnffg_status == "PENDING_UPDATE" and vnffg_retries > 0:
time.sleep(VNFFG_RETRY_WAIT)
vnffg_list = old_mes['mes_mapping']['VNFFG']
# Todo: support multiple VNFFGs
is_existed = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffg_check',
vnffg_id=vnffg_list[0],
auth_attr=vim_res['vim_auth'], )
if not is_existed:
break
vnffg_instance = self._nfv_drivers.invoke(
nfv_driver, # How to tell it is Tacker
'vnffg_get',
vnffg_id=vnffg_list[0],
auth_attr=vim_res['vim_auth'], )
vnffg_status = vnffg_instance['status']
LOG.debug('status: %s', vnffg_status)
if vnffg_status == 'ERROR':
break
vnffg_retries = vnffg_retries - 1
if vnffg_retries == 0 and vnffg_status == 'PENDING_UPDATE':
error_reason_vnffg = _(
"MES update is not completed within"
" {wait} seconds as update of VNFFG(s)").format(
wait=VNFFG_RETRIES * VNFFG_RETRY_WAIT)
args['NS'] = old_mes['reused']
if mec_status == "ERROR" or ns_status == "ERROR" or vnffg_status == "ERROR": # noqa
mes_status = "ERROR"
error_reason = None
for reason in [error_reason_meca, error_reason_ns, error_reason_vnffg]: # noqa
if reason:
error_reason = reason
mes_status = "PENDING_UPDATE"
super(MesoPlugin, self)._update_mes_post(context, mes_id, error_reason, mes_status, args) # noqa
self.spawn_n(_update_mes_wait, self, mes_dict['id'])
return mes_dict

View File

@ -4,6 +4,8 @@ wrap_width = 79
namespace = apmec.common.config
namespace = apmec.wsgi
namespace = apmec.service
namespace = apmec.meso.meso_plugin
namespace = apmec.meso.drivers.nfv_drivers.tacker_driver
namespace = apmec.meo.meo_plugin
namespace = apmec.meo.drivers.vim.openstack_driver
namespace = apmec.keymgr
@ -24,4 +26,4 @@ namespace = oslo.messaging
namespace = oslo.db
namespace = oslo.log
namespace = oslo.policy
namespace = oslo.service.service
namespace = oslo.service.service

View File

@ -44,7 +44,10 @@ apmec.service_plugins =
dummy = apmec.tests.unit.dummy_plugin:DummyServicePlugin
mem = apmec.mem.plugin:MEMPlugin
meo = apmec.meo.meo_plugin:MeoPlugin
meso = apmec.meso.meso_plugin:MesoPlugin
commonservices = apmec.plugins.common_services.common_services_plugin:CommonServicesPlugin
apmec.meso.drivers =
tacker = apmec.meso.drivers.nfv_drivers.tacker_driver:Tacker_Driver
apmec.meo.vim.drivers =
openstack = apmec.meo.drivers.vim.openstack_driver:OpenStack_Driver
apmec.openstack.common.cache.backends =
@ -71,6 +74,8 @@ oslo.config.opts =
apmec.service = apmec.service:config_opts
apmec.meo.meo_plugin = apmec.meo.meo_plugin:config_opts
apmec.meo.drivers.vim.openstack_driver = apmec.meo.drivers.vim.openstack_driver:config_opts
apmec.meso.meso_plugin = apmec.meso.meso_plugin:config_opts
apmec.mseo.drivers.nfv_drivers.tacker_driver = apmec.meso.drivers.nfv_drivers.tacker_driver:config_opts
apmec.keymgr = apmec.keymgr:config_opts
apmec.mem.monitor = apmec.mem.monitor:config_opts
apmec.mem.plugin = apmec.mem.plugin:config_opts