Add new policy action: vdu_autoheal
This patch adds below functionality: - New policy action ``vdu_autoheal`` for recovering failed VDUs as reported by the respective monitoring driver configured in the monitoring policy of the VNFD template. - Add unit/functional tests. - Added oslo_versioned library to implement HealVnfRequest object. Note: The implementation of vdu_autoheal policy action will support HealVnfRequest interface as mentioned in the ETSI standard [1] [1]: https://www.etsi.org/deliver/etsi_gs/NFV-SOL/001_099/003/02.05.01_60/gs_NFV-SOL003v020501p.pdf Implements: blueprint vdu-auto-healing Change-Id: If62acbdac41c92842de0ae3b7dedcda9fd1f86e6
This commit is contained in:
parent
4b9bcfeeef
commit
5f1e48ff46
|
@ -83,7 +83,13 @@ The available actions that a monitor driver can call when a particular event
|
||||||
occurs.
|
occurs.
|
||||||
|
|
||||||
#. respawn
|
#. respawn
|
||||||
|
In case of OpenStack VIM, when any VDU monitoring fails, it will delete
|
||||||
|
the entire VNF and create a new one.
|
||||||
#. log
|
#. log
|
||||||
|
#. vdu_autoheal
|
||||||
|
In case of OpenStack VIM, when any VDU monitoring fails, it will delete
|
||||||
|
only that specific VDU resource and create a new one alone with it's
|
||||||
|
dependent resources like CP.
|
||||||
|
|
||||||
How to write TOSCA template to monitor VNF entities
|
How to write TOSCA template to monitor VNF entities
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
@ -134,3 +140,9 @@ Example Template
|
||||||
max_foo_reached: scale_up
|
max_foo_reached: scale_up
|
||||||
min_foo_reached: scale_down
|
min_foo_reached: scale_down
|
||||||
|
|
||||||
|
vdu3:
|
||||||
|
monitoring_policy:
|
||||||
|
ping:
|
||||||
|
actions:
|
||||||
|
failure: vdu_autoheal
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,7 @@ oslo.serialization==2.18.0
|
||||||
oslo.service==1.24.0
|
oslo.service==1.24.0
|
||||||
oslo.upgradecheck==0.1.0
|
oslo.upgradecheck==0.1.0
|
||||||
oslo.utils==3.33.0
|
oslo.utils==3.33.0
|
||||||
|
oslo.versionedobjects==1.33.3
|
||||||
oslotest==3.2.0
|
oslotest==3.2.0
|
||||||
packaging==17.1
|
packaging==17.1
|
||||||
paramiko==2.0.0
|
paramiko==2.0.0
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added a new monitoring policy action ``vdu_autoheal`` to bring back the
|
||||||
|
failed VDU. If a VNF contains one or more VDUs with monitoring policy
|
||||||
|
action set to `vdu_autoheal` and if any one of the VDU is unreachable,
|
||||||
|
it will simply delete the resources of that particular VDUs and re-create
|
||||||
|
them again.
|
||||||
|
|
||||||
|
The `vdu_autoheal` monitoring policy action is implemented only for
|
||||||
|
openstack infra driver.
|
|
@ -34,6 +34,7 @@ oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||||
oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
|
oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
|
||||||
oslo.upgradecheck>=0.1.0 # Apache-2.0
|
oslo.upgradecheck>=0.1.0 # Apache-2.0
|
||||||
oslo.utils>=3.33.0 # Apache-2.0
|
oslo.utils>=3.33.0 # Apache-2.0
|
||||||
|
oslo.versionedobjects>=1.33.3 # Apache-2.0
|
||||||
openstackdocstheme>=1.18.1 # Apache-2.0
|
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||||
python-neutronclient>=6.7.0 # Apache-2.0
|
python-neutronclient>=6.7.0 # Apache-2.0
|
||||||
python-novaclient>=9.1.0 # Apache-2.0
|
python-novaclient>=9.1.0 # Apache-2.0
|
||||||
|
|
|
@ -73,6 +73,7 @@ tacker.tacker.policy.actions =
|
||||||
respawn = tacker.vnfm.policy_actions.respawn.respawn:VNFActionRespawn
|
respawn = tacker.vnfm.policy_actions.respawn.respawn:VNFActionRespawn
|
||||||
log = tacker.vnfm.policy_actions.log.log:VNFActionLog
|
log = tacker.vnfm.policy_actions.log.log:VNFActionLog
|
||||||
log_and_kill = tacker.vnfm.policy_actions.log.log:VNFActionLogAndKill
|
log_and_kill = tacker.vnfm.policy_actions.log.log:VNFActionLogAndKill
|
||||||
|
vdu_autoheal = tacker.vnfm.policy_actions.vdu_autoheal.vdu_autoheal:VNFActionVduAutoheal
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
tacker.common.config = tacker.common.config:config_opts
|
tacker.common.config = tacker.common.config:config_opts
|
||||||
tacker.wsgi = tacker.wsgi:config_opts
|
tacker.wsgi = tacker.wsgi:config_opts
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from tacker.conductor import conductor_server
|
from tacker.conductor import conductor_server
|
||||||
|
from tacker import objects
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
objects.register_all()
|
||||||
conductor_server.main()
|
conductor_server.main()
|
||||||
|
|
|
@ -25,6 +25,7 @@ import oslo_i18n
|
||||||
from oslo_service import service as common_service
|
from oslo_service import service as common_service
|
||||||
|
|
||||||
from tacker.common import config
|
from tacker.common import config
|
||||||
|
from tacker import objects
|
||||||
from tacker import service
|
from tacker import service
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ oslo_i18n.install("tacker")
|
||||||
def main():
|
def main():
|
||||||
# the configuration will be read into the cfg.CONF global data structure
|
# the configuration will be read into the cfg.CONF global data structure
|
||||||
config.init(sys.argv[1:])
|
config.init(sys.argv[1:])
|
||||||
|
objects.register_all()
|
||||||
if not cfg.CONF.config_file:
|
if not cfg.CONF.config_file:
|
||||||
sys.exit(_("ERROR: Unable to find configuration file via the default"
|
sys.exit(_("ERROR: Unable to find configuration file via the default"
|
||||||
" search paths (~/.tacker/, ~/, /etc/tacker/, /etc/) and"
|
" search paths (~/.tacker/, ~/, /etc/tacker/, /etc/) and"
|
||||||
|
|
|
@ -28,6 +28,7 @@ from tacker.db.common_services import common_services_db
|
||||||
from tacker.db.nfvo import nfvo_db
|
from tacker.db.nfvo import nfvo_db
|
||||||
from tacker.extensions import nfvo
|
from tacker.extensions import nfvo
|
||||||
from tacker import manager
|
from tacker import manager
|
||||||
|
from tacker import objects
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
from tacker import service as tacker_service
|
from tacker import service as tacker_service
|
||||||
from tacker import version
|
from tacker import version
|
||||||
|
@ -80,6 +81,7 @@ def init(args, **kwargs):
|
||||||
|
|
||||||
def main(manager='tacker.conductor.conductor_server.Conductor'):
|
def main(manager='tacker.conductor.conductor_server.Conductor'):
|
||||||
init(sys.argv[1:])
|
init(sys.argv[1:])
|
||||||
|
objects.register_all()
|
||||||
logging.setup(cfg.CONF, "tacker")
|
logging.setup(cfg.CONF, "tacker")
|
||||||
oslo_messaging.set_transport_defaults(control_exchange='tacker')
|
oslo_messaging.set_transport_defaults(control_exchange='tacker')
|
||||||
logging.setup(cfg.CONF, "tacker")
|
logging.setup(cfg.CONF, "tacker")
|
||||||
|
|
|
@ -40,11 +40,12 @@ from tacker import manager
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
_ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE)
|
_ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE,
|
||||||
|
constants.PENDING_HEAL)
|
||||||
_ACTIVE_UPDATE_ERROR_DEAD = (
|
_ACTIVE_UPDATE_ERROR_DEAD = (
|
||||||
constants.PENDING_CREATE, constants.ACTIVE, constants.PENDING_UPDATE,
|
constants.PENDING_CREATE, constants.ACTIVE, constants.PENDING_UPDATE,
|
||||||
constants.PENDING_SCALE_IN, constants.PENDING_SCALE_OUT, constants.ERROR,
|
constants.PENDING_SCALE_IN, constants.PENDING_SCALE_OUT, constants.ERROR,
|
||||||
constants.PENDING_DELETE, constants.DEAD)
|
constants.PENDING_DELETE, constants.DEAD, constants.PENDING_HEAL)
|
||||||
CREATE_STATES = (constants.PENDING_CREATE, constants.DEAD)
|
CREATE_STATES = (constants.PENDING_CREATE, constants.DEAD)
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,7 +499,8 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||||
"is not permited. Please contact your "
|
"is not permited. Please contact your "
|
||||||
"Administrator.")
|
"Administrator.")
|
||||||
raise vnfm.VNFDeleteFailed(reason=error_reason)
|
raise vnfm.VNFDeleteFailed(reason=error_reason)
|
||||||
if vnf_db.status == constants.PENDING_UPDATE:
|
if(vnf_db.status in [constants.PENDING_UPDATE,
|
||||||
|
constants.PENDING_HEAL]):
|
||||||
raise vnfm.VNFInUse(vnf_id=vnf_id)
|
raise vnfm.VNFInUse(vnf_id=vnf_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -522,28 +524,30 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||||
tstamp=timeutils.utcnow())
|
tstamp=timeutils.utcnow())
|
||||||
return updated_vnf_dict
|
return updated_vnf_dict
|
||||||
|
|
||||||
def _update_vnf_pre(self, context, vnf_id):
|
def _update_vnf_pre(self, context, vnf_id, new_status):
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
vnf_db = self._update_vnf_status_db(
|
vnf_db = self._update_vnf_status_db(
|
||||||
context, vnf_id, _ACTIVE_UPDATE, constants.PENDING_UPDATE)
|
context, vnf_id, _ACTIVE_UPDATE, new_status)
|
||||||
updated_vnf_dict = self._make_vnf_dict(vnf_db)
|
updated_vnf_dict = self._make_vnf_dict(vnf_db)
|
||||||
self._cos_db_plg.create_event(
|
if new_status in constants.VNF_STATUS_TO_EVT_TYPES:
|
||||||
context, res_id=vnf_id,
|
self._cos_db_plg.create_event(
|
||||||
res_type=constants.RES_TYPE_VNF,
|
context, res_id=vnf_id,
|
||||||
res_state=updated_vnf_dict['status'],
|
res_type=constants.RES_TYPE_VNF,
|
||||||
evt_type=constants.RES_EVT_UPDATE,
|
res_state=updated_vnf_dict['status'],
|
||||||
tstamp=timeutils.utcnow())
|
evt_type=constants.VNF_STATUS_TO_EVT_TYPES[new_status],
|
||||||
|
tstamp=timeutils.utcnow())
|
||||||
return updated_vnf_dict
|
return updated_vnf_dict
|
||||||
|
|
||||||
def _update_vnf_post(self, context, vnf_id, new_status,
|
def _update_vnf_post(self, context, vnf_id, new_status,
|
||||||
new_vnf_dict):
|
new_vnf_dict, vnf_status, evt_type):
|
||||||
updated_time_stamp = timeutils.utcnow()
|
updated_time_stamp = timeutils.utcnow()
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
(self._model_query(context, VNF).
|
(self._model_query(context, VNF).
|
||||||
filter(VNF.id == vnf_id).
|
filter(VNF.id == vnf_id).
|
||||||
filter(VNF.status == constants.PENDING_UPDATE).
|
filter(VNF.status == vnf_status).
|
||||||
update({'status': new_status,
|
update({'status': new_status,
|
||||||
'updated_at': updated_time_stamp}))
|
'updated_at': updated_time_stamp,
|
||||||
|
'mgmt_url': new_vnf_dict['mgmt_url']}))
|
||||||
|
|
||||||
dev_attrs = new_vnf_dict.get('attributes', {})
|
dev_attrs = new_vnf_dict.get('attributes', {})
|
||||||
(context.session.query(VNFAttribute).
|
(context.session.query(VNFAttribute).
|
||||||
|
@ -559,7 +563,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||||
context, res_id=vnf_id,
|
context, res_id=vnf_id,
|
||||||
res_type=constants.RES_TYPE_VNF,
|
res_type=constants.RES_TYPE_VNF,
|
||||||
res_state=new_status,
|
res_state=new_status,
|
||||||
evt_type=constants.RES_EVT_UPDATE,
|
evt_type=evt_type,
|
||||||
tstamp=updated_time_stamp)
|
tstamp=updated_time_stamp)
|
||||||
|
|
||||||
def _delete_vnf_pre(self, context, vnf_id):
|
def _delete_vnf_pre(self, context, vnf_id):
|
||||||
|
@ -635,7 +639,8 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||||
|
|
||||||
# reference implementation. needs to be overrided by subclass
|
# reference implementation. needs to be overrided by subclass
|
||||||
def update_vnf(self, context, vnf_id, vnf):
|
def update_vnf(self, context, vnf_id, vnf):
|
||||||
vnf_dict = self._update_vnf_pre(context, vnf_id)
|
new_status = constants.PENDING_UPDATE
|
||||||
|
vnf_dict = self._update_vnf_pre(context, vnf_id, new_status)
|
||||||
# start actual update of hosting vnf
|
# start actual update of hosting vnf
|
||||||
# waiting for completion of update should be done backgroundly
|
# waiting for completion of update should be done backgroundly
|
||||||
# by another thread if it takes a while
|
# by another thread if it takes a while
|
||||||
|
|
|
@ -63,6 +63,10 @@ class VNFCreateFailed(exceptions.TackerException):
|
||||||
message = _('creating VNF based on %(vnfd_id)s failed')
|
message = _('creating VNF based on %(vnfd_id)s failed')
|
||||||
|
|
||||||
|
|
||||||
|
class VNFUpdateWaitFailed(exceptions.TackerException):
|
||||||
|
message = _('%(reason)s')
|
||||||
|
|
||||||
|
|
||||||
class VNFCreateWaitFailed(exceptions.TackerException):
|
class VNFCreateWaitFailed(exceptions.TackerException):
|
||||||
message = _('%(reason)s')
|
message = _('%(reason)s')
|
||||||
|
|
||||||
|
@ -79,6 +83,10 @@ class VNFDeleteFailed(exceptions.TackerException):
|
||||||
message = _('%(reason)s')
|
message = _('%(reason)s')
|
||||||
|
|
||||||
|
|
||||||
|
class VNFHealFailed(exceptions.TackerException):
|
||||||
|
message = _('VNF %(vnf_id)s failed to heal')
|
||||||
|
|
||||||
|
|
||||||
class VNFDNotFound(exceptions.NotFound):
|
class VNFDNotFound(exceptions.NotFound):
|
||||||
message = _('VNFD %(vnfd_id)s could not be found')
|
message = _('VNFD %(vnfd_id)s could not be found')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright 2018 NTT DATA.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# NOTE(bhagyashris): You may scratch your head as you see code that imports
|
||||||
|
# this module and then accesses attributes for objects such as Instance,
|
||||||
|
# etc, yet you do not see these attributes in here. Never fear, there is
|
||||||
|
# a little bit of magic. When objects are registered, an attribute is set
|
||||||
|
# on this module automatically, pointing to the newest/latest version of
|
||||||
|
# the object.
|
||||||
|
|
||||||
|
|
||||||
|
def register_all():
|
||||||
|
# NOTE(bhagyashris): You must make sure your object gets imported in this
|
||||||
|
# function in order for it to be registered by services that may
|
||||||
|
# need to receive it via RPC.
|
||||||
|
__import__('tacker.objects.heal_vnf_request')
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Copyright 2018 NTT DATA.
|
||||||
|
#
|
||||||
|
# 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 oslo_utils import versionutils
|
||||||
|
from oslo_versionedobjects import base as ovoo_base
|
||||||
|
|
||||||
|
from tacker import objects
|
||||||
|
|
||||||
|
|
||||||
|
def get_attrname(name):
|
||||||
|
"""Return the mangled name of the attribute's underlying storage."""
|
||||||
|
return '_obj_' + name
|
||||||
|
|
||||||
|
|
||||||
|
class TackerObjectRegistry(ovoo_base.VersionedObjectRegistry):
|
||||||
|
notification_classes = []
|
||||||
|
|
||||||
|
def registration_hook(self, cls, index):
|
||||||
|
# NOTE(bhagyashris): This is called when an object is registered,
|
||||||
|
# and is responsible for maintaining tacker.objects.$OBJECT
|
||||||
|
# as the highest-versioned implementation of a given object.
|
||||||
|
version = versionutils.convert_version_to_tuple(cls.VERSION)
|
||||||
|
if not hasattr(objects, cls.obj_name()):
|
||||||
|
setattr(objects, cls.obj_name(), cls)
|
||||||
|
else:
|
||||||
|
cur_version = versionutils.convert_version_to_tuple(
|
||||||
|
getattr(objects, cls.obj_name()).VERSION)
|
||||||
|
if version >= cur_version:
|
||||||
|
setattr(objects, cls.obj_name(), cls)
|
||||||
|
|
||||||
|
|
||||||
|
class TackerObject(ovoo_base.VersionedObject):
|
||||||
|
# NOTE(bhagyashris): OBJ_PROJECT_NAMESPACE needs to be set so that nova,
|
||||||
|
# tacker, and other objects can exist on the same bus and be distinguished
|
||||||
|
# from one another.
|
||||||
|
OBJ_SERIAL_NAMESPACE = 'tacker_object'
|
||||||
|
OBJ_PROJECT_NAMESPACE = 'tacker'
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright 2018 NTT Data.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_versionedobjects import fields
|
||||||
|
|
||||||
|
|
||||||
|
# Import fields from oslo.versionedobjects
|
||||||
|
StringField = fields.StringField
|
||||||
|
ListOfObjectsField = fields.ListOfObjectsField
|
||||||
|
ListOfStringsField = fields.ListOfStringsField
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Copyright 2018 NTT Data.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from tacker.objects import base
|
||||||
|
from tacker.objects import fields
|
||||||
|
|
||||||
|
|
||||||
|
@base.TackerObjectRegistry.register
|
||||||
|
class HealVnfAdditionalParams(base.TackerObject):
|
||||||
|
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'parameter': fields.StringField(),
|
||||||
|
'cause': fields.ListOfStringsField()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@base.TackerObjectRegistry.register
|
||||||
|
class HealVnfRequest(base.TackerObject):
|
||||||
|
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'cause': fields.StringField(),
|
||||||
|
'additional_params': fields.ListOfObjectsField(
|
||||||
|
'HealVnfAdditionalParams')
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ PENDING_UPDATE = "PENDING_UPDATE"
|
||||||
PENDING_DELETE = "PENDING_DELETE"
|
PENDING_DELETE = "PENDING_DELETE"
|
||||||
PENDING_SCALE_IN = "PENDING_SCALE_IN"
|
PENDING_SCALE_IN = "PENDING_SCALE_IN"
|
||||||
PENDING_SCALE_OUT = "PENDING_SCALE_OUT"
|
PENDING_SCALE_OUT = "PENDING_SCALE_OUT"
|
||||||
|
PENDING_HEAL = "PENDING_HEAL"
|
||||||
|
|
||||||
DEAD = "DEAD"
|
DEAD = "DEAD"
|
||||||
ERROR = "ERROR"
|
ERROR = "ERROR"
|
||||||
|
@ -67,6 +68,14 @@ RES_EVT_MONITOR = "MONITOR"
|
||||||
RES_EVT_SCALE = "SCALE"
|
RES_EVT_SCALE = "SCALE"
|
||||||
RES_EVT_NA_STATE = "Not Applicable"
|
RES_EVT_NA_STATE = "Not Applicable"
|
||||||
RES_EVT_ONBOARDED = "OnBoarded"
|
RES_EVT_ONBOARDED = "OnBoarded"
|
||||||
|
RES_EVT_HEAL = "HEAL"
|
||||||
|
|
||||||
|
|
||||||
|
VNF_STATUS_TO_EVT_TYPES = {PENDING_CREATE: RES_EVT_CREATE,
|
||||||
|
PENDING_UPDATE: RES_EVT_UPDATE,
|
||||||
|
PENDING_DELETE: RES_EVT_DELETE,
|
||||||
|
PENDING_HEAL: RES_EVT_HEAL}
|
||||||
|
|
||||||
|
|
||||||
RES_EVT_CREATED_FLD = "created_at"
|
RES_EVT_CREATED_FLD = "created_at"
|
||||||
RES_EVT_DELETED_FLD = "deleted_at"
|
RES_EVT_DELETED_FLD = "deleted_at"
|
||||||
|
|
|
@ -22,3 +22,9 @@ SCALE_WINDOW_SLEEP_TIME = 120
|
||||||
NS_CREATE_TIMEOUT = 400
|
NS_CREATE_TIMEOUT = 400
|
||||||
NS_DELETE_TIMEOUT = 300
|
NS_DELETE_TIMEOUT = 300
|
||||||
NOVA_CLIENT_VERSION = 2
|
NOVA_CLIENT_VERSION = 2
|
||||||
|
VDU_MARK_UNHEALTHY_TIMEOUT = 500
|
||||||
|
VDU_MARK_UNHEALTHY_SLEEP_TIME = 3
|
||||||
|
VDU_AUTOHEALING_TIMEOUT = 500
|
||||||
|
VDU_AUTOHEALING_SLEEP_TIME = 3
|
||||||
|
VNF_CIRROS_PENDING_HEAL_TIMEOUT = 300
|
||||||
|
PENDING_SLEEP_TIME = 3
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||||
|
description: Demo example
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
template_name: sample-tosca-vnfd
|
||||||
|
|
||||||
|
topology_template:
|
||||||
|
node_templates:
|
||||||
|
VDU1:
|
||||||
|
type: tosca.nodes.nfv.VDU.Tacker
|
||||||
|
capabilities:
|
||||||
|
nfv_compute:
|
||||||
|
properties:
|
||||||
|
disk_size: 1 GB
|
||||||
|
mem_size: 512 MB
|
||||||
|
num_cpus: 1
|
||||||
|
properties:
|
||||||
|
image: cirros-0.4.0-x86_64-disk
|
||||||
|
mgmt_driver: noop
|
||||||
|
availability_zone: nova
|
||||||
|
mgmt_driver: noop
|
||||||
|
monitoring_policy:
|
||||||
|
name: ping
|
||||||
|
parameters:
|
||||||
|
monitoring_delay: 45
|
||||||
|
count: 3
|
||||||
|
interval: 1
|
||||||
|
timeout: 2
|
||||||
|
actions:
|
||||||
|
failure: vdu_autoheal
|
||||||
|
|
||||||
|
CP1:
|
||||||
|
type: tosca.nodes.nfv.CP.Tacker
|
||||||
|
properties:
|
||||||
|
management: true
|
||||||
|
anti_spoofing_protection: false
|
||||||
|
requirements:
|
||||||
|
- virtualLink:
|
||||||
|
node: VL1
|
||||||
|
- virtualBinding:
|
||||||
|
node: VDU1
|
||||||
|
|
||||||
|
VDU2:
|
||||||
|
type: tosca.nodes.nfv.VDU.Tacker
|
||||||
|
capabilities:
|
||||||
|
nfv_compute:
|
||||||
|
properties:
|
||||||
|
disk_size: 1 GB
|
||||||
|
mem_size: 512 MB
|
||||||
|
num_cpus: 1
|
||||||
|
properties:
|
||||||
|
image: cirros-0.4.0-x86_64-disk
|
||||||
|
mgmt_driver: noop
|
||||||
|
availability_zone: nova
|
||||||
|
mgmt_driver: noop
|
||||||
|
monitoring_policy:
|
||||||
|
name: ping
|
||||||
|
parameters:
|
||||||
|
monitoring_delay: 45
|
||||||
|
count: 3
|
||||||
|
interval: 1
|
||||||
|
timeout: 2
|
||||||
|
actions:
|
||||||
|
failure: vdu_autoheal
|
||||||
|
user_data_format: RAW
|
||||||
|
user_data: |
|
||||||
|
#!/bin/sh
|
||||||
|
echo "my hostname is `hostname`" > /tmp/hostname
|
||||||
|
df -h > /home/cirros/diskinfo
|
||||||
|
sleep 90
|
||||||
|
sudo ifdown eth0
|
||||||
|
|
||||||
|
CP2:
|
||||||
|
type: tosca.nodes.nfv.CP.Tacker
|
||||||
|
properties:
|
||||||
|
management: true
|
||||||
|
anti_spoofing_protection: false
|
||||||
|
requirements:
|
||||||
|
- virtualLink:
|
||||||
|
node: VL1
|
||||||
|
- virtualBinding:
|
||||||
|
node: VDU2
|
||||||
|
|
||||||
|
VL1:
|
||||||
|
type: tosca.nodes.nfv.VL
|
||||||
|
properties:
|
||||||
|
network_name: net_mgmt
|
||||||
|
vendor: Tacker
|
|
@ -0,0 +1,55 @@
|
||||||
|
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||||
|
description: Demo example
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
template_name: sample-tosca-vnfd
|
||||||
|
|
||||||
|
topology_template:
|
||||||
|
node_templates:
|
||||||
|
VDU1:
|
||||||
|
type: tosca.nodes.nfv.VDU.Tacker
|
||||||
|
capabilities:
|
||||||
|
nfv_compute:
|
||||||
|
properties:
|
||||||
|
disk_size: 1 GB
|
||||||
|
mem_size: 512 MB
|
||||||
|
num_cpus: 1
|
||||||
|
properties:
|
||||||
|
image: cirros-0.4.0-x86_64-disk
|
||||||
|
mgmt_driver: noop
|
||||||
|
availability_zone: nova
|
||||||
|
mgmt_driver: noop
|
||||||
|
monitoring_policy:
|
||||||
|
name: ping
|
||||||
|
parameters:
|
||||||
|
monitoring_delay: 45
|
||||||
|
count: 3
|
||||||
|
interval: 1
|
||||||
|
timeout: 2
|
||||||
|
actions:
|
||||||
|
failure: vdu_autoheal
|
||||||
|
user_data_format: RAW
|
||||||
|
user_data: |
|
||||||
|
#!/bin/sh
|
||||||
|
echo "my hostname is `hostname`" > /tmp/hostname
|
||||||
|
df -h > /home/cirros/diskinfo
|
||||||
|
sleep 90
|
||||||
|
sudo ifdown eth0
|
||||||
|
|
||||||
|
CP1:
|
||||||
|
type: tosca.nodes.nfv.CP.Tacker
|
||||||
|
properties:
|
||||||
|
management: true
|
||||||
|
anti_spoofing_protection: false
|
||||||
|
requirements:
|
||||||
|
- virtualLink:
|
||||||
|
node: VL1
|
||||||
|
- virtualBinding:
|
||||||
|
node: VDU1
|
||||||
|
|
||||||
|
VL1:
|
||||||
|
type: tosca.nodes.nfv.VL
|
||||||
|
properties:
|
||||||
|
network_name: net_mgmt
|
||||||
|
vendor: Tacker
|
||||||
|
|
|
@ -119,6 +119,9 @@ class BaseTackerTest(base.BaseTestCase):
|
||||||
auth_ses = session.Session(auth=auth, verify=verify)
|
auth_ses = session.Session(auth=auth, verify=verify)
|
||||||
return glance_client.Client(session=auth_ses)
|
return glance_client.Client(session=auth_ses)
|
||||||
|
|
||||||
|
def get_vdu_resource(self, stack_id, res_name):
|
||||||
|
return self.h_client.resources.get(stack_id, res_name)
|
||||||
|
|
||||||
def wait_until_vnf_status(self, vnf_id, target_status, timeout,
|
def wait_until_vnf_status(self, vnf_id, target_status, timeout,
|
||||||
sleep_interval):
|
sleep_interval):
|
||||||
start_time = int(time.time())
|
start_time = int(time.time())
|
||||||
|
@ -140,6 +143,17 @@ class BaseTackerTest(base.BaseTestCase):
|
||||||
self.wait_until_vnf_status(vnf_id, 'ACTIVE', timeout,
|
self.wait_until_vnf_status(vnf_id, 'ACTIVE', timeout,
|
||||||
sleep_interval)
|
sleep_interval)
|
||||||
|
|
||||||
|
def verify_vnf_update(self, vnf_id):
|
||||||
|
self.wait_until_vnf_status(vnf_id, 'ACTIVE',
|
||||||
|
constants.VNF_CIRROS_CREATE_TIMEOUT,
|
||||||
|
constants.ACTIVE_SLEEP_TIME)
|
||||||
|
self.wait_until_vnf_status(vnf_id, 'PENDING_HEAL',
|
||||||
|
constants.VNF_CIRROS_PENDING_HEAL_TIMEOUT,
|
||||||
|
constants.PENDING_SLEEP_TIME)
|
||||||
|
self.wait_until_vnf_status(vnf_id, 'ACTIVE',
|
||||||
|
constants.VNF_CIRROS_CREATE_TIMEOUT,
|
||||||
|
constants.ACTIVE_SLEEP_TIME)
|
||||||
|
|
||||||
def wait_until_vnf_delete(self, vnf_id, timeout):
|
def wait_until_vnf_delete(self, vnf_id, timeout):
|
||||||
start_time = int(time.time())
|
start_time = int(time.time())
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -20,7 +20,7 @@ from tacker.tests.utils import read_file
|
||||||
|
|
||||||
class VnfTestPingMonitor(base.BaseTackerTest):
|
class VnfTestPingMonitor(base.BaseTackerTest):
|
||||||
|
|
||||||
def _test_vnf_with_monitoring(self, vnfd_file, vnf_name):
|
def _vnfd_and_vnf_create(self, vnfd_file, vnf_name):
|
||||||
data = dict()
|
data = dict()
|
||||||
data['tosca'] = read_file(vnfd_file)
|
data['tosca'] = read_file(vnfd_file)
|
||||||
toscal = data['tosca']
|
toscal = data['tosca']
|
||||||
|
@ -36,6 +36,15 @@ class VnfTestPingMonitor(base.BaseTackerTest):
|
||||||
vnf_arg = {'vnf': {'vnfd_id': vnfd_id, 'name': vnf_name}}
|
vnf_arg = {'vnf': {'vnfd_id': vnfd_id, 'name': vnf_name}}
|
||||||
vnf_instance = self.client.create_vnf(body=vnf_arg)
|
vnf_instance = self.client.create_vnf(body=vnf_arg)
|
||||||
|
|
||||||
|
# Delete vnfd_instance
|
||||||
|
self.addCleanup(self.client.delete_vnfd, vnfd_id)
|
||||||
|
|
||||||
|
return vnfd_instance, vnf_instance
|
||||||
|
|
||||||
|
def _test_vnf_with_monitoring(self, vnfd_file, vnf_name):
|
||||||
|
vnfd_instance, vnf_instance = self._vnfd_and_vnf_create(vnfd_file,
|
||||||
|
vnf_name)
|
||||||
|
|
||||||
# Verify vnf goes from ACTIVE->DEAD->ACTIVE states
|
# Verify vnf goes from ACTIVE->DEAD->ACTIVE states
|
||||||
self.verify_vnf_restart(vnfd_instance, vnf_instance)
|
self.verify_vnf_restart(vnfd_instance, vnf_instance)
|
||||||
|
|
||||||
|
@ -51,8 +60,6 @@ class VnfTestPingMonitor(base.BaseTackerTest):
|
||||||
vnf_state_list = [evt_constants.ACTIVE, evt_constants.DEAD]
|
vnf_state_list = [evt_constants.ACTIVE, evt_constants.DEAD]
|
||||||
self.verify_vnf_monitor_events(vnf_id, vnf_state_list)
|
self.verify_vnf_monitor_events(vnf_id, vnf_state_list)
|
||||||
|
|
||||||
# Delete vnfd_instance
|
|
||||||
self.addCleanup(self.client.delete_vnfd, vnfd_id)
|
|
||||||
self.addCleanup(self.wait_until_vnf_delete, vnf_id,
|
self.addCleanup(self.wait_until_vnf_delete, vnf_id,
|
||||||
constants.VNF_CIRROS_DELETE_TIMEOUT)
|
constants.VNF_CIRROS_DELETE_TIMEOUT)
|
||||||
|
|
||||||
|
@ -65,3 +72,39 @@ class VnfTestPingMonitor(base.BaseTackerTest):
|
||||||
self._test_vnf_with_monitoring(
|
self._test_vnf_with_monitoring(
|
||||||
'sample-tosca-vnfd-multi-vdu-monitoring.yaml',
|
'sample-tosca-vnfd-multi-vdu-monitoring.yaml',
|
||||||
'ping monitor multi vdu vnf with tosca template')
|
'ping monitor multi vdu vnf with tosca template')
|
||||||
|
|
||||||
|
def _test_vnf_with_monitoring_vdu_autoheal_action(
|
||||||
|
self, vnfd_file, vnf_name):
|
||||||
|
vnfd_instance, vnf_instance = self._vnfd_and_vnf_create(vnfd_file,
|
||||||
|
vnf_name)
|
||||||
|
vnf_id = vnf_instance['vnf']['id']
|
||||||
|
|
||||||
|
self.verify_vnf_update(vnf_id)
|
||||||
|
|
||||||
|
# Delete vnf_instance with vnf_id
|
||||||
|
try:
|
||||||
|
self.client.delete_vnf(vnf_id)
|
||||||
|
except Exception:
|
||||||
|
assert False, ("Failed to delete vnf %s after the monitor test" %
|
||||||
|
vnf_id)
|
||||||
|
self.addCleanup(self.wait_until_vnf_delete, vnf_id,
|
||||||
|
constants.VNF_CIRROS_DELETE_TIMEOUT)
|
||||||
|
|
||||||
|
params = {'resource_id': vnf_id,
|
||||||
|
'resource_state': 'PENDING_UPDATE',
|
||||||
|
'event_type': evt_constants.RES_EVT_MONITOR}
|
||||||
|
vnf_events = self.client.list_vnf_events(**params)
|
||||||
|
# Check if vdu_autoheal action emits 4 monitoring events.
|
||||||
|
self.assertGreaterEqual(4, len(vnf_events['vnf_events']))
|
||||||
|
|
||||||
|
def test_vnf_monitoring_with_vdu_autoheal_action_for_multi_vdu(self):
|
||||||
|
self._test_vnf_with_monitoring_vdu_autoheal_action(
|
||||||
|
'sample-tosca-vnfd-multi-vdu-monitoring-vdu-autoheal.yaml',
|
||||||
|
'ping multi vdu monitor having vdu_autoheal failure action '
|
||||||
|
'with tosca template')
|
||||||
|
|
||||||
|
def test_vnf_monitoring_with_vdu_autoheal_action_for_single_vdu(self):
|
||||||
|
self._test_vnf_with_monitoring_vdu_autoheal_action(
|
||||||
|
'sample-tosca-vnfd-single-vdu-monitoring-vdu-autoheal.yaml',
|
||||||
|
'ping vdu monitor having vdu_autoheal failure action '
|
||||||
|
'with tosca template')
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Copyright 2018 NTT DATA.
|
||||||
|
#
|
||||||
|
# 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 six
|
||||||
|
|
||||||
|
from tacker.objects import base
|
||||||
|
from tacker.objects import fields
|
||||||
|
from tacker.tests.unit import base as test_base
|
||||||
|
|
||||||
|
|
||||||
|
class TestString(test_base.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestString, self).setUp()
|
||||||
|
self.field = fields.StringField()
|
||||||
|
self.coerce_good_values = [('foo', 'foo'), (1, '1'), (True, 'True')]
|
||||||
|
if six.PY2:
|
||||||
|
self.coerce_good_values.append((int(1), '1'))
|
||||||
|
self.coerce_bad_values = [None]
|
||||||
|
|
||||||
|
def test_stringify(self):
|
||||||
|
self.assertEqual("'123'", self.field.stringify(123))
|
||||||
|
|
||||||
|
|
||||||
|
class TestListOfStrings(test_base.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestListOfStrings, self).setUp()
|
||||||
|
self.field = fields.ListOfStringsField()
|
||||||
|
|
||||||
|
def test_list_of_string(self):
|
||||||
|
self.assertEqual("['abc']", self.field.stringify(['abc']))
|
||||||
|
|
||||||
|
|
||||||
|
class TestListOfObjects(test_base.TestCase):
|
||||||
|
|
||||||
|
def test_list_of_obj(self):
|
||||||
|
@base.TackerObjectRegistry.register_if(False)
|
||||||
|
class MyObjElement(base.TackerObject):
|
||||||
|
fields = {'foo': fields.StringField()}
|
||||||
|
|
||||||
|
def __init__(self, foo):
|
||||||
|
super(MyObjElement, self).__init__()
|
||||||
|
self.foo = foo
|
||||||
|
|
||||||
|
@base.TackerObjectRegistry.register_if(False)
|
||||||
|
class MyList(base.TackerObject):
|
||||||
|
fields = {'objects': fields.ListOfObjectsField('MyObjElement')}
|
||||||
|
|
||||||
|
mylist = MyList()
|
||||||
|
mylist.objects = [MyObjElement('a'), MyObjElement('b')]
|
||||||
|
self.assertEqual(['a', 'b'], [x.foo for x in mylist.objects])
|
|
@ -20,12 +20,63 @@ import os
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from tacker import context
|
from tacker import context
|
||||||
|
from tacker.db.common_services import common_services_db_plugin
|
||||||
from tacker.extensions import vnfm
|
from tacker.extensions import vnfm
|
||||||
from tacker.tests.unit import base
|
from tacker.tests.unit import base
|
||||||
from tacker.tests.unit.db import utils
|
from tacker.tests.unit.db import utils
|
||||||
from tacker.vnfm.infra_drivers.openstack import openstack
|
from tacker.vnfm.infra_drivers.openstack import openstack
|
||||||
|
|
||||||
|
|
||||||
|
vnf_dict = {
|
||||||
|
'attributes': {
|
||||||
|
'heat_template': {
|
||||||
|
'outputs': {
|
||||||
|
'mgmt_ip-VDU1': {
|
||||||
|
'value': {
|
||||||
|
'get_attr': [
|
||||||
|
'CP1', 'fixed_ips', 0, 'ip_address']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'description': 'Demo example\n',
|
||||||
|
'parameters': {},
|
||||||
|
'resources': {
|
||||||
|
'VDU1': {
|
||||||
|
'type': 'OS::Nova::Server',
|
||||||
|
'properties': {
|
||||||
|
'user_data_format': 'SOFTWARE_CONFIG',
|
||||||
|
'availability_zone': 'nova',
|
||||||
|
'image': 'cirros-0.4.0-x86_64-disk',
|
||||||
|
'config_drive': False,
|
||||||
|
'flavor': {'get_resource': 'VDU1_flavor'},
|
||||||
|
'networks': [{'port': {'get_resource': 'CP1'}}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'CP1': {
|
||||||
|
'type': 'OS::Neutron::Port',
|
||||||
|
'properties': {
|
||||||
|
'port_security_enabled': False,
|
||||||
|
'network': 'net_mgmt'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'VDU1_flavor': {
|
||||||
|
'type': 'OS::Nova::Flavor',
|
||||||
|
'properties': {'vcpus': 1, 'disk': 1, 'ram': 512}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'vnfd_id': '576acf48-b9df-491d-a57c-342de660ec78',
|
||||||
|
'tenant_id': '13d2ca8de70d48b2a2e0dbac2c327c0b',
|
||||||
|
'vim_id': '3f41faa7-5630-47d2-9d4a-1216953c8887',
|
||||||
|
'instance_id': 'd1121d3c-368b-4ac2-b39d-835aa3e4ccd8',
|
||||||
|
'placement_attr': {'vim_name': 'openstack-vim'},
|
||||||
|
'id': 'a27fc58e-66ae-4031-bba4-efede318c60b',
|
||||||
|
'name': 'vnf_create_1'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class FakeHeatClient(mock.Mock):
|
class FakeHeatClient(mock.Mock):
|
||||||
|
|
||||||
class Stack(mock.Mock):
|
class Stack(mock.Mock):
|
||||||
|
@ -59,6 +110,11 @@ class TestOpenStack(base.TestCase):
|
||||||
self.context = context.get_admin_context()
|
self.context = context.get_admin_context()
|
||||||
self.infra_driver = openstack.OpenStack()
|
self.infra_driver = openstack.OpenStack()
|
||||||
self._mock_heat_client()
|
self._mock_heat_client()
|
||||||
|
mock.patch('tacker.db.common_services.common_services_db_plugin.'
|
||||||
|
'CommonServicesPluginDb.create_event'
|
||||||
|
).start()
|
||||||
|
self._cos_db_plugin = \
|
||||||
|
common_services_db_plugin.CommonServicesPluginDb()
|
||||||
self.addCleanup(mock.patch.stopall)
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
|
||||||
def _mock_heat_client(self):
|
def _mock_heat_client(self):
|
||||||
|
@ -181,6 +237,25 @@ class TestOpenStack(base.TestCase):
|
||||||
'config'])
|
'config'])
|
||||||
self.assertEqual(expected_vnf_update, vnf_obj)
|
self.assertEqual(expected_vnf_update, vnf_obj)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'tacker.vnfm.infra_drivers.openstack.vdu.Vdu')
|
||||||
|
def test_heal_vdu(self, mock_vdu):
|
||||||
|
self.infra_driver.heal_vdu(None, self.context, vnf_dict,
|
||||||
|
mock.ANY)
|
||||||
|
mock_vdu.assert_called_once_with(self.context, vnf_dict,
|
||||||
|
mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'tacker.vnfm.infra_drivers.openstack.vdu.Vdu')
|
||||||
|
@mock.patch('tacker.vnfm.infra_drivers.openstack.openstack.LOG')
|
||||||
|
def test_heal_vdu_failed(self, mock_log, mock_vdu):
|
||||||
|
mock_vdu.side_effect = Exception
|
||||||
|
self.assertRaises(vnfm.VNFHealFailed, self.infra_driver.heal_vdu,
|
||||||
|
None, self.context, vnf_dict,
|
||||||
|
mock.ANY)
|
||||||
|
mock_log.error.assert_called_with(
|
||||||
|
"VNF '%s' failed to heal", vnf_dict['id'])
|
||||||
|
|
||||||
def _get_expected_fields_tosca(self, template):
|
def _get_expected_fields_tosca(self, template):
|
||||||
return {'stack_name':
|
return {'stack_name':
|
||||||
'test_openwrt_eb84260e-5ff7-4332-b032-50a14d6c1123',
|
'test_openwrt_eb84260e-5ff7-4332-b032-50a14d6c1123',
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
# Copyright 2018 NTT DATA
|
||||||
|
# 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 mock
|
||||||
|
|
||||||
|
from tacker import context
|
||||||
|
from tacker.db.common_services import common_services_db_plugin
|
||||||
|
from tacker.objects import heal_vnf_request
|
||||||
|
from tacker.plugins.common import constants
|
||||||
|
from tacker.tests.unit import base
|
||||||
|
from tacker.vnfm.infra_drivers.openstack import vdu
|
||||||
|
|
||||||
|
|
||||||
|
vnf_dict = {
|
||||||
|
'attributes': {
|
||||||
|
'heat_template': {
|
||||||
|
'outputs': {
|
||||||
|
'mgmt_ip-VDU1': {
|
||||||
|
'value': {
|
||||||
|
'get_attr': [
|
||||||
|
'CP1', 'fixed_ips', 0, 'ip_address']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'description': 'Demo example\n',
|
||||||
|
'parameters': {},
|
||||||
|
'resources': {
|
||||||
|
'VDU1': {
|
||||||
|
'type': 'OS::Nova::Server',
|
||||||
|
'properties': {
|
||||||
|
'user_data_format': 'SOFTWARE_CONFIG',
|
||||||
|
'availability_zone': 'nova',
|
||||||
|
'image': 'cirros-0.4.0-x86_64-disk',
|
||||||
|
'config_drive': False,
|
||||||
|
'flavor': {'get_resource': 'VDU1_flavor'},
|
||||||
|
'networks': [{'port': {'get_resource': 'CP1'}}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'CP1': {
|
||||||
|
'type': 'OS::Neutron::Port',
|
||||||
|
'properties': {
|
||||||
|
'port_security_enabled': False,
|
||||||
|
'network': 'net_mgmt'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'VDU1_flavor': {
|
||||||
|
'type': 'OS::Nova::Flavor',
|
||||||
|
'properties': {'vcpus': 1, 'disk': 1, 'ram': 512}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'vnfd_id': '576acf48-b9df-491d-a57c-342de660ec78',
|
||||||
|
'tenant_id': '13d2ca8de70d48b2a2e0dbac2c327c0b',
|
||||||
|
'vim_id': '3f41faa7-5630-47d2-9d4a-1216953c8887',
|
||||||
|
'instance_id': 'd1121d3c-368b-4ac2-b39d-835aa3e4ccd8',
|
||||||
|
'placement_attr': {'vim_name': 'openstack-vim'},
|
||||||
|
'id': 'a27fc58e-66ae-4031-bba4-efede318c60b',
|
||||||
|
'name': 'vnf_create_1'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeHeatClient(mock.Mock):
|
||||||
|
|
||||||
|
class Stack(mock.Mock):
|
||||||
|
stack_status = 'CREATE_COMPLETE'
|
||||||
|
outputs = [{u'output_value': u'192.168.120.31', u'description':
|
||||||
|
u'management ip address', u'output_key': u'mgmt_ip-vdu1'}]
|
||||||
|
|
||||||
|
def create(self, *args, **kwargs):
|
||||||
|
return {'stack': {'id': '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'}}
|
||||||
|
|
||||||
|
def get(self, id):
|
||||||
|
return self.Stack()
|
||||||
|
|
||||||
|
def update(self, stack_id, **kwargs):
|
||||||
|
return self.Stack()
|
||||||
|
|
||||||
|
def resource_mark_unhealthy(self, stack_id, resource_name,
|
||||||
|
mark_unhealthy, resource_status_reason):
|
||||||
|
return self.Stack()
|
||||||
|
|
||||||
|
|
||||||
|
class TestVDU(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestVDU, self).setUp()
|
||||||
|
self.context = context.get_admin_context()
|
||||||
|
self._mock_heat_client()
|
||||||
|
|
||||||
|
mock.patch('tacker.vnfm.vim_client.VimClient.get_vim').start()
|
||||||
|
self.additional_paramas_obj = heal_vnf_request.HealVnfAdditionalParams(
|
||||||
|
parameter='VDU1',
|
||||||
|
cause=["Unable to reach while monitoring resource: 'VDU1'"])
|
||||||
|
self.heal_request_data_obj = heal_vnf_request.HealVnfRequest(
|
||||||
|
cause='VNF monitoring fails.',
|
||||||
|
additional_params=[self.additional_paramas_obj])
|
||||||
|
self.heal_vdu = vdu.Vdu(self.context, vnf_dict,
|
||||||
|
self.heal_request_data_obj)
|
||||||
|
|
||||||
|
mock.patch('tacker.db.common_services.common_services_db_plugin.'
|
||||||
|
'CommonServicesPluginDb.create_event'
|
||||||
|
).start()
|
||||||
|
self._cos_db_plugin = \
|
||||||
|
common_services_db_plugin.CommonServicesPluginDb()
|
||||||
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
|
||||||
|
def _mock_heat_client(self):
|
||||||
|
self.heat_client = mock.Mock(wraps=FakeHeatClient())
|
||||||
|
fake_heat_client = mock.Mock()
|
||||||
|
fake_heat_client.return_value = self.heat_client
|
||||||
|
self._mock(
|
||||||
|
'tacker.vnfm.infra_drivers.openstack.heat_client.HeatClient',
|
||||||
|
fake_heat_client)
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.vim_client.VimClient.get_vim')
|
||||||
|
def test_heal_vdu(self, mock_get_vim):
|
||||||
|
mock_get_vim.return_value = mock.MagicMock()
|
||||||
|
|
||||||
|
self.heal_vdu.heal_vdu()
|
||||||
|
|
||||||
|
self.heat_client.update.assert_called_once_with(
|
||||||
|
stack_id=vnf_dict['instance_id'], existing=True)
|
||||||
|
|
||||||
|
self._cos_db_plugin.create_event.assert_called_with(
|
||||||
|
self.context, res_id=vnf_dict['id'],
|
||||||
|
res_type=constants.RES_TYPE_VNF, res_state=vnf_dict['status'],
|
||||||
|
evt_type=constants.RES_EVT_HEAL, tstamp=mock.ANY,
|
||||||
|
details=("HealVnfRequest invoked to update the stack '%s'" %
|
||||||
|
vnf_dict['instance_id']))
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.vim_client.VimClient.get_vim')
|
||||||
|
def test_resource_mark_unhealthy(self, mock_get_vim):
|
||||||
|
mock_get_vim.return_value = mock.MagicMock()
|
||||||
|
|
||||||
|
self.heal_vdu._resource_mark_unhealthy()
|
||||||
|
|
||||||
|
self.heat_client.resource_mark_unhealthy.assert_called_once_with(
|
||||||
|
stack_id=vnf_dict['instance_id'],
|
||||||
|
resource_name=self.additional_paramas_obj.parameter,
|
||||||
|
mark_unhealthy=True,
|
||||||
|
resource_status_reason=self.additional_paramas_obj.cause)
|
||||||
|
|
||||||
|
self._cos_db_plugin.create_event.assert_called_with(
|
||||||
|
self.context, res_id=vnf_dict['id'],
|
||||||
|
res_type=constants.RES_TYPE_VNF, res_state=vnf_dict['status'],
|
||||||
|
evt_type=constants.RES_EVT_HEAL, tstamp=mock.ANY,
|
||||||
|
details="HealVnfRequest invoked to mark resource 'VDU1' "
|
||||||
|
"to unhealthy.")
|
|
@ -0,0 +1,147 @@
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from tacker import context
|
||||||
|
from tacker.db.nfvo import nfvo_db
|
||||||
|
from tacker.objects import heal_vnf_request
|
||||||
|
from tacker.tests.unit.db import base as db_base
|
||||||
|
from tacker.vnfm import plugin
|
||||||
|
from tacker.vnfm.policy_actions.vdu_autoheal import vdu_autoheal
|
||||||
|
|
||||||
|
|
||||||
|
vnf_dict = {
|
||||||
|
'id': uuidutils.generate_uuid(),
|
||||||
|
'mgmt_url': '{"VDU1": "a.b.c.d"}',
|
||||||
|
'vim_id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||||
|
'instance_id': 'a737497c-761c-11e5-89c3-9cb6541d805d',
|
||||||
|
'attributes': {
|
||||||
|
'heat_template': {
|
||||||
|
'resources': {
|
||||||
|
'VDU1': {
|
||||||
|
'properties': {
|
||||||
|
'networks': [{'port': {'get_resource': 'CP1'}}]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeDriverManager(mock.Mock):
|
||||||
|
def invoke(self, *args, **kwargs):
|
||||||
|
if 'create' in args:
|
||||||
|
return uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
if 'get_resource_info' in args:
|
||||||
|
return {'resources': {'name': 'dummy_vnf',
|
||||||
|
'type': 'dummy',
|
||||||
|
'id': uuidutils.generate_uuid()}}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeVNFMonitor(mock.Mock):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestVNFActionVduAutoheal(db_base.SqlTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestVNFActionVduAutoheal, self).setUp()
|
||||||
|
self.context = context.get_admin_context()
|
||||||
|
self._mock_device_manager()
|
||||||
|
self._mock_vnf_monitor()
|
||||||
|
self._insert_dummy_vim()
|
||||||
|
self.vnfm_plugin = plugin.VNFMPlugin()
|
||||||
|
self.vdu_autoheal = vdu_autoheal.VNFActionVduAutoheal()
|
||||||
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
|
||||||
|
def _mock_device_manager(self):
|
||||||
|
self._device_manager = mock.Mock(wraps=FakeDriverManager())
|
||||||
|
self._device_manager.__contains__ = mock.Mock(
|
||||||
|
return_value=True)
|
||||||
|
fake_device_manager = mock.Mock()
|
||||||
|
fake_device_manager.return_value = self._device_manager
|
||||||
|
self._mock(
|
||||||
|
'tacker.common.driver_manager.DriverManager', fake_device_manager)
|
||||||
|
|
||||||
|
def _mock_vnf_monitor(self):
|
||||||
|
self._vnf_monitor = mock.Mock(wraps=FakeVNFMonitor())
|
||||||
|
fake_vnf_monitor = mock.Mock()
|
||||||
|
fake_vnf_monitor.return_value = self._vnf_monitor
|
||||||
|
self._mock(
|
||||||
|
'tacker.vnfm.monitor.VNFMonitor', fake_vnf_monitor)
|
||||||
|
|
||||||
|
def _insert_dummy_vim(self):
|
||||||
|
session = self.context.session
|
||||||
|
vim_db = nfvo_db.Vim(
|
||||||
|
id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||||
|
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
|
||||||
|
name='fake_vim',
|
||||||
|
description='fake_vim_description',
|
||||||
|
type='test_vim',
|
||||||
|
status='Active',
|
||||||
|
deleted_at=datetime.min,
|
||||||
|
placement_attr={'regions': ['RegionOne']})
|
||||||
|
vim_auth_db = nfvo_db.VimAuth(
|
||||||
|
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||||
|
password='encrypted_pw',
|
||||||
|
auth_url='http://localhost:5000',
|
||||||
|
vim_project={'name': 'test_project'},
|
||||||
|
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
|
||||||
|
'project_domain_id': 'default'})
|
||||||
|
session.add(vim_db)
|
||||||
|
session.add(vim_auth_db)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.plugin.VNFMPlugin.heal_vnf')
|
||||||
|
@mock.patch('yaml.safe_load')
|
||||||
|
@mock.patch('tacker.objects.HealVnfRequest')
|
||||||
|
def test_vdu_autoheal_execute_action(self, mock_heal_vnf_request,
|
||||||
|
mock_safe_load,
|
||||||
|
mock_heal_vnf):
|
||||||
|
# Here yaml.safe_load is mock as in the test case i am passing
|
||||||
|
# vnf_dict containing having vnf_dict['attributes']['heat_template']
|
||||||
|
# value in json format so while excution it giving the error as
|
||||||
|
# dict object has no read attribute where as in actual execution the
|
||||||
|
# value of vnf_dict['attributes']['heat_template'] is in ymal format.
|
||||||
|
mock_safe_load.return_value = vnf_dict['attributes']['heat_template']
|
||||||
|
resource_list = ['VDU1', 'CP1']
|
||||||
|
additional_params = []
|
||||||
|
for resource in resource_list:
|
||||||
|
additional_paramas_obj = heal_vnf_request.HealVnfAdditionalParams(
|
||||||
|
parameter=resource,
|
||||||
|
cause=["Unable to reach while monitoring resource: '%s'" %
|
||||||
|
resource])
|
||||||
|
additional_params.append(additional_paramas_obj)
|
||||||
|
heal_request_data_obj = heal_vnf_request.HealVnfRequest(
|
||||||
|
cause='VNF monitoring fails.',
|
||||||
|
additional_params=additional_params)
|
||||||
|
mock_heal_vnf_request.return_value = heal_request_data_obj
|
||||||
|
self.vdu_autoheal.execute_action(self.vnfm_plugin, self.context,
|
||||||
|
vnf_dict, args={'vdu_name': 'VDU1'})
|
||||||
|
mock_heal_vnf.assert_called_once_with(self.context, vnf_dict['id'],
|
||||||
|
heal_request_data_obj)
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.policy_actions.vdu_autoheal.'
|
||||||
|
'vdu_autoheal.LOG')
|
||||||
|
def test_vdu_autoheal_action_with_no_vdu_name(self, mock_log):
|
||||||
|
expected_error_msg = ("VDU resource of vnf '%s' is not present for "
|
||||||
|
"autoheal." % vnf_dict['id'])
|
||||||
|
self.vdu_autoheal.execute_action(self.vnfm_plugin, self.context,
|
||||||
|
vnf_dict, args={})
|
||||||
|
mock_log.error.assert_called_with(expected_error_msg)
|
|
@ -50,6 +50,33 @@ MOCK_VNF = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MOCK_VNF_DEVICE_FOR_VDU_AUTOHEAL = {
|
||||||
|
'id': MOCK_VNF_ID,
|
||||||
|
'management_ip_addresses': {
|
||||||
|
'vdu1': 'a.b.c.d'
|
||||||
|
},
|
||||||
|
'monitoring_policy': {
|
||||||
|
'vdus': {
|
||||||
|
'vdu1': {
|
||||||
|
'ping': {
|
||||||
|
'actions': {
|
||||||
|
'failure': 'vdu_autoheal'
|
||||||
|
},
|
||||||
|
'monitoring_params': {
|
||||||
|
'count': 1,
|
||||||
|
'monitoring_delay': 0,
|
||||||
|
'interval': 0,
|
||||||
|
'timeout': 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'boot_at': timeutils.utcnow(),
|
||||||
|
'action_cb': mock.MagicMock()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestVNFMonitor(testtools.TestCase):
|
class TestVNFMonitor(testtools.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -112,7 +139,7 @@ class TestVNFMonitor(testtools.TestCase):
|
||||||
@mock.patch('tacker.vnfm.monitor.VNFMonitor.__run__')
|
@mock.patch('tacker.vnfm.monitor.VNFMonitor.__run__')
|
||||||
def test_run_monitor(self, mock_monitor_run):
|
def test_run_monitor(self, mock_monitor_run):
|
||||||
test_hosting_vnf = MOCK_VNF
|
test_hosting_vnf = MOCK_VNF
|
||||||
test_hosting_vnf['vnf'] = {}
|
test_hosting_vnf['vnf'] = {'status': 'ACTIVE'}
|
||||||
test_boot_wait = 30
|
test_boot_wait = 30
|
||||||
mock_kwargs = {
|
mock_kwargs = {
|
||||||
'count': 1,
|
'count': 1,
|
||||||
|
@ -125,6 +152,55 @@ class TestVNFMonitor(testtools.TestCase):
|
||||||
self.mock_monitor_manager.invoke = mock.MagicMock()
|
self.mock_monitor_manager.invoke = mock.MagicMock()
|
||||||
test_vnfmonitor._monitor_manager = self.mock_monitor_manager
|
test_vnfmonitor._monitor_manager = self.mock_monitor_manager
|
||||||
test_vnfmonitor.run_monitor(test_hosting_vnf)
|
test_vnfmonitor.run_monitor(test_hosting_vnf)
|
||||||
self.mock_monitor_manager\
|
self.mock_monitor_manager \
|
||||||
.invoke.assert_called_once_with('ping', 'monitor_call', vnf={},
|
.invoke.assert_called_once_with('ping', 'monitor_call',
|
||||||
|
vnf={'status': 'ACTIVE'},
|
||||||
kwargs=mock_kwargs)
|
kwargs=mock_kwargs)
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.monitor.VNFMonitor.__run__')
|
||||||
|
@mock.patch('tacker.vnfm.monitor.VNFMonitor.monitor_call')
|
||||||
|
def test_vdu_autoheal_action(self, mock_monitor_call, mock_monitor_run):
|
||||||
|
test_hosting_vnf = MOCK_VNF_DEVICE_FOR_VDU_AUTOHEAL
|
||||||
|
test_boot_wait = 30
|
||||||
|
test_device_dict = {
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'id': MOCK_VNF_ID,
|
||||||
|
'mgmt_url': '{"vdu1": "a.b.c.d"}',
|
||||||
|
'attributes': {
|
||||||
|
'monitoring_policy': json.dumps(
|
||||||
|
MOCK_VNF_DEVICE_FOR_VDU_AUTOHEAL['monitoring_policy'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test_hosting_vnf['vnf'] = test_device_dict
|
||||||
|
mock_monitor_call.return_value = 'failure'
|
||||||
|
test_vnfmonitor = monitor.VNFMonitor(test_boot_wait)
|
||||||
|
test_vnfmonitor._monitor_manager = self.mock_monitor_manager
|
||||||
|
test_vnfmonitor.run_monitor(test_hosting_vnf)
|
||||||
|
test_hosting_vnf['action_cb'].assert_called_once_with(
|
||||||
|
'vdu_autoheal', vdu_name='vdu1')
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.monitor.VNFMonitor.__run__')
|
||||||
|
def test_update_hosting_vnf(self, mock_monitor_run):
|
||||||
|
test_boot_wait = 30
|
||||||
|
test_vnfmonitor = monitor.VNFMonitor(test_boot_wait)
|
||||||
|
vnf_dict = {
|
||||||
|
'id': MOCK_VNF_ID,
|
||||||
|
'mgmt_url': '{"vdu1": "a.b.c.d"}',
|
||||||
|
'management_ip_addresses': 'a.b.c.d',
|
||||||
|
'vnf': {
|
||||||
|
'id': MOCK_VNF_ID,
|
||||||
|
'mgmt_url': '{"vdu1": "a.b.c.d"}',
|
||||||
|
'attributes': {
|
||||||
|
'monitoring_policy': json.dumps(
|
||||||
|
MOCK_VNF['monitoring_policy'])
|
||||||
|
},
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_vnfmonitor.add_hosting_vnf(vnf_dict)
|
||||||
|
vnf_dict['status'] = 'PENDING_HEAL'
|
||||||
|
test_vnfmonitor.update_hosting_vnf(vnf_dict)
|
||||||
|
test_device_status = test_vnfmonitor._hosting_vnfs[MOCK_VNF_ID][
|
||||||
|
'vnf']['status']
|
||||||
|
self.assertEqual('PENDING_HEAL', test_device_status)
|
||||||
|
|
|
@ -27,6 +27,7 @@ from tacker.db.nfvo import nfvo_db
|
||||||
from tacker.db.nfvo import ns_db
|
from tacker.db.nfvo import ns_db
|
||||||
from tacker.db.vnfm import vnfm_db
|
from tacker.db.vnfm import vnfm_db
|
||||||
from tacker.extensions import vnfm
|
from tacker.extensions import vnfm
|
||||||
|
from tacker.objects import heal_vnf_request
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
from tacker.tests.unit.db import base as db_base
|
from tacker.tests.unit.db import base as db_base
|
||||||
from tacker.tests.unit.db import utils
|
from tacker.tests.unit.db import utils
|
||||||
|
@ -622,3 +623,32 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||||
self.context,
|
self.context,
|
||||||
uuidutils.generate_uuid(),
|
uuidutils.generate_uuid(),
|
||||||
policy_type='invalid_policy_type')
|
policy_type='invalid_policy_type')
|
||||||
|
|
||||||
|
@mock.patch('tacker.vnfm.monitor.VNFMonitor.update_hosting_vnf')
|
||||||
|
def test_heal_vnf_vdu(self, mock_update_hosting_vnf):
|
||||||
|
self._insert_dummy_vnf_template()
|
||||||
|
dummy_device_obj = self._insert_dummy_vnf()
|
||||||
|
additional_params_obj = heal_vnf_request.HealVnfAdditionalParams(
|
||||||
|
parameter='VDU1',
|
||||||
|
cause=["Unable to reach while monitoring resource: 'VDU1'"])
|
||||||
|
heal_request_data_obj = heal_vnf_request.HealVnfRequest(
|
||||||
|
cause='VNF monitoring fails.',
|
||||||
|
additional_params=[additional_params_obj])
|
||||||
|
result = self.vnfm_plugin.heal_vnf(self.context,
|
||||||
|
dummy_device_obj['id'],
|
||||||
|
heal_request_data_obj)
|
||||||
|
self.assertIsNotNone(result)
|
||||||
|
self.assertEqual(dummy_device_obj['id'], result['id'])
|
||||||
|
self.assertIn('instance_id', result)
|
||||||
|
self.assertIn('status', result)
|
||||||
|
self.assertIn('attributes', result)
|
||||||
|
self.assertIn('mgmt_url', result)
|
||||||
|
self.assertIn('updated_at', result)
|
||||||
|
|
||||||
|
self._vnf_manager.invoke.assert_called_with(
|
||||||
|
'test_vim', 'heal_vdu', plugin=self.vnfm_plugin,
|
||||||
|
context=self.context, vnf_dict=mock.ANY,
|
||||||
|
heal_request_data_obj=heal_request_data_obj)
|
||||||
|
self._pool.spawn_n.assert_called_once_with(
|
||||||
|
self.vnfm_plugin._update_vnf_wait, self.context, mock.ANY,
|
||||||
|
mock.ANY, 'test_vim', vnf_heal=True)
|
||||||
|
|
|
@ -53,7 +53,7 @@ class VnfAbstractDriver(extensions.PluginInterface):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def update_wait(self, plugin, context, vnf_id):
|
def update_wait(self, plugin, context, vnf_dict):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
@ -69,3 +69,7 @@ class VnfAbstractDriver(extensions.PluginInterface):
|
||||||
region_name=None):
|
region_name=None):
|
||||||
'''Fetches optional details of a VNF'''
|
'''Fetches optional details of a VNF'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data):
|
||||||
|
pass
|
||||||
|
|
|
@ -545,3 +545,6 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
|
||||||
if file_descriptor is not None:
|
if file_descriptor is not None:
|
||||||
file_path = vim_auth.pop('ca_cert_file')
|
file_path = vim_auth.pop('ca_cert_file')
|
||||||
self.kubernetes.close_tmp_file(file_descriptor, file_path)
|
self.kubernetes.close_tmp_file(file_descriptor, file_path)
|
||||||
|
|
||||||
|
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data):
|
||||||
|
pass
|
||||||
|
|
|
@ -73,3 +73,6 @@ class VnfNoop(abstract_driver.VnfAbstractDriver):
|
||||||
def get_resource_info(self, plugin, context, vnf_info, auth_attr,
|
def get_resource_info(self, plugin, context, vnf_info, auth_attr,
|
||||||
region_name=None):
|
region_name=None):
|
||||||
return {'noop': {'id': uuidutils.generate_uuid(), 'type': 'noop'}}
|
return {'noop': {'id': uuidutils.generate_uuid(), 'type': 'noop'}}
|
||||||
|
|
||||||
|
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data):
|
||||||
|
pass
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright 2018 OpenStack Foundation.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# openstack infra constants
|
||||||
|
|
||||||
|
STACK_CREATE_IN_PROGRESS = "CREATE_IN_PROGRESS"
|
||||||
|
STACK_CREATE_COMPLETE = "CREATE_COMPLETE"
|
||||||
|
STACK_UPDATE_IN_PROGRESS = "UPDATE_IN_PROGRESS"
|
||||||
|
STACK_UPDATE_COMPLETE = "UPDATE_COMPLETE"
|
||||||
|
STACK_DELETE_IN_PROGRESS = "DELETE_IN_PROGRESS"
|
||||||
|
STACK_DELETE_COMPLETE = "DELETE_COMPLETE"
|
|
@ -53,6 +53,9 @@ class HeatClient(object):
|
||||||
def get(self, stack_id):
|
def get(self, stack_id):
|
||||||
return self.stacks.get(stack_id)
|
return self.stacks.get(stack_id)
|
||||||
|
|
||||||
|
def update(self, stack_id, **kwargs):
|
||||||
|
return self.stacks.update(stack_id, **kwargs)
|
||||||
|
|
||||||
def resource_attr_support(self, resource_name, property_name):
|
def resource_attr_support(self, resource_name, property_name):
|
||||||
resource = self.resource_types.get(resource_name)
|
resource = self.resource_types.get(resource_name)
|
||||||
return property_name in resource['attributes']
|
return property_name in resource['attributes']
|
||||||
|
@ -72,3 +75,9 @@ class HeatClient(object):
|
||||||
|
|
||||||
def resource_metadata(self, stack_id, rsc_name):
|
def resource_metadata(self, stack_id, rsc_name):
|
||||||
return self.heat.resources.metadata(stack_id, rsc_name)
|
return self.heat.resources.metadata(stack_id, rsc_name)
|
||||||
|
|
||||||
|
def resource_mark_unhealthy(self, stack_id, resource_name,
|
||||||
|
mark_unhealthy, resource_status_reason):
|
||||||
|
return self.heat.resources.mark_unhealthy(stack_id, resource_name,
|
||||||
|
mark_unhealthy,
|
||||||
|
resource_status_reason)
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from heatclient import exc as heatException
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
@ -26,8 +25,10 @@ from tacker.common import log
|
||||||
from tacker.common import utils
|
from tacker.common import utils
|
||||||
from tacker.extensions import vnfm
|
from tacker.extensions import vnfm
|
||||||
from tacker.vnfm.infra_drivers import abstract_driver
|
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 heat_client as hc
|
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 translate_template
|
||||||
|
from tacker.vnfm.infra_drivers.openstack import vdu
|
||||||
from tacker.vnfm.infra_drivers import scale_driver
|
from tacker.vnfm.infra_drivers import scale_driver
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,11 +129,45 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
'region_name', None)
|
'region_name', None)
|
||||||
heatclient = hc.HeatClient(auth_attr, region_name)
|
heatclient = hc.HeatClient(auth_attr, region_name)
|
||||||
|
|
||||||
|
stack, status, stack_retries = self._wait_until_stack_ready(
|
||||||
|
vnf_id, auth_attr, infra_cnst.STACK_CREATE_IN_PROGRESS,
|
||||||
|
region_name=region_name)
|
||||||
|
|
||||||
|
if stack_retries == 0 and status != infra_cnst.STACK_CREATE_COMPLETE:
|
||||||
|
error_reason = _("Resource creation is not completed within"
|
||||||
|
" {wait} seconds as creation of stack {stack}"
|
||||||
|
" is not completed").format(
|
||||||
|
wait=(self.STACK_RETRIES *
|
||||||
|
self.STACK_RETRY_WAIT),
|
||||||
|
stack=vnf_id)
|
||||||
|
LOG.warning("VNF Creation failed: %(reason)s",
|
||||||
|
{'reason': error_reason})
|
||||||
|
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
|
||||||
|
|
||||||
|
elif stack_retries != 0 and status != infra_cnst.STACK_CREATE_COMPLETE:
|
||||||
|
error_reason = stack.stack_status_reason
|
||||||
|
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
|
||||||
|
|
||||||
|
# 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_url'] = jsonutils.dumps(mgmt_ips)
|
||||||
|
|
||||||
|
def _wait_until_stack_ready(self, vnf_id, auth_attr, wait_status,
|
||||||
|
region_name=None):
|
||||||
|
heatclient = hc.HeatClient(auth_attr, region_name)
|
||||||
stack = heatclient.get(vnf_id)
|
stack = heatclient.get(vnf_id)
|
||||||
status = stack.stack_status
|
status = stack.stack_status
|
||||||
stack_retries = self.STACK_RETRIES
|
stack_retries = self.STACK_RETRIES
|
||||||
error_reason = None
|
while status == wait_status and stack_retries > 0:
|
||||||
while status == 'CREATE_IN_PROGRESS' and stack_retries > 0:
|
|
||||||
time.sleep(self.STACK_RETRY_WAIT)
|
time.sleep(self.STACK_RETRY_WAIT)
|
||||||
try:
|
try:
|
||||||
stack = heatclient.get(vnf_id)
|
stack = heatclient.get(vnf_id)
|
||||||
|
@ -146,45 +181,19 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
status = stack.stack_status
|
status = stack.stack_status
|
||||||
LOG.debug('status: %s', status)
|
LOG.debug('status: %s', status)
|
||||||
stack_retries = stack_retries - 1
|
stack_retries = stack_retries - 1
|
||||||
|
|
||||||
LOG.debug('stack status: %(stack)s %(status)s',
|
LOG.debug('stack status: %(stack)s %(status)s',
|
||||||
{'stack': str(stack), 'status': status})
|
{'stack': str(stack), 'status': status})
|
||||||
if stack_retries == 0 and status != 'CREATE_COMPLETE':
|
|
||||||
error_reason = _("Resource creation is not completed within"
|
|
||||||
" {wait} seconds as creation of stack {stack}"
|
|
||||||
" is not completed").format(
|
|
||||||
wait=(self.STACK_RETRIES *
|
|
||||||
self.STACK_RETRY_WAIT),
|
|
||||||
stack=vnf_id)
|
|
||||||
LOG.warning("VNF Creation failed: %(reason)s",
|
|
||||||
{'reason': error_reason})
|
|
||||||
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
|
|
||||||
|
|
||||||
elif stack_retries != 0 and status != 'CREATE_COMPLETE':
|
return stack, status, stack_retries
|
||||||
error_reason = stack.stack_status_reason
|
|
||||||
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
|
|
||||||
|
|
||||||
def _find_mgmt_ips(outputs):
|
def _find_mgmt_ips(self, outputs):
|
||||||
LOG.debug('outputs %s', outputs)
|
LOG.debug('outputs %s', outputs)
|
||||||
mgmt_ips = dict((output['output_key'][len(OUTPUT_PREFIX):],
|
mgmt_ips = dict((output['output_key'][len(OUTPUT_PREFIX):],
|
||||||
output['output_value'])
|
output['output_value'])
|
||||||
for output in outputs
|
for output in outputs
|
||||||
if output.get('output_key',
|
if output.get('output_key',
|
||||||
'').startswith(OUTPUT_PREFIX))
|
'').startswith(OUTPUT_PREFIX))
|
||||||
return mgmt_ips
|
return mgmt_ips
|
||||||
|
|
||||||
# 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 = _find_mgmt_ips(stack.outputs)
|
|
||||||
|
|
||||||
if mgmt_ips:
|
|
||||||
vnf_dict['mgmt_url'] = jsonutils.dumps(mgmt_ips)
|
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def update(self, plugin, context, vnf_id, vnf_dict, vnf,
|
def update(self, plugin, context, vnf_id, vnf_dict, vnf,
|
||||||
|
@ -219,11 +228,32 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
vnf_dict.setdefault('attributes', {})['config'] = new_yaml
|
vnf_dict.setdefault('attributes', {})['config'] = new_yaml
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def update_wait(self, plugin, context, vnf_id, auth_attr,
|
def update_wait(self, plugin, context, vnf_dict, auth_attr,
|
||||||
region_name=None):
|
region_name=None):
|
||||||
# do nothing but checking if the stack exists at the moment
|
stack, status, stack_retries = self._wait_until_stack_ready(
|
||||||
heatclient = hc.HeatClient(auth_attr, region_name)
|
vnf_dict['instance_id'], auth_attr,
|
||||||
heatclient.get(vnf_id)
|
infra_cnst.STACK_UPDATE_IN_PROGRESS,
|
||||||
|
region_name=region_name)
|
||||||
|
|
||||||
|
if stack_retries == 0 and status != infra_cnst.STACK_UPDATE_COMPLETE:
|
||||||
|
error_reason = _("Resource updation is not completed within"
|
||||||
|
" {wait} seconds as updation of stack {stack}"
|
||||||
|
" is not completed").format(
|
||||||
|
wait=(self.STACK_RETRIES * self.STACK_RETRY_WAIT),
|
||||||
|
stack=vnf_dict['instance_id'])
|
||||||
|
LOG.error("VNF Updation failed: %(reason)s",
|
||||||
|
{'reason': error_reason})
|
||||||
|
raise vnfm.VNFUpdateWaitFailed(reason=error_reason)
|
||||||
|
|
||||||
|
elif stack_retries != 0 and (status !=
|
||||||
|
infra_cnst.STACK_UPDATE_COMPLETE):
|
||||||
|
error_reason = stack.stack_status_reason
|
||||||
|
raise vnfm.VNFUpdateWaitFailed(reason=error_reason)
|
||||||
|
|
||||||
|
mgmt_ips = self._find_mgmt_ips(stack.outputs)
|
||||||
|
|
||||||
|
if mgmt_ips:
|
||||||
|
vnf_dict['mgmt_url'] = jsonutils.dumps(mgmt_ips)
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def delete(self, plugin, context, vnf_id, auth_attr, region_name=None):
|
def delete(self, plugin, context, vnf_id, auth_attr, region_name=None):
|
||||||
|
@ -233,29 +263,11 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
@log.log
|
@log.log
|
||||||
def delete_wait(self, plugin, context, vnf_id, auth_attr,
|
def delete_wait(self, plugin, context, vnf_id, auth_attr,
|
||||||
region_name=None):
|
region_name=None):
|
||||||
heatclient = hc.HeatClient(auth_attr, region_name)
|
stack, status, stack_retries = self._wait_until_stack_ready(
|
||||||
|
vnf_id, auth_attr, infra_cnst.STACK_DELETE_IN_PROGRESS,
|
||||||
|
region_name=region_name)
|
||||||
|
|
||||||
stack = heatclient.get(vnf_id)
|
if stack_retries == 0 and status != infra_cnst.STACK_DELETE_COMPLETE:
|
||||||
status = stack.stack_status
|
|
||||||
error_reason = None
|
|
||||||
stack_retries = self.STACK_RETRIES
|
|
||||||
while (status == 'DELETE_IN_PROGRESS' and stack_retries > 0):
|
|
||||||
time.sleep(self.STACK_RETRY_WAIT)
|
|
||||||
try:
|
|
||||||
stack = heatclient.get(vnf_id)
|
|
||||||
except heatException.HTTPNotFound:
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
LOG.warning("VNF Instance cleanup may not have "
|
|
||||||
"happened because Heat API request failed "
|
|
||||||
"while waiting for the stack %(stack)s to be "
|
|
||||||
"deleted", {'stack': vnf_id})
|
|
||||||
# Just like create wait, ignore the exception to
|
|
||||||
# avoid temporary connection error.
|
|
||||||
status = stack.stack_status
|
|
||||||
stack_retries = stack_retries - 1
|
|
||||||
|
|
||||||
if stack_retries == 0 and status != 'DELETE_COMPLETE':
|
|
||||||
error_reason = _("Resource cleanup for vnf is"
|
error_reason = _("Resource cleanup for vnf is"
|
||||||
" not completed within {wait} seconds as "
|
" not completed within {wait} seconds as "
|
||||||
"deletion of Stack {stack} is "
|
"deletion of Stack {stack} is "
|
||||||
|
@ -264,7 +276,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
LOG.warning(error_reason)
|
LOG.warning(error_reason)
|
||||||
raise vnfm.VNFDeleteWaitFailed(reason=error_reason)
|
raise vnfm.VNFDeleteWaitFailed(reason=error_reason)
|
||||||
|
|
||||||
if stack_retries != 0 and status != 'DELETE_COMPLETE':
|
if stack_retries != 0 and status != infra_cnst.STACK_DELETE_COMPLETE:
|
||||||
error_reason = _("VNF {vnf_id} deletion is not completed. "
|
error_reason = _("VNF {vnf_id} deletion is not completed. "
|
||||||
"{stack_status}").format(vnf_id=vnf_id,
|
"{stack_status}").format(vnf_id=vnf_id,
|
||||||
stack_status=status)
|
stack_status=status)
|
||||||
|
@ -401,3 +413,11 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
# Raise exception when Heat API service is not available
|
# Raise exception when Heat API service is not available
|
||||||
except Exception:
|
except Exception:
|
||||||
raise vnfm.InfraDriverUnreachable(service="Heat API service")
|
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'])
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Copyright 2018 NTT DATA
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.plugins.common import constants
|
||||||
|
from tacker.vnfm.infra_drivers.openstack import heat_client as hc
|
||||||
|
from tacker.vnfm import utils as vnfm_utils
|
||||||
|
from tacker.vnfm import vim_client
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class Vdu(object):
|
||||||
|
def __init__(self, context, vnf_dict, heal_request_data_obj):
|
||||||
|
super(Vdu, self).__init__()
|
||||||
|
self.context = context
|
||||||
|
self.vnf_dict = vnf_dict
|
||||||
|
self.heal_request_data_obj = heal_request_data_obj
|
||||||
|
vim_id = self.vnf_dict['vim_id']
|
||||||
|
vim_res = vim_client.VimClient().get_vim(context, vim_id)
|
||||||
|
placement_attr = vnf_dict.get('placement_attr', {})
|
||||||
|
auth_attr = vim_res['vim_auth']
|
||||||
|
region_name = placement_attr.get('region_name', None)
|
||||||
|
self.heat_client = hc.HeatClient(auth_attr=auth_attr,
|
||||||
|
region_name=region_name)
|
||||||
|
|
||||||
|
def _get_resource_status(self, stack_id, rsc_name):
|
||||||
|
# Get the status of VDU resource from heat
|
||||||
|
vdu_resource = self.heat_client.resource_get(stack_id=stack_id,
|
||||||
|
rsc_name=rsc_name)
|
||||||
|
return vdu_resource.resource_status
|
||||||
|
|
||||||
|
def _resource_mark_unhealthy(self):
|
||||||
|
"""Mark the resource unhealthy using heat."""
|
||||||
|
|
||||||
|
additional_params = self.heal_request_data_obj.additional_params
|
||||||
|
for additional_param in additional_params:
|
||||||
|
resource_name = additional_param.parameter
|
||||||
|
res_status = self._get_resource_status(
|
||||||
|
self.vnf_dict['instance_id'], resource_name)
|
||||||
|
if res_status != 'CHECK_FAILED':
|
||||||
|
self.heat_client.resource_mark_unhealthy(
|
||||||
|
stack_id=self.vnf_dict['instance_id'],
|
||||||
|
resource_name=resource_name, mark_unhealthy=True,
|
||||||
|
resource_status_reason=additional_param.cause)
|
||||||
|
LOG.debug("Heat stack '%s' resource '%s' marked as "
|
||||||
|
"unhealthy", self.vnf_dict['instance_id'],
|
||||||
|
resource_name)
|
||||||
|
evt_details = (("HealVnfRequest invoked to mark resource "
|
||||||
|
"'%s' to unhealthy.") % resource_name)
|
||||||
|
vnfm_utils.log_events(self.context, self.vnf_dict,
|
||||||
|
constants.RES_EVT_HEAL,
|
||||||
|
evt_details)
|
||||||
|
else:
|
||||||
|
LOG.debug("Heat stack '%s' resource '%s' already mark "
|
||||||
|
"unhealthy.", self.vnf_dict['instance_id'],
|
||||||
|
resource_name)
|
||||||
|
|
||||||
|
def heal_vdu(self):
|
||||||
|
"""Update stack using heat.
|
||||||
|
|
||||||
|
This will re-create the resource which are mark as unhealthy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Mark all the resources as unhealthy
|
||||||
|
self._resource_mark_unhealthy()
|
||||||
|
self.heat_client.update(stack_id=self.vnf_dict['instance_id'],
|
||||||
|
existing=True)
|
||||||
|
LOG.debug("Heat stack '%s' update initiated to revive "
|
||||||
|
"unhealthy resources.", self.vnf_dict['instance_id'])
|
||||||
|
evt_details = (("HealVnfRequest invoked to update the stack "
|
||||||
|
"'%s'") % self.vnf_dict['instance_id'])
|
||||||
|
vnfm_utils.log_events(self.context, self.vnf_dict,
|
||||||
|
constants.RES_EVT_HEAL, evt_details)
|
|
@ -15,6 +15,7 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
@ -26,8 +27,8 @@ from oslo_utils import timeutils
|
||||||
|
|
||||||
from tacker.common import driver_manager
|
from tacker.common import driver_manager
|
||||||
from tacker import context as t_context
|
from tacker import context as t_context
|
||||||
from tacker.db.common_services import common_services_db_plugin
|
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
|
from tacker.vnfm import utils as vnfm_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
@ -46,16 +47,6 @@ def config_opts():
|
||||||
('tacker', VNFAppMonitor.OPTS)]
|
('tacker', VNFAppMonitor.OPTS)]
|
||||||
|
|
||||||
|
|
||||||
def _log_monitor_events(context, vnf_dict, evt_details):
|
|
||||||
_cos_db_plg = common_services_db_plugin.CommonServicesPluginDb()
|
|
||||||
_cos_db_plg.create_event(context, res_id=vnf_dict['id'],
|
|
||||||
res_type=constants.RES_TYPE_VNF,
|
|
||||||
res_state=vnf_dict['status'],
|
|
||||||
evt_type=constants.RES_EVT_MONITOR,
|
|
||||||
tstamp=timeutils.utcnow(),
|
|
||||||
details=evt_details)
|
|
||||||
|
|
||||||
|
|
||||||
class VNFMonitor(object):
|
class VNFMonitor(object):
|
||||||
"""VNF Monitor."""
|
"""VNF Monitor."""
|
||||||
|
|
||||||
|
@ -96,8 +87,12 @@ class VNFMonitor(object):
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
for hosting_vnf in VNFMonitor._hosting_vnfs.values():
|
for hosting_vnf in VNFMonitor._hosting_vnfs.values():
|
||||||
if hosting_vnf.get('dead', False):
|
if hosting_vnf.get('dead', False) or (
|
||||||
LOG.debug('monitor skips dead vnf %s', hosting_vnf)
|
hosting_vnf['vnf']['status'] ==
|
||||||
|
constants.PENDING_HEAL):
|
||||||
|
LOG.debug(
|
||||||
|
'monitor skips for DEAD/PENDING_HEAL vnf %s',
|
||||||
|
hosting_vnf)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
self.run_monitor(hosting_vnf)
|
self.run_monitor(hosting_vnf)
|
||||||
|
@ -130,8 +125,9 @@ class VNFMonitor(object):
|
||||||
mon_policy_dict = attrib_dict['monitoring_policy']
|
mon_policy_dict = attrib_dict['monitoring_policy']
|
||||||
evt_details = (("VNF added for monitoring. "
|
evt_details = (("VNF added for monitoring. "
|
||||||
"mon_policy_dict = %s,") % (mon_policy_dict))
|
"mon_policy_dict = %s,") % (mon_policy_dict))
|
||||||
_log_monitor_events(t_context.get_admin_context(), new_vnf['vnf'],
|
vnfm_utils.log_events(t_context.get_admin_context(),
|
||||||
evt_details)
|
new_vnf['vnf'],
|
||||||
|
constants.RES_EVT_MONITOR, evt_details)
|
||||||
|
|
||||||
def delete_hosting_vnf(self, vnf_id):
|
def delete_hosting_vnf(self, vnf_id):
|
||||||
LOG.debug('deleting vnf_id %(vnf_id)s', {'vnf_id': vnf_id})
|
LOG.debug('deleting vnf_id %(vnf_id)s', {'vnf_id': vnf_id})
|
||||||
|
@ -142,6 +138,22 @@ class VNFMonitor(object):
|
||||||
{'vnf_id': vnf_id,
|
{'vnf_id': vnf_id,
|
||||||
'ips': hosting_vnf['management_ip_addresses']})
|
'ips': hosting_vnf['management_ip_addresses']})
|
||||||
|
|
||||||
|
def update_hosting_vnf(self, updated_vnf_dict, evt_details=None):
|
||||||
|
with self._lock:
|
||||||
|
vnf_to_update = VNFMonitor._hosting_vnfs.get(
|
||||||
|
updated_vnf_dict.get('id'))
|
||||||
|
if vnf_to_update:
|
||||||
|
updated_vnf = copy.deepcopy(updated_vnf_dict)
|
||||||
|
vnf_to_update['vnf'] = updated_vnf
|
||||||
|
vnf_to_update['management_ip_addresses'] = jsonutils.loads(
|
||||||
|
updated_vnf_dict['mgmt_url'])
|
||||||
|
|
||||||
|
if evt_details is not None:
|
||||||
|
vnfm_utils.log_events(t_context.get_admin_context(),
|
||||||
|
vnf_to_update['vnf'],
|
||||||
|
constants.RES_EVT_HEAL,
|
||||||
|
evt_details=evt_details)
|
||||||
|
|
||||||
def run_monitor(self, hosting_vnf):
|
def run_monitor(self, hosting_vnf):
|
||||||
mgmt_ips = hosting_vnf['management_ip_addresses']
|
mgmt_ips = hosting_vnf['management_ip_addresses']
|
||||||
vdupolicies = hosting_vnf['monitoring_policy']['vdus']
|
vdupolicies = hosting_vnf['monitoring_policy']['vdus']
|
||||||
|
@ -150,7 +162,8 @@ class VNFMonitor(object):
|
||||||
'monitoring_delay', self.boot_wait)
|
'monitoring_delay', self.boot_wait)
|
||||||
|
|
||||||
for vdu in vdupolicies.keys():
|
for vdu in vdupolicies.keys():
|
||||||
if hosting_vnf.get('dead'):
|
if hosting_vnf.get('dead') or (
|
||||||
|
hosting_vnf['vnf']['status']) == constants.PENDING_HEAL:
|
||||||
return
|
return
|
||||||
|
|
||||||
policy = vdupolicies[vdu]
|
policy = vdupolicies[vdu]
|
||||||
|
@ -165,8 +178,7 @@ class VNFMonitor(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
actions = policy[driver].get('actions', {})
|
actions = policy[driver].get('actions', {})
|
||||||
if 'mgmt_ip' not in params:
|
params['mgmt_ip'] = mgmt_ips[vdu]
|
||||||
params['mgmt_ip'] = mgmt_ips[vdu]
|
|
||||||
|
|
||||||
driver_return = self.monitor_call(driver,
|
driver_return = self.monitor_call(driver,
|
||||||
hosting_vnf['vnf'],
|
hosting_vnf['vnf'],
|
||||||
|
@ -176,7 +188,7 @@ class VNFMonitor(object):
|
||||||
|
|
||||||
if driver_return in actions:
|
if driver_return in actions:
|
||||||
action = actions[driver_return]
|
action = actions[driver_return]
|
||||||
hosting_vnf['action_cb'](action)
|
hosting_vnf['action_cb'](action, vdu_name=vdu)
|
||||||
|
|
||||||
def mark_dead(self, vnf_id):
|
def mark_dead(self, vnf_id):
|
||||||
VNFMonitor._hosting_vnfs[vnf_id]['dead'] = True
|
VNFMonitor._hosting_vnfs[vnf_id]['dead'] = True
|
||||||
|
@ -270,9 +282,9 @@ class VNFAlarmMonitor(object):
|
||||||
# TODO(Tung Doan) trigger_dict.get('actions') needs to be used
|
# TODO(Tung Doan) trigger_dict.get('actions') needs to be used
|
||||||
policy_action = trigger_dict.get('action')
|
policy_action = trigger_dict.get('action')
|
||||||
if len(policy_action) == 0:
|
if len(policy_action) == 0:
|
||||||
_log_monitor_events(t_context.get_admin_context(),
|
vnfm_utils.log_events(t_context.get_admin_context(), vnf,
|
||||||
vnf,
|
constants.RES_EVT_MONITOR,
|
||||||
"Alarm not set: policy action missing")
|
"Alarm not set: policy action missing")
|
||||||
return
|
return
|
||||||
# Other backend policies with the construct (policy, action)
|
# Other backend policies with the construct (policy, action)
|
||||||
# ex: (SP1, in), (SP1, out)
|
# ex: (SP1, in), (SP1, out)
|
||||||
|
@ -303,9 +315,8 @@ class VNFAlarmMonitor(object):
|
||||||
alarm_url[trigger_name] =\
|
alarm_url[trigger_name] =\
|
||||||
self.call_alarm_url(driver, vnf, params)
|
self.call_alarm_url(driver, vnf, params)
|
||||||
details = "Alarm URL set successfully: %s" % alarm_url
|
details = "Alarm URL set successfully: %s" % alarm_url
|
||||||
_log_monitor_events(t_context.get_admin_context(),
|
vnfm_utils.log_events(t_context.get_admin_context(), vnf,
|
||||||
vnf,
|
constants.RES_EVT_MONITOR, details)
|
||||||
details)
|
|
||||||
return alarm_url
|
return alarm_url
|
||||||
|
|
||||||
def process_alarm_for_vnf(self, vnf, trigger):
|
def process_alarm_for_vnf(self, vnf, trigger):
|
||||||
|
|
|
@ -124,7 +124,7 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
OPTS_POLICY_ACTION = [
|
OPTS_POLICY_ACTION = [
|
||||||
cfg.ListOpt(
|
cfg.ListOpt(
|
||||||
'policy_action', default=['autoscaling', 'respawn',
|
'policy_action', default=['autoscaling', 'respawn',
|
||||||
'log', 'log_and_kill'],
|
'vdu_autoheal', 'log', 'log_and_kill'],
|
||||||
help=_('Hosting vnf drivers tacker plugin will use')),
|
help=_('Hosting vnf drivers tacker plugin will use')),
|
||||||
]
|
]
|
||||||
cfg.CONF.register_opts(OPTS_POLICY_ACTION, 'tacker')
|
cfg.CONF.register_opts(OPTS_POLICY_ACTION, 'tacker')
|
||||||
|
@ -239,11 +239,11 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
dev_attrs = vnf_dict['attributes']
|
dev_attrs = vnf_dict['attributes']
|
||||||
mgmt_url = vnf_dict['mgmt_url']
|
mgmt_url = vnf_dict['mgmt_url']
|
||||||
if 'monitoring_policy' in dev_attrs and mgmt_url:
|
if 'monitoring_policy' in dev_attrs and mgmt_url:
|
||||||
def action_cb(action):
|
def action_cb(action, **kwargs):
|
||||||
LOG.debug('policy action: %s', action)
|
LOG.debug('policy action: %s', action)
|
||||||
self._vnf_action.invoke(
|
self._vnf_action.invoke(
|
||||||
action, 'execute_action', plugin=self, context=context,
|
action, 'execute_action', plugin=self, context=context,
|
||||||
vnf_dict=hosting_vnf['vnf'], args={})
|
vnf_dict=hosting_vnf['vnf'], args=kwargs)
|
||||||
|
|
||||||
hosting_vnf = self._vnf_monitor.to_hosting_vnf(
|
hosting_vnf = self._vnf_monitor.to_hosting_vnf(
|
||||||
vnf_dict, action_cb)
|
vnf_dict, action_cb)
|
||||||
|
@ -435,8 +435,8 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
self._create_vnf_wait(context, vnf_dict, vim_auth, infra_driver)
|
self._create_vnf_wait(context, vnf_dict, vim_auth, infra_driver)
|
||||||
return vnf_dict
|
return vnf_dict
|
||||||
|
|
||||||
def _update_vnf_wait(self, context, vnf_dict, vim_auth, driver_name):
|
def _update_vnf_wait(self, context, vnf_dict, vim_auth, driver_name,
|
||||||
instance_id = self._instance_id(vnf_dict)
|
vnf_heal=False):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_UPDATE_VNF,
|
mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_UPDATE_VNF,
|
||||||
mgmt_constants.KEY_KWARGS: {'vnf': vnf_dict},
|
mgmt_constants.KEY_KWARGS: {'vnf': vnf_dict},
|
||||||
|
@ -448,9 +448,15 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
try:
|
try:
|
||||||
self._vnf_manager.invoke(
|
self._vnf_manager.invoke(
|
||||||
driver_name, 'update_wait', plugin=self,
|
driver_name, 'update_wait', plugin=self,
|
||||||
context=context, vnf_id=instance_id, auth_attr=vim_auth,
|
context=context, vnf_dict=vnf_dict, auth_attr=vim_auth,
|
||||||
region_name=region_name)
|
region_name=region_name)
|
||||||
self.mgmt_call(context, vnf_dict, kwargs)
|
self.mgmt_call(context, vnf_dict, kwargs)
|
||||||
|
except vnfm.VNFUpdateWaitFailed as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
new_status = constants.ERROR
|
||||||
|
self._vnf_monitor.delete_hosting_vnf(vnf_dict['id'])
|
||||||
|
self.set_vnf_error_status_reason(context, vnf_dict['id'],
|
||||||
|
six.text_type(e))
|
||||||
except exceptions.MgmtDriverException as e:
|
except exceptions.MgmtDriverException as e:
|
||||||
LOG.error('VNF configuration failed')
|
LOG.error('VNF configuration failed')
|
||||||
new_status = constants.ERROR
|
new_status = constants.ERROR
|
||||||
|
@ -460,8 +466,21 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
vnf_dict['status'] = new_status
|
vnf_dict['status'] = new_status
|
||||||
self.mgmt_update_post(context, vnf_dict)
|
self.mgmt_update_post(context, vnf_dict)
|
||||||
|
|
||||||
self._update_vnf_post(context, vnf_dict['id'],
|
if vnf_heal:
|
||||||
new_status, vnf_dict)
|
# Update vnf status to 'ACTIVE' so that monitoring can be resumed.
|
||||||
|
evt_details = ("Ends the heal vnf request for VNF '%s'" %
|
||||||
|
vnf_dict['id'])
|
||||||
|
self._vnf_monitor.update_hosting_vnf(vnf_dict, evt_details)
|
||||||
|
# _update_vnf_post() method updates vnf_status and mgmt_url
|
||||||
|
self._update_vnf_post(context, vnf_dict['id'],
|
||||||
|
new_status, vnf_dict,
|
||||||
|
constants.PENDING_HEAL,
|
||||||
|
constants.RES_EVT_HEAL)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._update_vnf_post(context, vnf_dict['id'], new_status,
|
||||||
|
vnf_dict, constants.PENDING_UPDATE,
|
||||||
|
constants.RES_EVT_UPDATE)
|
||||||
|
|
||||||
def update_vnf(self, context, vnf_id, vnf):
|
def update_vnf(self, context, vnf_id, vnf):
|
||||||
vnf_attributes = vnf['vnf']['attributes']
|
vnf_attributes = vnf['vnf']['attributes']
|
||||||
|
@ -474,7 +493,8 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
vnf_attributes['config'] = yaml.safe_dump(config)
|
vnf_attributes['config'] = yaml.safe_dump(config)
|
||||||
else:
|
else:
|
||||||
self._report_deprecated_yaml_str()
|
self._report_deprecated_yaml_str()
|
||||||
vnf_dict = self._update_vnf_pre(context, vnf_id)
|
vnf_dict = self._update_vnf_pre(context, vnf_id,
|
||||||
|
constants.PENDING_UPDATE)
|
||||||
driver_name, vim_auth = self._get_infra_driver(context, vnf_dict)
|
driver_name, vim_auth = self._get_infra_driver(context, vnf_dict)
|
||||||
instance_id = self._instance_id(vnf_dict)
|
instance_id = self._instance_id(vnf_dict)
|
||||||
|
|
||||||
|
@ -494,12 +514,48 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||||
self.mgmt_update_post(context, vnf_dict)
|
self.mgmt_update_post(context, vnf_dict)
|
||||||
self._update_vnf_post(context, vnf_id,
|
self._update_vnf_post(context, vnf_id,
|
||||||
constants.ERROR,
|
constants.ERROR,
|
||||||
vnf_dict)
|
vnf_dict, constants.PENDING_UPDATE,
|
||||||
|
constants.RES_EVT_UPDATE)
|
||||||
|
|
||||||
self.spawn_n(self._update_vnf_wait, context, vnf_dict, vim_auth,
|
self.spawn_n(self._update_vnf_wait, context, vnf_dict, vim_auth,
|
||||||
driver_name)
|
driver_name)
|
||||||
return vnf_dict
|
return vnf_dict
|
||||||
|
|
||||||
|
def heal_vnf(self, context, vnf_id, heal_request_data_obj):
|
||||||
|
vnf_dict = self._update_vnf_pre(context, vnf_id,
|
||||||
|
constants.PENDING_HEAL)
|
||||||
|
driver_name, vim_auth = self._get_infra_driver(context, vnf_dict)
|
||||||
|
# Update vnf status to 'PENDING_HEAL' so that monitoring can
|
||||||
|
# be paused.
|
||||||
|
evt_details = ("Starts heal vnf request for VNF '%s'. "
|
||||||
|
"Reason to Heal VNF: '%s'" % (vnf_dict['id'],
|
||||||
|
heal_request_data_obj.cause))
|
||||||
|
self._vnf_monitor.update_hosting_vnf(vnf_dict, evt_details)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.mgmt_update_pre(context, vnf_dict)
|
||||||
|
self._vnf_manager.invoke(
|
||||||
|
driver_name, 'heal_vdu', plugin=self,
|
||||||
|
context=context, vnf_dict=vnf_dict,
|
||||||
|
heal_request_data_obj=heal_request_data_obj)
|
||||||
|
except vnfm.VNFHealFailed as e:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
vnf_dict['status'] = constants.ERROR
|
||||||
|
self._vnf_monitor.delete_hosting_vnf(vnf_id)
|
||||||
|
self.set_vnf_error_status_reason(context,
|
||||||
|
vnf_dict['id'],
|
||||||
|
six.text_type(e))
|
||||||
|
self.mgmt_update_post(context, vnf_dict)
|
||||||
|
self._update_vnf_post(context, vnf_id,
|
||||||
|
constants.ERROR,
|
||||||
|
vnf_dict, constants.PENDING_HEAL,
|
||||||
|
constants.RES_EVT_HEAL)
|
||||||
|
|
||||||
|
self.spawn_n(self._update_vnf_wait, context, vnf_dict, vim_auth,
|
||||||
|
driver_name, vnf_heal=True)
|
||||||
|
|
||||||
|
return vnf_dict
|
||||||
|
|
||||||
def _delete_vnf_wait(self, context, vnf_dict, auth_attr, driver_name):
|
def _delete_vnf_wait(self, context, vnf_dict, auth_attr, driver_name):
|
||||||
instance_id = self._instance_id(vnf_dict)
|
instance_id = self._instance_id(vnf_dict)
|
||||||
e = None
|
e = None
|
||||||
|
|
|
@ -13,25 +13,14 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from tacker.db.common_services import common_services_db_plugin
|
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
from tacker.vnfm.policy_actions import abstract_action
|
from tacker.vnfm.policy_actions import abstract_action
|
||||||
|
from tacker.vnfm import utils as vnfm_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _log_monitor_events(context, vnf_dict, evt_details):
|
|
||||||
_cos_db_plg = common_services_db_plugin.CommonServicesPluginDb()
|
|
||||||
_cos_db_plg.create_event(context, res_id=vnf_dict['id'],
|
|
||||||
res_type=constants.RES_TYPE_VNF,
|
|
||||||
res_state=vnf_dict['status'],
|
|
||||||
evt_type=constants.RES_EVT_MONITOR,
|
|
||||||
tstamp=timeutils.utcnow(),
|
|
||||||
details=evt_details)
|
|
||||||
|
|
||||||
|
|
||||||
class VNFActionAutoscaling(abstract_action.AbstractPolicyAction):
|
class VNFActionAutoscaling(abstract_action.AbstractPolicyAction):
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
return 'autoscaling'
|
return 'autoscaling'
|
||||||
|
@ -44,7 +33,7 @@ class VNFActionAutoscaling(abstract_action.AbstractPolicyAction):
|
||||||
|
|
||||||
def execute_action(self, plugin, context, vnf_dict, args):
|
def execute_action(self, plugin, context, vnf_dict, args):
|
||||||
vnf_id = vnf_dict['id']
|
vnf_id = vnf_dict['id']
|
||||||
_log_monitor_events(context,
|
vnfm_utils.log_events(context, vnf_dict,
|
||||||
vnf_dict,
|
constants.RES_EVT_MONITOR,
|
||||||
"ActionAutoscalingHeat invoked")
|
"ActionAutoscalingHeat invoked")
|
||||||
plugin.create_vnf_scale(context, vnf_id, args)
|
plugin.create_vnf_scale(context, vnf_id, args)
|
||||||
|
|
|
@ -13,25 +13,14 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from tacker.db.common_services import common_services_db_plugin
|
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
from tacker.vnfm.policy_actions import abstract_action
|
from tacker.vnfm.policy_actions import abstract_action
|
||||||
|
from tacker.vnfm import utils as vnfm_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _log_monitor_events(context, vnf_dict, evt_details):
|
|
||||||
_cos_db_plg = common_services_db_plugin.CommonServicesPluginDb()
|
|
||||||
_cos_db_plg.create_event(context, res_id=vnf_dict['id'],
|
|
||||||
res_type=constants.RES_TYPE_VNF,
|
|
||||||
res_state=vnf_dict['status'],
|
|
||||||
evt_type=constants.RES_EVT_MONITOR,
|
|
||||||
tstamp=timeutils.utcnow(),
|
|
||||||
details=evt_details)
|
|
||||||
|
|
||||||
|
|
||||||
class VNFActionLog(abstract_action.AbstractPolicyAction):
|
class VNFActionLog(abstract_action.AbstractPolicyAction):
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
return 'log'
|
return 'log'
|
||||||
|
@ -45,9 +34,9 @@ class VNFActionLog(abstract_action.AbstractPolicyAction):
|
||||||
def execute_action(self, plugin, context, vnf_dict, args):
|
def execute_action(self, plugin, context, vnf_dict, args):
|
||||||
vnf_id = vnf_dict['id']
|
vnf_id = vnf_dict['id']
|
||||||
LOG.error('vnf %s dead', vnf_id)
|
LOG.error('vnf %s dead', vnf_id)
|
||||||
_log_monitor_events(context,
|
vnfm_utils.log_events(context, vnf_dict,
|
||||||
vnf_dict,
|
constants.RES_EVT_MONITOR,
|
||||||
"ActionLogOnly invoked")
|
"ActionLogOnly invoked")
|
||||||
|
|
||||||
|
|
||||||
class VNFActionLogAndKill(abstract_action.AbstractPolicyAction):
|
class VNFActionLogAndKill(abstract_action.AbstractPolicyAction):
|
||||||
|
@ -61,9 +50,9 @@ class VNFActionLogAndKill(abstract_action.AbstractPolicyAction):
|
||||||
return 'Tacker VNF log_and_kill policy'
|
return 'Tacker VNF log_and_kill policy'
|
||||||
|
|
||||||
def execute_action(self, plugin, context, vnf_dict, args):
|
def execute_action(self, plugin, context, vnf_dict, args):
|
||||||
_log_monitor_events(context,
|
vnfm_utils.log_events(context, vnf_dict,
|
||||||
vnf_dict,
|
constants.RES_EVT_MONITOR,
|
||||||
"ActionLogAndKill invoked")
|
"ActionLogAndKill invoked")
|
||||||
vnf_id = vnf_dict['id']
|
vnf_id = vnf_dict['id']
|
||||||
if plugin._mark_vnf_dead(vnf_dict['id']):
|
if plugin._mark_vnf_dead(vnf_dict['id']):
|
||||||
if vnf_dict['attributes'].get('monitoring_policy'):
|
if vnf_dict['attributes'].get('monitoring_policy'):
|
||||||
|
|
|
@ -13,27 +13,16 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from tacker.db.common_services import common_services_db_plugin
|
|
||||||
from tacker.plugins.common import constants
|
from tacker.plugins.common import constants
|
||||||
from tacker.vnfm.infra_drivers.openstack import heat_client as hc
|
from tacker.vnfm.infra_drivers.openstack import heat_client as hc
|
||||||
from tacker.vnfm.policy_actions import abstract_action
|
from tacker.vnfm.policy_actions import abstract_action
|
||||||
|
from tacker.vnfm import utils as vnfm_utils
|
||||||
from tacker.vnfm import vim_client
|
from tacker.vnfm import vim_client
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _log_monitor_events(context, vnf_dict, evt_details):
|
|
||||||
_cos_db_plg = common_services_db_plugin.CommonServicesPluginDb()
|
|
||||||
_cos_db_plg.create_event(context, res_id=vnf_dict['id'],
|
|
||||||
res_type=constants.RES_TYPE_VNF,
|
|
||||||
res_state=vnf_dict['status'],
|
|
||||||
evt_type=constants.RES_EVT_MONITOR,
|
|
||||||
tstamp=timeutils.utcnow(),
|
|
||||||
details=evt_details)
|
|
||||||
|
|
||||||
|
|
||||||
class VNFActionRespawn(abstract_action.AbstractPolicyAction):
|
class VNFActionRespawn(abstract_action.AbstractPolicyAction):
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
return 'respawn'
|
return 'respawn'
|
||||||
|
@ -71,7 +60,9 @@ class VNFActionRespawn(abstract_action.AbstractPolicyAction):
|
||||||
heatclient.delete(vnf_dict['instance_id'])
|
heatclient.delete(vnf_dict['instance_id'])
|
||||||
LOG.debug("Heat stack %s delete initiated",
|
LOG.debug("Heat stack %s delete initiated",
|
||||||
vnf_dict['instance_id'])
|
vnf_dict['instance_id'])
|
||||||
_log_monitor_events(context, vnf_dict, "ActionRespawnHeat invoked")
|
vnfm_utils.log_events(context, vnf_dict,
|
||||||
|
constants.RES_EVT_MONITOR,
|
||||||
|
"ActionRespawnHeat invoked")
|
||||||
|
|
||||||
def _respawn_vnf():
|
def _respawn_vnf():
|
||||||
update_vnf_dict = plugin.create_vnf_sync(context, vnf_dict)
|
update_vnf_dict = plugin.create_vnf_sync(context, vnf_dict)
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
#
|
||||||
|
# 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 oslo_log import log as logging
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from tacker import objects
|
||||||
|
from tacker.vnfm.policy_actions import abstract_action
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VNFActionVduAutoheal(abstract_action.AbstractPolicyAction):
|
||||||
|
def get_type(self):
|
||||||
|
return 'vdu_autoheal'
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return 'vdu_autoheal'
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return 'Tacker VNF vdu_autoheal policy'
|
||||||
|
|
||||||
|
def execute_action(self, plugin, context, vnf_dict, args):
|
||||||
|
vdu_name = args.get('vdu_name')
|
||||||
|
if vdu_name is None:
|
||||||
|
LOG.error("VDU resource of vnf '%s' is not present for "
|
||||||
|
"autoheal." % vnf_dict['id'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def _get_vdu_resources():
|
||||||
|
"""Get all the resources linked to the VDU.
|
||||||
|
|
||||||
|
Returns: resource list for eg. ['VDU1', CP1]
|
||||||
|
"""
|
||||||
|
resource_list = [vdu_name]
|
||||||
|
heat_template = yaml.safe_load(vnf_dict['attributes'].get(
|
||||||
|
'heat_template'))
|
||||||
|
vdu_resources = heat_template['resources'].get(vdu_name)
|
||||||
|
cp_resources = vdu_resources['properties'].get('networks')
|
||||||
|
for resource in cp_resources:
|
||||||
|
resource_list.append(resource['port'].get('get_resource'))
|
||||||
|
|
||||||
|
return resource_list
|
||||||
|
|
||||||
|
resource_list = _get_vdu_resources()
|
||||||
|
additional_params = []
|
||||||
|
for resource in resource_list:
|
||||||
|
additional_paramas_obj = objects.HealVnfAdditionalParams(
|
||||||
|
parameter=resource,
|
||||||
|
cause=["Unable to reach while monitoring resource: '%s'" %
|
||||||
|
resource])
|
||||||
|
additional_params.append(additional_paramas_obj)
|
||||||
|
|
||||||
|
heal_request_data_obj = objects.HealVnfRequest(
|
||||||
|
cause=("Failed to monitor VDU resource '%s'" % vdu_name),
|
||||||
|
additional_params=additional_params)
|
||||||
|
|
||||||
|
plugin.heal_vnf(context, vnf_dict['id'], heal_request_data_obj)
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Copyright 2018, OpenStack Foundation.
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Borrowed from nova code base, more utilities will be added/borrowed as and
|
||||||
|
# when needed.
|
||||||
|
|
||||||
|
"""Utilities and helper functions."""
|
||||||
|
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
from tacker.db.common_services import common_services_db_plugin
|
||||||
|
from tacker.plugins.common import constants
|
||||||
|
|
||||||
|
|
||||||
|
def log_events(context, vnf_dict, evt_type, evt_details):
|
||||||
|
_cos_db_plg = common_services_db_plugin.CommonServicesPluginDb()
|
||||||
|
_cos_db_plg.create_event(context, res_id=vnf_dict['id'],
|
||||||
|
res_type=constants.RES_TYPE_VNF,
|
||||||
|
res_state=vnf_dict['status'],
|
||||||
|
evt_type=evt_type,
|
||||||
|
tstamp=timeutils.utcnow(),
|
||||||
|
details=evt_details)
|
Loading…
Reference in New Issue