diff --git a/higgins/common/rpc_service.py b/higgins/common/rpc_service.py index 742015e76..d0954f50a 100644 --- a/higgins/common/rpc_service.py +++ b/higgins/common/rpc_service.py @@ -21,7 +21,7 @@ from oslo_service import service from higgins.common import rpc from higgins.objects import base as objects_base - +from higgins.servicegroup import higgins_service_periodic as servicegroup # NOTE(paulczar): # Ubuntu 14.04 forces librabbitmq when kombu is used @@ -39,7 +39,15 @@ TRANSPORT_ALIASES = { 'higgins.openstack.common.rpc.impl_zmq': 'zmq', } +periodic_opts = [ + cfg.IntOpt('periodic_interval_max', + default=60, + help='Max interval size between periodic tasks execution in ' + 'seconds.'), +] + CONF = cfg.CONF +CONF.register_opts(periodic_opts) class Service(service.Service): @@ -57,6 +65,7 @@ class Service(service.Service): self.binary = binary def start(self): + servicegroup.setup(CONF, self.binary, self.tg) self._server.start() def stop(self): diff --git a/higgins/objects/__init__.py b/higgins/objects/__init__.py index e69de29bb..1747ddabc 100644 --- a/higgins/objects/__init__.py +++ b/higgins/objects/__init__.py @@ -0,0 +1,19 @@ +# 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 higgins.objects import higgins_service + + +HigginsService = higgins_service.HigginsService + +__all__ = (HigginsService,) diff --git a/higgins/objects/higgins_service.py b/higgins/objects/higgins_service.py new file mode 100644 index 000000000..23d095529 --- /dev/null +++ b/higgins/objects/higgins_service.py @@ -0,0 +1,148 @@ +# 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 + +from higgins.db import api as dbapi +from higgins.objects import base + + +@base.HigginsObjectRegistry.register +class HigginsService(base.HigginsPersistentObject, base.HigginsObject, + base.HigginsObjectDictCompat): + # Version 1.0: Initial version + VERSION = '1.0' + + dbapi = dbapi.get_instance() + + fields = { + 'id': fields.IntegerField(), + 'host': fields.StringField(nullable=True), + 'binary': fields.StringField(nullable=True), + 'disabled': fields.BooleanField(), + 'disabled_reason': fields.StringField(nullable=True), + 'last_seen_up': fields.DateTimeField(nullable=True), + 'forced_down': fields.BooleanField(), + 'report_count': fields.IntegerField(), + } + + @staticmethod + def _from_db_object(higgins_service, db_higgins_service): + """Converts a database entity to a formal object.""" + for field in higgins_service.fields: + higgins_service[field] = db_higgins_service[field] + + higgins_service.obj_reset_changes() + return higgins_service + + @staticmethod + def _from_db_object_list(db_objects, cls, context): + """Converts a list of database entities to a list of formal objects.""" + return [HigginsService._from_db_object(cls(context), obj) + for obj in db_objects] + + @base.remotable_classmethod + def get_by_host_and_binary(cls, context, host, binary): + """Find a higgins_service based on its hostname and binary. + + :param host: The host on which the binary is running. + :param binary: The name of the binary. + :param context: Security context. + :returns: a :class:`HigginsService` object. + """ + db_higgins_service = cls.dbapi.get_higgins_service_by_host_and_binary( + context, host, binary) + if db_higgins_service is None: + return None + higgins_service = HigginsService._from_db_object( + cls(context), db_higgins_service) + return higgins_service + + @base.remotable_classmethod + def list(cls, context, limit=None, marker=None, + sort_key=None, sort_dir=None): + """Return a list of HigginsService objects. + + :param context: Security context. + :param limit: maximum number of resources to return in a single result. + :param marker: pagination marker for large data sets. + :param sort_key: column to sort results by. + :param sort_dir: direction to sort. "asc" or "desc". + :returns: a list of :class:`HigginsService` object. + + """ + db_higgins_services = cls.dbapi.get_higgins_service_list( + context, limit=limit, marker=marker, sort_key=sort_key, + sort_dir=sort_dir) + return HigginsService._from_db_object_list(db_higgins_services, cls, + context) + + @base.remotable + def create(self, context=None): + """Create a HigginsService record in the DB. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: HigginsService(context) + """ + values = self.obj_get_changes() + db_higgins_service = self.dbapi.create_higgins_service(values) + self._from_db_object(self, db_higgins_service) + + @base.remotable + def destroy(self, context=None): + """Delete the HigginsService from the DB. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: HigginsService(context) + """ + self.dbapi.destroy_higgins_service(self.id) + self.obj_reset_changes() + + @base.remotable + def save(self, context=None): + """Save updates to this HigginsService. + + Updates will be made column by column based on the result + of self.what_changed(). + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: HigginsService(context) + """ + updates = self.obj_get_changes() + self.dbapi.update_higgins_service(self.id, updates) + self.obj_reset_changes() + + @base.remotable + def report_state_up(self, context=None): + """Touching the higgins_service record to show aliveness. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: HigginsService(context) + """ + self.report_count += 1 + self.save() diff --git a/higgins/opts.py b/higgins/opts.py index 2246c0ec9..7952224e0 100644 --- a/higgins/opts.py +++ b/higgins/opts.py @@ -22,6 +22,7 @@ def list_opts(): return [ ('DEFAULT', itertools.chain( + higgins.common.rpc_service.periodic_opts, higgins.common.service.service_opts, )), ('api', higgins.api.app.API_SERVICE_OPTS), diff --git a/higgins/servicegroup/__init__.py b/higgins/servicegroup/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/higgins/servicegroup/higgins_service_periodic.py b/higgins/servicegroup/higgins_service_periodic.py new file mode 100644 index 000000000..73952e727 --- /dev/null +++ b/higgins/servicegroup/higgins_service_periodic.py @@ -0,0 +1,62 @@ +# 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. + +"""Higgins Service Layer""" + +from oslo_log import log +from oslo_service import periodic_task + +from higgins import objects +# from higgins.service import periodic + + +LOG = log.getLogger(__name__) + + +class HigginsServicePeriodicTasks(periodic_task.PeriodicTasks): + '''Higgins periodic Task class + + Any periodic task job need to be added into this class + ''' + + def __init__(self, conf, binary): + self.higgins_service_ref = None + self.host = conf.host + self.binary = binary + super(HigginsServicePeriodicTasks, self).__init__(conf) + + @periodic_task.periodic_task(run_immediately=True) + # TODO(wangjian): uncomment this when we need + # @periodic.set_context + def update_higgins_service(self, ctx): + LOG.debug('Update higgins_service') + if self.higgins_service_ref is None: + self.higgins_service_ref = \ + objects.HigginsService.get_by_host_and_binary( + ctx, self.host, self.binary) + if self.higgins_service_ref is None: + higgins_service_dict = { + 'host': self.host, + 'binary': self.binary + } + self.higgins_service_ref = objects.HigginsService( + ctx, **higgins_service_dict) + self.higgins_service_ref.create() + self.higgins_service_ref.report_state_up() + + +def setup(conf, binary, tg): + pt = HigginsServicePeriodicTasks(conf, binary) + tg.add_dynamic_timer( + pt.run_periodic_tasks, + periodic_interval_max=conf.periodic_interval_max, + context=None) diff --git a/higgins/tests/unit/base.py b/higgins/tests/base.py similarity index 74% rename from higgins/tests/unit/base.py rename to higgins/tests/base.py index 02059bce3..ff9dc411c 100644 --- a/higgins/tests/unit/base.py +++ b/higgins/tests/base.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # @@ -15,12 +13,24 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg +from oslo_log import log from oslotest import base +from higgins.tests import conf_fixture + + +CONF = cfg.CONF +try: + log.register_options(CONF) +except cfg.ArgsAlreadyParsedError: + pass +CONF.set_override('use_stderr', False) + class BaseTestCase(base.BaseTestCase): - """Test case base class for all unit tests.""" def setUp(self): super(BaseTestCase, self).setUp() + self.useFixture(conf_fixture.ConfFixture()) diff --git a/higgins/tests/conf_fixture.py b/higgins/tests/conf_fixture.py new file mode 100644 index 000000000..061387b6f --- /dev/null +++ b/higgins/tests/conf_fixture.py @@ -0,0 +1,31 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 fixtures +from oslo_config import cfg + +# from higgins.common import config + +CONF = cfg.CONF +CONF.import_opt('host', 'higgins.common.service') + + +class ConfFixture(fixtures.Fixture): + """Fixture to manage global conf settings.""" + + def _setUp(self): + CONF.set_default('host', 'fake-mini') + self.addCleanup(CONF.reset) diff --git a/higgins/tests/unit/servicegroup/__init__.py b/higgins/tests/unit/servicegroup/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/higgins/tests/unit/servicegroup/test_higgins_service.py b/higgins/tests/unit/servicegroup/test_higgins_service.py new file mode 100644 index 000000000..09af995ff --- /dev/null +++ b/higgins/tests/unit/servicegroup/test_higgins_service.py @@ -0,0 +1,73 @@ +# 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 higgins.common.rpc_service import CONF +from higgins import objects +from higgins.servicegroup import higgins_service_periodic as periodic +from higgins.tests import base + + +class HigginsServicePeriodicTestCase(base.BaseTestCase): + def setUp(self): + super(HigginsServicePeriodicTestCase, self).setUp() + mock_higgins_service_refresh = mock.Mock() + + class FakeSrv(object): + report_state_up = mock_higgins_service_refresh + + self.fake_srv = FakeSrv() + self.fake_srv_refresh = mock_higgins_service_refresh + + @mock.patch.object(objects.HigginsService, 'get_by_host_and_binary') + @mock.patch.object(objects.HigginsService, 'create') + @mock.patch.object(objects.HigginsService, 'report_state_up') + def test_update_higgins_service_firsttime(self, + mock_srv_refresh, + mock_srv_create, + mock_srv_get + ): + p_task = periodic.HigginsServicePeriodicTasks(CONF, + 'fake-conductor') + mock_srv_get.return_value = None + + p_task.update_higgins_service(None) + + mock_srv_get.assert_called_once_with(mock.ANY, p_task.host, + p_task.binary) + mock_srv_create.assert_called_once_with() + mock_srv_refresh.assert_called_once_with() + + @mock.patch.object(objects.HigginsService, 'get_by_host_and_binary') + @mock.patch.object(objects.HigginsService, 'create') + def test_update_higgins_service_on_restart(self, + mock_srv_create, + mock_srv_get): + p_task = periodic.HigginsServicePeriodicTasks(CONF, + 'fake-conductor') + mock_srv_get.return_value = self.fake_srv + + p_task.update_higgins_service(None) + + mock_srv_get.assert_called_once_with(mock.ANY, p_task.host, + p_task.binary) + self.fake_srv_refresh.assert_called_once_with() + + def test_update_higgins_service_regular(self): + p_task = periodic.HigginsServicePeriodicTasks(CONF, + 'fake-conductor') + p_task.higgins_service_ref = self.fake_srv + + p_task.update_higgins_service(None) + + self.fake_srv_refresh.assert_called_once_with() diff --git a/higgins/tests/unit/test_hacking.py b/higgins/tests/unit/test_hacking.py index 7d9146c5b..d6f829e45 100644 --- a/higgins/tests/unit/test_hacking.py +++ b/higgins/tests/unit/test_hacking.py @@ -18,7 +18,7 @@ import mock import pep8 from higgins.hacking import checks -from higgins.tests.unit import base +from higgins.tests import base class HackingTestCase(base.BaseTestCase): diff --git a/higgins/tests/unit/test_higgins.py b/higgins/tests/unit/test_higgins.py index 9e6db4fe5..9519c3f51 100644 --- a/higgins/tests/unit/test_higgins.py +++ b/higgins/tests/unit/test_higgins.py @@ -19,7 +19,7 @@ test_higgins Tests for `higgins` module. """ -from higgins.tests.unit import base +from higgins.tests import base class TestHiggins(base.BaseTestCase):