From df66e4cf5853453845768cfdf3c278d894d382e2 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sat, 10 Feb 2018 22:46:23 +0000 Subject: [PATCH] Add availability_zone to service Add a new attribute 'availability_zone' to the 'service' resource. This attribute indicates the availability zone of the zun-compute agent. Cloud administrators can customize the availability zone of each compute node by setting the following in the config file: [DEFAULT] default_availability_zone = my-zone If this is not set, the default availability zone is 'nova' (we use this value for consistency with neutron and cinder's defaults). In the future, we will add support for scheduling containers to a specified AZ and the 'availability_zone' attribute will be used by scheduler to check if the availability zone of the compute node equals to the one requested by API users. Change-Id: I4a4206b4eb0aa5149bbfc8ab72ae408a08317de4 Partial-Implements: blueprint zun-availability-zone --- zun/api/controllers/v1/zun_services.py | 12 +++++-- zun/conf/__init__.py | 2 ++ zun/conf/availability_zone.py | 36 +++++++++++++++++++ ...520409_add_availability_zone_to_service.py | 34 ++++++++++++++++++ zun/db/sqlalchemy/models.py | 1 + zun/objects/zun_service.py | 4 ++- .../api/controllers/v1/test_zun_service.py | 22 ++++++++++-- zun/tests/unit/api/utils.py | 1 + zun/tests/unit/db/utils.py | 1 + zun/tests/unit/objects/test_objects.py | 2 +- 10 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 zun/conf/availability_zone.py create mode 100644 zun/db/sqlalchemy/alembic/versions/3f49fa520409_add_availability_zone_to_service.py diff --git a/zun/api/controllers/v1/zun_services.py b/zun/api/controllers/v1/zun_services.py index ee9136c17..b4a31f0f3 100644 --- a/zun/api/controllers/v1/zun_services.py +++ b/zun/api/controllers/v1/zun_services.py @@ -21,9 +21,13 @@ from zun.api import servicegroup as svcgrp_api from zun.common import exception from zun.common import policy from zun.common import validation +import zun.conf from zun import objects +CONF = zun.conf.CONF + + class ZunServiceCollection(collection.Collection): fields = { @@ -41,11 +45,13 @@ class ZunServiceCollection(collection.Collection): collection = ZunServiceCollection() collection.services = [] for p in rpc_hsvcs: - hsvc = p.as_dict() + service = p.as_dict() alive = servicegroup_api.service_is_up(p) state = 'up' if alive else 'down' - hsvc['state'] = state - collection.services.append(hsvc) + service['state'] = state + collection.services.append(service) + if not service['availability_zone']: + service['availability_zone'] = CONF.default_availability_zone next = collection.get_next(limit=None, url=None, **kwargs) if next is not None: collection.next = next diff --git a/zun/conf/__init__.py b/zun/conf/__init__.py index 43a83c6df..d6f4fc89c 100644 --- a/zun/conf/__init__.py +++ b/zun/conf/__init__.py @@ -15,6 +15,7 @@ from oslo_config import cfg from zun.conf import api +from zun.conf import availability_zone from zun.conf import cinder_client from zun.conf import compute from zun.conf import container_driver @@ -61,3 +62,4 @@ pci.register_opts(CONF) volume.register_opts(CONF) cinder_client.register_opts(CONF) netconf.register_opts(CONF) +availability_zone.register_opts(CONF) diff --git a/zun/conf/availability_zone.py b/zun/conf/availability_zone.py new file mode 100644 index 000000000..614aa55b2 --- /dev/null +++ b/zun/conf/availability_zone.py @@ -0,0 +1,36 @@ +# 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 + +availability_zone_opts = [ + cfg.StrOpt('default_availability_zone', + default='nova', + help=""" +Default availability zone for compute services. + +This option determines the default availability zone for 'zun-compute' +services. + +Possible values: + +* Any string representing an existing availability zone name. +"""), +] + + +def register_opts(conf): + conf.register_opts(availability_zone_opts) + + +def list_opts(): + return {'DEFAULT': availability_zone_opts} diff --git a/zun/db/sqlalchemy/alembic/versions/3f49fa520409_add_availability_zone_to_service.py b/zun/db/sqlalchemy/alembic/versions/3f49fa520409_add_availability_zone_to_service.py new file mode 100644 index 000000000..96e29b723 --- /dev/null +++ b/zun/db/sqlalchemy/alembic/versions/3f49fa520409_add_availability_zone_to_service.py @@ -0,0 +1,34 @@ +# 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 availability_zone to service + +Revision ID: 3f49fa520409 +Revises: 50829990c965 +Create Date: 2018-02-10 22:33:22.890723 + +""" + +# revision identifiers, used by Alembic. +revision = '3f49fa520409' +down_revision = '50829990c965' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('zun_service', + sa.Column('availability_zone', sa.String(255), + nullable=True)) diff --git a/zun/db/sqlalchemy/models.py b/zun/db/sqlalchemy/models.py index d63d7abb9..21c96bd80 100644 --- a/zun/db/sqlalchemy/models.py +++ b/zun/db/sqlalchemy/models.py @@ -122,6 +122,7 @@ class ZunService(Base): last_seen_up = Column(DateTime, nullable=True) forced_down = Column(Boolean, default=False) report_count = Column(Integer, nullable=False, default=0) + availability_zone = Column(String(255), nullable=True) class Container(Base): diff --git a/zun/objects/zun_service.py b/zun/objects/zun_service.py index 3c76df190..c73b0a376 100644 --- a/zun/objects/zun_service.py +++ b/zun/objects/zun_service.py @@ -21,7 +21,8 @@ class ZunService(base.ZunPersistentObject, base.ZunObject): # Version 1.0: Initial version # Version 1.1: Add update method - VERSION = '1.1' + # Version 1.2: Add availability_zone field + VERSION = '1.2' fields = { 'id': fields.IntegerField(), @@ -32,6 +33,7 @@ class ZunService(base.ZunPersistentObject, base.ZunObject): 'last_seen_up': fields.DateTimeField(nullable=True), 'forced_down': fields.BooleanField(), 'report_count': fields.IntegerField(), + 'availability_zone': fields.StringField(nullable=True), } @staticmethod diff --git a/zun/tests/unit/api/controllers/v1/test_zun_service.py b/zun/tests/unit/api/controllers/v1/test_zun_service.py index 5b1a507fd..1e9b9daa5 100644 --- a/zun/tests/unit/api/controllers/v1/test_zun_service.py +++ b/zun/tests/unit/api/controllers/v1/test_zun_service.py @@ -11,6 +11,7 @@ # limitations under the License. import mock +from oslo_config import cfg from zun.api import servicegroup from zun import objects @@ -35,10 +36,10 @@ class TestZunServiceController(api_base.FunctionalTest): response = self.get_json('/services') self.assertEqual([], response['services']) - def _rpc_api_reply(self, count=1): + def _rpc_api_reply(self, count=1, **kwarg): reclist = [] for i in range(count): - elem = api_utils.zservice_get_data() + elem = api_utils.zservice_get_data(**kwarg) elem['id'] = i + 1 rec = DbRec(elem) reclist.append(rec) @@ -55,6 +56,8 @@ class TestZunServiceController(api_base.FunctionalTest): response = self.get_json('/services') self.assertEqual(1, len(response['services'])) self.assertEqual(1, response['services'][0]['id']) + self.assertEqual('fake-zone', + response['services'][0]['availability_zone']) @mock.patch('zun.common.policy.enforce') @mock.patch.object(objects.ZunService, 'list') @@ -71,6 +74,21 @@ class TestZunServiceController(api_base.FunctionalTest): elem = response['services'][i] self.assertEqual(elem['id'], i + 1) + @mock.patch('zun.common.policy.enforce') + @mock.patch.object(objects.ZunService, 'list') + @mock.patch.object(servicegroup.ServiceGroup, 'service_is_up') + def test_default_availability_zone(self, svc_up, mock_list, mock_policy): + cfg.CONF.set_override("default_availability_zone", "default-zone") + mock_policy.return_value = True + mock_list.return_value = self._rpc_api_reply(availability_zone=None) + svc_up.return_value = "up" + + response = self.get_json('/services') + self.assertEqual(1, len(response['services'])) + self.assertEqual(1, response['services'][0]['id']) + self.assertEqual('default-zone', + response['services'][0]['availability_zone']) + @mock.patch('zun.common.policy.enforce') @mock.patch.object(objects.ZunService, 'get_by_host_and_binary') @mock.patch.object(objects.ZunService, 'update') diff --git a/zun/tests/unit/api/utils.py b/zun/tests/unit/api/utils.py index 8393c7b8d..44856c794 100644 --- a/zun/tests/unit/api/utils.py +++ b/zun/tests/unit/api/utils.py @@ -31,4 +31,5 @@ def zservice_get_data(**kwargs): 'last_seen_up': kwargs.get('last_seen_up', faketime), 'created_at': kwargs.get('created_at', faketime), 'updated_at': kwargs.get('updated_at', faketime), + 'availability_zone': kwargs.get('availability_zone', 'fake-zone'), } diff --git a/zun/tests/unit/db/utils.py b/zun/tests/unit/db/utils.py index a9949f29e..6c0549f5e 100644 --- a/zun/tests/unit/db/utils.py +++ b/zun/tests/unit/db/utils.py @@ -198,6 +198,7 @@ def get_test_zun_service(**kwargs): 'report_count': kwargs.get('report_count', 13), 'created_at': kwargs.get('created_at'), 'updated_at': kwargs.get('updated_at'), + 'availability_zone': kwargs.get('availability_zone', 'fake-zone'), } diff --git a/zun/tests/unit/objects/test_objects.py b/zun/tests/unit/objects/test_objects.py index dd15a4da6..57bc948b4 100644 --- a/zun/tests/unit/objects/test_objects.py +++ b/zun/tests/unit/objects/test_objects.py @@ -352,7 +352,7 @@ object_data = { 'NUMATopology': '1.0-b54086eda7e4b2e6145ecb6ee2c925ab', 'ResourceClass': '1.1-d661c7675b3cd5b8c3618b68ba64324e', 'ResourceProvider': '1.0-92b427359d5a4cf9ec6c72cbe630ee24', - 'ZunService': '1.1-b1549134bfd5271daec417ca8cabc77e', + 'ZunService': '1.2-deff2a74a9ce23baa231ae12f39a6189', 'Capsule': '1.5-cbdaffa78fa68c26cf4a61d8f75dd32d', 'PciDevice': '1.1-6e3f0851ad1cf12583e6af4df1883979', 'ComputeNode': '1.9-e8536102d3b28cb3378e9e26f508cd72',