Browse Source
To support 'magnum service-list' after 'nova service-list', we need to introduce periodic status update functionality for internal services. Change-Id: Ia0c09222405c87cb61e5de4a43ba345ae3405b50 Partially-Implements: blueprint magnum-service-list Closes-bug: #1492501changes/02/220702/10
17 changed files with 665 additions and 10 deletions
@ -0,0 +1,49 @@
|
||||
# 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. |
||||
|
||||
"""adding magnum_service functionality |
||||
|
||||
Revision ID: 27ad304554e2 |
||||
Revises: 1d045384b966 |
||||
Create Date: 2015-09-01 18:27:14.371860 |
||||
|
||||
""" |
||||
|
||||
# revision identifiers, used by Alembic. |
||||
revision = '27ad304554e2' |
||||
down_revision = '1d045384b966' |
||||
|
||||
from alembic import op |
||||
import sqlalchemy as sa |
||||
|
||||
|
||||
def upgrade(): |
||||
op.create_table( |
||||
'magnum_service', |
||||
sa.Column('created_at', sa.DateTime(), nullable=True), |
||||
sa.Column('updated_at', sa.DateTime(), nullable=True), |
||||
sa.Column('id', sa.Integer(), nullable=False), |
||||
sa.Column('report_count', sa.Integer(), nullable=False), |
||||
sa.Column('host', sa.String(length=255), nullable=True), |
||||
sa.Column('binary', sa.String(length=255), nullable=True), |
||||
sa.Column('disabled', sa.Boolean(), nullable=True), |
||||
sa.Column('disabled_reason', sa.String(length=255), nullable=True), |
||||
# 'last_seen_up' has different purpose than 'updated_at'. |
||||
# 'updated_at' refers to any modification of the entry, which can |
||||
# be administrative too, whereas 'last_seen_up' is more related to |
||||
# magnum_service. Modeled after nova/servicegroup |
||||
sa.Column('last_seen_up', sa.DateTime(), nullable=True), |
||||
sa.Column('forced_down', sa.Boolean(), nullable=True), |
||||
sa.PrimaryKeyConstraint('id'), |
||||
sa.UniqueConstraint('host', 'binary', |
||||
name='uniq_magnum_service0host0binary') |
||||
) |
@ -0,0 +1,127 @@
|
||||
# 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 magnum.db import api as dbapi |
||||
from magnum.objects import base |
||||
|
||||
|
||||
@base.MagnumObjectRegistry.register |
||||
class MagnumService(base.MagnumPersistentObject, base.MagnumObject, |
||||
base.MagnumObjectDictCompat): |
||||
# 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(magnum_service, db_magnum_service): |
||||
"""Converts a database entity to a formal object.""" |
||||
for field in magnum_service.fields: |
||||
magnum_service[field] = db_magnum_service[field] |
||||
|
||||
magnum_service.obj_reset_changes() |
||||
return magnum_service |
||||
|
||||
@staticmethod |
||||
def _from_db_object_list(db_objects, cls, context): |
||||
"""Converts a list of database entities to a list of formal objects.""" |
||||
return [MagnumService._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 magnum_service based on its hostname and binary. |
||||
|
||||
:param host: The host on which the binary is running. |
||||
:param binary: The name of the binary. |
||||
:returns: a :class:`MagnumService` object. |
||||
""" |
||||
db_magnum_service = cls.dbapi.get_magnum_service_by_host_and_binary( |
||||
context, host, binary) |
||||
if db_magnum_service is None: |
||||
return None |
||||
magnum_service = MagnumService._from_db_object( |
||||
cls(context), db_magnum_service) |
||||
return magnum_service |
||||
|
||||
@base.remotable_classmethod |
||||
def list(cls, context, limit=None, marker=None, |
||||
sort_key=None, sort_dir=None): |
||||
"""Return a list of MagnumService 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:`MagnumService` object. |
||||
|
||||
""" |
||||
db_magnum_services = cls.dbapi.get_magnum_service_list( |
||||
context, limit=limit, marker=marker, sort_key=sort_key, |
||||
sort_dir=sort_dir) |
||||
return MagnumService._from_db_object_list(db_magnum_services, cls, |
||||
context) |
||||
|
||||
@base.remotable |
||||
def create(self, context=None): |
||||
"""Create a MagnumService record in the DB. |
||||
|
||||
:param context: Security context. |
||||
""" |
||||
values = self.obj_get_changes() |
||||
db_magnum_service = self.dbapi.create_magnum_service(values) |
||||
self._from_db_object(self, db_magnum_service) |
||||
|
||||
@base.remotable |
||||
def destroy(self, context=None): |
||||
"""Delete the MagnumService from the DB. |
||||
|
||||
:param context: Security context. |
||||
""" |
||||
self.dbapi.destroy_magnum_service(self.id) |
||||
self.obj_reset_changes() |
||||
|
||||
@base.remotable |
||||
def save(self, context=None): |
||||
"""Save updates to this MagnumService. |
||||
|
||||
Updates will be made column by column based on the result |
||||
of self.what_changed(). |
||||
|
||||
:param context: Security context. |
||||
""" |
||||
updates = self.obj_get_changes() |
||||
self.dbapi.update_magnum_service(self.id, updates) |
||||
self.obj_reset_changes() |
||||
|
||||
@base.remotable |
||||
def report_state_up(self, context=None): |
||||
"""Touching the magnum_service record to show aliveness. |
||||
|
||||
:param context: Security context. |
||||
""" |
||||
self.report_count += 1 |
||||
self.save(context) |
@ -0,0 +1,33 @@
|
||||
# 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 timeutils |
||||
|
||||
from magnum.db.sqlalchemy import models |
||||
|
||||
|
||||
class API(object): |
||||
def __init__(self, conf): |
||||
self.service_down_time = 3 * conf.periodic_interval_max |
||||
|
||||
def service_is_up(self, member): |
||||
if not isinstance(member, models.MagnumService): |
||||
raise TypeError |
||||
if member.get('forced_down'): |
||||
return False |
||||
|
||||
last_heartbeat = (member.get( |
||||
'last_seen_up') or member['updated_at'] or member['created_at']) |
||||
elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow()) |
||||
is_up = abs(elapsed) <= self.service_down_time |
||||
return is_up |
@ -0,0 +1,100 @@
|
||||
# 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. |
||||
|
||||
"""Tests for manipulating MagnumService via the DB API""" |
||||
|
||||
from magnum.common import context # NOQA |
||||
from magnum.common import exception |
||||
from magnum.tests.unit.db import base |
||||
from magnum.tests.unit.db import utils |
||||
|
||||
|
||||
class DbMagnumServiceTestCase(base.DbTestCase): |
||||
|
||||
def test_create_magnum_service(self): |
||||
utils.create_test_magnum_service() |
||||
|
||||
def test_create_magnum_service_failure_for_dup(self): |
||||
utils.create_test_magnum_service() |
||||
self.assertRaises(exception.MagnumServiceAlreadyExists, |
||||
utils.create_test_magnum_service) |
||||
|
||||
def test_get_magnum_service_by_host_and_binary(self): |
||||
ms = utils.create_test_magnum_service() |
||||
res = self.dbapi.get_magnum_service_by_host_and_binary( |
||||
self.context, ms['host'], ms['binary']) |
||||
self.assertEqual(ms.id, res.id) |
||||
|
||||
def test_get_magnum_service_by_host_and_binary_failure(self): |
||||
utils.create_test_magnum_service() |
||||
res = self.dbapi.get_magnum_service_by_host_and_binary( |
||||
self.context, 'fakehost1', 'fake-bin1') |
||||
self.assertEqual(res, None) |
||||
|
||||
def test_update_magnum_service(self): |
||||
ms = utils.create_test_magnum_service() |
||||
d2 = True |
||||
update = {'disabled': d2} |
||||
ms1 = self.dbapi.update_magnum_service(ms['id'], update) |
||||
self.assertEqual(ms['id'], ms1['id']) |
||||
self.assertEqual(ms1['disabled'], d2) |
||||
res = self.dbapi.get_magnum_service_by_host_and_binary( |
||||
self.context, 'fakehost', 'fake-bin') |
||||
self.assertEqual(res['id'], ms1['id']) |
||||
self.assertEqual(res['disabled'], d2) |
||||
|
||||
def test_update_magnum_service_failure(self): |
||||
ms = utils.create_test_magnum_service() |
||||
fake_update = {'fake_field': 'fake_value'} |
||||
self.assertRaises(exception.MagnumServiceNotFound, |
||||
self.dbapi.update_magnum_service, |
||||
ms['id'] + 1, fake_update) |
||||
|
||||
def test_destroy_magnum_service(self): |
||||
ms = utils.create_test_magnum_service() |
||||
res = self.dbapi.get_magnum_service_by_host_and_binary( |
||||
self.context, 'fakehost', 'fake-bin') |
||||
self.assertEqual(res['id'], ms['id']) |
||||
self.dbapi.destroy_magnum_service(ms['id']) |
||||
res = self.dbapi.get_magnum_service_by_host_and_binary( |
||||
self.context, 'fakehost', 'fake-bin') |
||||
self.assertEqual(res, None) |
||||
|
||||
def test_destroy_magnum_service_failure(self): |
||||
ms = utils.create_test_magnum_service() |
||||
self.assertRaises(exception.MagnumServiceNotFound, |
||||
self.dbapi.destroy_magnum_service, |
||||
ms['id'] + 1) |
||||
|
||||
def test_get_magnum_service_list(self): |
||||
fake_ms_params = { |
||||
'report_count': 1010, |
||||
'host': 'FakeHost', |
||||
'binary': 'FakeBin', |
||||
'disabled': False, |
||||
'disabled_reason': 'FakeReason' |
||||
} |
||||
utils.create_test_magnum_service(**fake_ms_params) |
||||
res = self.dbapi.get_magnum_service_list(self.context) |
||||
self.assertEqual(1, len(res)) |
||||
res = res[0] |
||||
for k, v in fake_ms_params.iteritems(): |
||||
self.assertEqual(res[k], v) |
||||
|
||||
fake_ms_params['binary'] = 'FakeBin1' |
||||
fake_ms_params['disabled'] = True |
||||
utils.create_test_magnum_service(**fake_ms_params) |
||||
res = self.dbapi.get_magnum_service_list(self.context, disabled=True) |
||||
self.assertEqual(1, len(res)) |
||||
res = res[0] |
||||
for k, v in fake_ms_params.iteritems(): |
||||
self.assertEqual(res[k], v) |
@ -0,0 +1,100 @@
|
||||
# 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 magnum import objects |
||||
from magnum.tests.unit.db import base |
||||
from magnum.tests.unit.db import utils |
||||
|
||||
|
||||
class TestMagnumServiceObject(base.DbTestCase): |
||||
|
||||
def setUp(self): |
||||
super(TestMagnumServiceObject, self).setUp() |
||||
self.fake_magnum_service = utils.get_test_magnum_service() |
||||
|
||||
def test_get_by_host_and_binary(self): |
||||
with mock.patch.object(self.dbapi, |
||||
'get_magnum_service_by_host_and_binary', |
||||
autospec=True) as mock_get_magnum_service: |
||||
mock_get_magnum_service.return_value = self.fake_magnum_service |
||||
ms = objects.MagnumService.get_by_host_and_binary(self.context, |
||||
'fake-host', |
||||
'fake-bin') |
||||
mock_get_magnum_service.assert_called_once_with(self.context, |
||||
'fake-host', |
||||
'fake-bin') |
||||
self.assertEqual(self.context, ms._context) |
||||
|
||||
def test_create(self): |
||||
with mock.patch.object(self.dbapi, 'create_magnum_service', |
||||
autospec=True) as mock_create_magnum_service: |
||||
mock_create_magnum_service.return_value = self.fake_magnum_service |
||||
ms_dict = {'host': 'fakehost', 'binary': 'fake-bin'} |
||||
ms = objects.MagnumService(self.context, **ms_dict) |
||||
ms.create(self.context) |
||||
mock_create_magnum_service.assert_called_once_with(ms_dict) |
||||
|
||||
def test_destroy(self): |
||||
with mock.patch.object(self.dbapi, |
||||
'get_magnum_service_by_host_and_binary', |
||||
autospec=True) as mock_get_magnum_service: |
||||
mock_get_magnum_service.return_value = self.fake_magnum_service |
||||
with mock.patch.object(self.dbapi, |
||||
'destroy_magnum_service', |
||||
autospec=True) as mock_destroy_ms: |
||||
ms = objects.MagnumService.get_by_host_and_binary( |
||||
self.context, 'fake-host', 'fake-bin') |
||||
ms.destroy() |
||||
mock_get_magnum_service.assert_called_once_with( |
||||
self.context, 'fake-host', 'fake-bin') |
||||
mock_destroy_ms.assert_called_once_with( |
||||
self.fake_magnum_service['id']) |
||||
self.assertEqual(self.context, ms._context) |
||||
|
||||
def test_save(self): |
||||
with mock.patch.object(self.dbapi, |
||||
'get_magnum_service_by_host_and_binary', |
||||
autospec=True) as mock_get_magnum_service: |
||||
mock_get_magnum_service.return_value = self.fake_magnum_service |
||||
with mock.patch.object(self.dbapi, |
||||
'update_magnum_service', |
||||
autospec=True) as mock_update_ms: |
||||
ms = objects.MagnumService.get_by_host_and_binary( |
||||
self.context, 'fake-host', 'fake-bin') |
||||
ms.disabled = True |
||||
ms.save() |
||||
mock_get_magnum_service.assert_called_once_with( |
||||
self.context, 'fake-host', 'fake-bin') |
||||
mock_update_ms.assert_called_once_with( |
||||
self.fake_magnum_service['id'], {'disabled': True}) |
||||
self.assertEqual(self.context, ms._context) |
||||
|
||||
def test_report_state_up(self): |
||||
with mock.patch.object(self.dbapi, |
||||
'get_magnum_service_by_host_and_binary', |
||||
autospec=True) as mock_get_magnum_service: |
||||
mock_get_magnum_service.return_value = self.fake_magnum_service |
||||
with mock.patch.object(self.dbapi, |
||||
'update_magnum_service', |
||||
autospec=True) as mock_update_ms: |
||||
ms = objects.MagnumService.get_by_host_and_binary( |
||||
self.context, 'fake-host', 'fake-bin') |
||||
last_report_count = self.fake_magnum_service['report_count'] |
||||
ms.report_state_up() |
||||
mock_get_magnum_service.assert_called_once_with( |
||||
self.context, 'fake-host', 'fake-bin') |
||||
self.assertEqual(self.context, ms._context) |
||||
mock_update_ms.assert_called_once_with( |
||||
self.fake_magnum_service['id'], |
||||
{'report_count': last_report_count + 1}) |
Loading…
Reference in new issue