diff --git a/releasenotes/notes/add-vim-monitor-to-tacker-3bccceaeb2ef6989.yaml b/releasenotes/notes/add-vim-monitor-to-tacker-3bccceaeb2ef6989.yaml new file mode 100644 index 000000000..618bf5b7d --- /dev/null +++ b/releasenotes/notes/add-vim-monitor-to-tacker-3bccceaeb2ef6989.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add VIM health monitor to Tacker diff --git a/tacker/db/migration/alembic_migrations/versions/22f5385a3d3f_add_status_to_vims.py b/tacker/db/migration/alembic_migrations/versions/22f5385a3d3f_add_status_to_vims.py new file mode 100644 index 000000000..c180de6fc --- /dev/null +++ b/tacker/db/migration/alembic_migrations/versions/22f5385a3d3f_add_status_to_vims.py @@ -0,0 +1,35 @@ +# Copyright 2016 OpenStack Foundation +# +# 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. +# + +"""Add status to vims + +Revision ID: 22f5385a3d3f +Revises: 5246a6bd410f +Create Date: 2016-05-12 13:29:30.615609 + +""" + +# revision identifiers, used by Alembic. +revision = '22f5385a3d3f' +down_revision = '5f88e86b35c7' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(active_plugins=None, options=None): + op.add_column('vims', + sa.Column('status', sa.String(255), + nullable=False, server_default='')) diff --git a/tacker/db/migration/alembic_migrations/versions/HEAD b/tacker/db/migration/alembic_migrations/versions/HEAD index 916d863da..bfeee502c 100644 --- a/tacker/db/migration/alembic_migrations/versions/HEAD +++ b/tacker/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -5f88e86b35c7 \ No newline at end of file +22f5385a3d3f diff --git a/tacker/db/nfvo/nfvo_db.py b/tacker/db/nfvo/nfvo_db.py index b116066a7..419d4ffbb 100644 --- a/tacker/db/nfvo/nfvo_db.py +++ b/tacker/db/nfvo/nfvo_db.py @@ -33,7 +33,7 @@ from tacker import manager VIM_ATTRIBUTES = ('id', 'type', 'tenant_id', 'name', 'description', - 'placement_attr', 'shared') + 'placement_attr', 'shared', 'status') VIM_AUTH_ATTRIBUTES = ('auth_url', 'vim_project', 'password', 'auth_cred') @@ -45,6 +45,7 @@ class Vim(model_base.BASE, models_v1.HasId, models_v1.HasTenant): shared = sa.Column(sa.Boolean, default=True, server_default=sql.true( ), nullable=False) vim_auth = orm.relationship('VimAuth') + status = sa.Column(sa.String(255), nullable=False) class VimAuth(model_base.BASE, models_v1.HasId): @@ -102,7 +103,8 @@ class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin): tenant_id=vim.get('tenant_id'), name=vim.get('name'), description=vim.get('description'), - placement_attr=vim.get('placement_attr')) + placement_attr=vim.get('placement_attr'), + status=vim.get('status')) context.session.add(vim_db) vim_auth_db = VimAuth( id=str(uuid.uuid4()), @@ -153,6 +155,16 @@ class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin): vim_project}) return self.get_vim(context, vim_id) + def update_vim_status(self, context, vim_id, status): + with context.session.begin(subtransactions=True): + try: + vim_db = (self._model_query(context, Vim).filter( + Vim.id == vim_id).with_lockmode('update').one()) + except orm_exc.NoResultFound: + raise nfvo.VimNotFoundException(vim_id=vim_id) + vim_db.update({'status': status}) + return self._make_vim_dict(vim_db) + def get_vim_by_name(self, context, vim_name, fields=None, mask_password=True): vim_db = self._get_by_name(context, Vim, vim_name) diff --git a/tacker/extensions/nfvo.py b/tacker/extensions/nfvo.py index 14fc53bee..9801df6ab 100644 --- a/tacker/extensions/nfvo.py +++ b/tacker/extensions/nfvo.py @@ -120,6 +120,12 @@ RESOURCE_ATTRIBUTE_MAP = { 'is_visible': True, 'default': '', }, + 'status': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + }, 'placement_attr': { 'allow_post': False, 'allow_put': False, diff --git a/tacker/nfvo/drivers/vim/abstract_vim_driver.py b/tacker/nfvo/drivers/vim/abstract_vim_driver.py index 73c3a3131..611a24c9c 100644 --- a/tacker/nfvo/drivers/vim/abstract_vim_driver.py +++ b/tacker/nfvo/drivers/vim/abstract_vim_driver.py @@ -82,3 +82,11 @@ class VimAbstractDriver(extensions.PluginInterface): Delete VIM sensitive information such as keys from file system or DB """ pass + + @abc.abstractmethod + def vim_status(self, auth_url): + """Health check for VIM + + Checks the health status of VIM and return a boolean value + """ + pass diff --git a/tacker/nfvo/drivers/vim/openstack_driver.py b/tacker/nfvo/drivers/vim/openstack_driver.py index 6041cb013..73d719135 100644 --- a/tacker/nfvo/drivers/vim/openstack_driver.py +++ b/tacker/nfvo/drivers/vim/openstack_driver.py @@ -20,8 +20,10 @@ from keystoneclient import exceptions from oslo_config import cfg from oslo_log import log as logging +from tacker.agent.linux import utils as linux_utils from tacker.common import log from tacker.extensions import nfvo +from tacker.i18n import _LW from tacker.nfvo.drivers.vim import abstract_vim_driver from tacker.vm import keystone @@ -31,7 +33,18 @@ CONF = cfg.CONF OPTS = [cfg.StrOpt('openstack', default='/etc/tacker/vim/fernet_keys', help='Dir.path to store fernet keys.')] + +# same params as we used in ping monitor driver +OPENSTACK_OPTS = [ + cfg.StrOpt('count', default='1', + help=_('number of ICMP packets to send')), + cfg.StrOpt('timeout', default='1', + help=_('number of seconds to wait for a response')), + cfg.StrOpt('interval', default='1', + help=_('number of seconds to wait between packets')) +] cfg.CONF.register_opts(OPTS, 'vim_keys') +cfg.CONF.register_opts(OPENSTACK_OPTS, 'vim_monitor') class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver): @@ -178,3 +191,20 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver): LOG.debug(_('VIM auth successfully stored for vim %s'), vim_id) except IOError: raise nfvo.VimKeyNotFoundException(vim_id=vim_id) + + @log.log + def vim_status(self, auth_url): + """Checks the VIM health status""" + vim_ip = auth_url.split("//")[-1].split(":")[0].split("/")[0] + ping_cmd = ['ping', + '-c', cfg.CONF.vim_monitor.count, + '-W', cfg.CONF.vim_monitor.timeout, + '-i', cfg.CONF.vim_monitor.interval, + vim_ip] + + try: + linux_utils.execute(ping_cmd, check_exit_code=True) + return True + except RuntimeError: + LOG.warning(_LW("Cannot ping ip address: %s"), vim_ip) + return False diff --git a/tacker/nfvo/nfvo_plugin.py b/tacker/nfvo/nfvo_plugin.py index 61f02f3e9..4a1bcae4e 100644 --- a/tacker/nfvo/nfvo_plugin.py +++ b/tacker/nfvo/nfvo_plugin.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import threading +import time import uuid from oslo_config import cfg @@ -23,6 +25,7 @@ from oslo_utils import excutils from tacker.common import driver_manager from tacker.common import log from tacker.common import utils +from tacker import context as t_context from tacker.db.nfvo import nfvo_db LOG = logging.getLogger(__name__) @@ -37,11 +40,15 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb): extension for providing the specified VIM information """ supported_extension_aliases = ['nfvo'] + _lock = threading.RLock() OPTS = [ cfg.ListOpt( 'vim_drivers', default=['openstack'], help=_('VIM driver for launching VNFs')), + cfg.IntOpt( + 'monitor_interval', default=30, + help=_('Interval to check for VIM health')), ] cfg.CONF.register_opts(OPTS, 'nfvo_vim') @@ -50,6 +57,19 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb): self._vim_drivers = driver_manager.DriverManager( 'tacker.nfvo.vim.drivers', cfg.CONF.nfvo_vim.vim_drivers) + self._created_vims = dict() + context = t_context.get_admin_context() + vims = self.get_vims(context) + for vim in vims: + self._created_vims[vim["id"]] = vim + self._monitor_interval = cfg.CONF.nfvo_vim.monitor_interval + threading.Thread(target=self.__run__).start() + + def __run__(self): + while(1): + time.sleep(self._monitor_interval) + for created_vim in self._created_vims.values(): + self.monitor_vim(created_vim) @log.log def create_vim(self, context, vim): @@ -57,9 +77,14 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb): vim_obj = vim['vim'] vim_type = vim_obj['type'] vim_obj['id'] = str(uuid.uuid4()) + vim_obj['status'] = 'PENDING' try: self._vim_drivers.invoke(vim_type, 'register_vim', vim_obj=vim_obj) res = super(NfvoPlugin, self).create_vim(context, vim_obj) + vim_obj["status"] = "REGISTERING" + with self._lock: + self._created_vims[res["id"]] = res + self.monitor_vim(vim_obj) return res except Exception: with excutils.save_and_reraise_exception(): @@ -88,4 +113,22 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb): vim_obj = self._get_vim(context, vim_id) self._vim_drivers.invoke(vim_obj['type'], 'deregister_vim', vim_id=vim_id) + with self._lock: + self._created_vims.pop(vim_id, None) super(NfvoPlugin, self).delete_vim(context, vim_id) + + @log.log + def monitor_vim(self, vim_obj): + vim_id = vim_obj["id"] + auth_url = vim_obj["auth_url"] + vim_status = self._vim_drivers.invoke(vim_obj['type'], + 'vim_status', + auth_url=auth_url) + current_status = "REACHABLE" if vim_status else "UNREACHABLE" + if current_status != vim_obj["status"]: + status = current_status + with self._lock: + super(NfvoPlugin, self).update_vim_status( + t_context.get_admin_context(), + vim_id, status) + self._created_vims[vim_id]["status"] = status diff --git a/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py b/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py index 6eae1aa58..bb06b5640 100644 --- a/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py +++ b/tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py @@ -37,6 +37,7 @@ class TestNfvoPlugin(db_base.SqlTestCase): self.addCleanup(mock.patch.stopall) self.context = context.get_admin_context() self._mock_driver_manager() + mock.patch('tacker.nfvo.nfvo_plugin.NfvoPlugin.__run__').start() self.nfvo_plugin = nfvo_plugin.NfvoPlugin() def _mock_driver_manager(self): @@ -56,6 +57,7 @@ class TestNfvoPlugin(db_base.SqlTestCase): name='fake_vim', description='fake_vim_description', type='openstack', + status='Active', placement_attr={'regions': ['RegionOne']}) vim_auth_db = nfvo_db.VimAuth( vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff', @@ -78,10 +80,10 @@ class TestNfvoPlugin(db_base.SqlTestCase): 'tenant_id': 'test-project'}} vim_type = 'openstack' res = self.nfvo_plugin.create_vim(self.context, vim_dict) - self._driver_manager.invoke.assert_called_once_with(vim_type, - 'register_vim', - vim_obj=vim_dict[ - 'vim']) + self._driver_manager.invoke.assert_any_call(vim_type, + 'register_vim', vim_obj=vim_dict['vim']) + self._driver_manager.invoke.assert_any_call('openstack', 'vim_status', + auth_url='http://localhost:5000') self.assertIsNotNone(res) self.assertEqual(SECRET_PASSWORD, res['auth_cred']['password']) self.assertIn('id', res) diff --git a/tacker/tests/unit/vm/test_plugin.py b/tacker/tests/unit/vm/test_plugin.py index 87c89185f..103d08558 100644 --- a/tacker/tests/unit/vm/test_plugin.py +++ b/tacker/tests/unit/vm/test_plugin.py @@ -132,6 +132,7 @@ class TestVNFMPlugin(db_base.SqlTestCase): name='fake_vim', description='fake_vim_description', type='openstack', + status='Active', placement_attr={'regions': ['RegionOne']}) vim_auth_db = nfvo_db.VimAuth( vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',