diff --git a/setup.cfg b/setup.cfg index 5e6303e1..2471ce2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -94,6 +94,8 @@ openstack.messaging.v2 = subscription_show = zaqarclient.queues.v2.cli:ShowSubscription subscription_list = zaqarclient.queues.v2.cli:ListSubscriptions queue_signed_url = zaqarclient.queues.v2.cli:CreateSignedUrl + messaging_ping = zaqarclient.queues.v2.cli:Ping + messaging_health = zaqarclient.queues.v2.cli:Health openstack.cli.extension = messaging = zaqarclient.queues.cli diff --git a/tests/functional/queues/v2/test_health.py b/tests/functional/queues/v2/test_health.py new file mode 100644 index 00000000..e0be83ca --- /dev/null +++ b/tests/functional/queues/v2/test_health.py @@ -0,0 +1,27 @@ +# Copyright (c) 2016 Catalyst IT Ltd. +# +# 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 zaqarclient.tests.queues import health +from zaqarclient.transport import http + + +class QueuesV2HealthHttpFunctionalTest( + health.QueuesV2HealthFunctionalTest): + + is_functional = True + transport_cls = http.HttpTransport + url = 'http://127.0.0.1:8888' + version = 2 diff --git a/tests/unit/queues/v2/test_client.py b/tests/unit/queues/v2/test_client.py index 9335a8f9..2c597496 100644 --- a/tests/unit/queues/v2/test_client.py +++ b/tests/unit/queues/v2/test_client.py @@ -18,15 +18,18 @@ import mock import ddt from zaqarclient.queues import client -from zaqarclient.queues.v1 import core -from zaqarclient.tests import base +from zaqarclient.tests.queues import base from zaqarclient.transport import errors +from zaqarclient.transport import http VERSIONS = [2] @ddt.ddt -class TestClient(base.TestBase): +class TestClient(base.QueuesTestBase): + transport_cls = http.HttpTransport + url = 'http://127.0.0.1:8888/v2' + version = VERSIONS[0] @ddt.data(*VERSIONS) def test_transport(self, version): @@ -35,21 +38,18 @@ class TestClient(base.TestBase): self.assertIsNotNone(cli.transport()) @ddt.data(*VERSIONS) - def test_health_ok(self, version): - cli = client.Client('http://example.com', - version, {"auth_opts": {'backend': 'noauth'}}) - with mock.patch.object(core, 'health', autospec=True) as core_health: - core_health.return_value = None - self.assertTrue(cli.health()) + def test_ping_ok(self, version): + with mock.patch.object(self.transport, 'send', + autospec=True) as send_method: + send_method.return_value = None + self.assertTrue(self.client.ping()) @ddt.data(*VERSIONS) - def test_health_bad(self, version): - cli = client.Client('http://example.com', - version, {"auth_opts": {'backend': 'noauth'}}) - + def test_ping_bad(self, version): def raise_error(*args, **kwargs): raise errors.ServiceUnavailableError() - with mock.patch.object(core, 'health', autospec=True) as core_health: - core_health.side_effect = raise_error - self.assertFalse(cli.health()) + with mock.patch.object(self.transport, 'send', + autospec=True) as send_method: + send_method.side_effect = raise_error + self.assertFalse(self.client.ping()) diff --git a/tests/unit/queues/v2/test_health.py b/tests/unit/queues/v2/test_health.py new file mode 100644 index 00000000..5853ce79 --- /dev/null +++ b/tests/unit/queues/v2/test_health.py @@ -0,0 +1,24 @@ +# Copyright (c) 2016 Catalyst IT Ltd. +# +# 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 zaqarclient.tests.queues import health +from zaqarclient.transport import http + + +class QueuesV2HealthHttpUnitTest(health.QueuesV2HealthUnitTest): + + transport_cls = http.HttpTransport + url = 'http://127.0.0.1:8888/v2' + version = 2 diff --git a/zaqarclient/queues/v2/api.py b/zaqarclient/queues/v2/api.py index 7adff1c7..53de3f77 100644 --- a/zaqarclient/queues/v2/api.py +++ b/zaqarclient/queues/v2/api.py @@ -74,4 +74,14 @@ V2.schema.update({ 'detailed': {'type': 'boolean'} } }, + + 'ping': { + 'ref': 'ping', + 'method': 'GET', + }, + + 'health': { + 'ref': 'health', + 'method': 'GET', + }, }) diff --git a/zaqarclient/queues/v2/cli.py b/zaqarclient/queues/v2/cli.py index 25974a86..0e80b9c4 100644 --- a/zaqarclient/queues/v2/cli.py +++ b/zaqarclient/queues/v2/cli.py @@ -393,3 +393,26 @@ class CreateSignedUrl(show.ShowOne): data['signature'], data['project'] ) + + +class Ping(show.ShowOne): + """Check if Zaqar server is alive or not""" + + log = logging.getLogger(__name__ + ".Ping") + + def take_action(self, parsed_args): + client = _get_client(self, parsed_args) + columns = ('Pingable', ) + return columns, utils.get_dict_properties({'pingable': client.ping()}, + columns) + + +class Health(command.Command): + """Display detailed health status of Zaqar server""" + + log = logging.getLogger(__name__ + ".Health") + + def take_action(self, parsed_args): + client = _get_client(self, parsed_args) + health = client.health() + print(json.dumps(health, indent=4, sort_keys=True)) diff --git a/zaqarclient/queues/v2/client.py b/zaqarclient/queues/v2/client.py index 308322cc..14eebfba 100644 --- a/zaqarclient/queues/v2/client.py +++ b/zaqarclient/queues/v2/client.py @@ -92,3 +92,14 @@ class Client(client.Client): subscription_list, 'subscriptions', subscription.create_object(self)) + + def ping(self): + """Gets the health status of Zaqar server.""" + req, trans = self._request_and_transport() + return core.ping(trans, req) + + @decorators.version(min_version=1.1) + def health(self): + """Gets the detailed health status of Zaqar server.""" + req, trans = self._request_and_transport() + return core.health(trans, req) diff --git a/zaqarclient/queues/v2/core.py b/zaqarclient/queues/v2/core.py index c491d07c..e8e82931 100644 --- a/zaqarclient/queues/v2/core.py +++ b/zaqarclient/queues/v2/core.py @@ -220,3 +220,42 @@ def subscription_list(transport, request, queue_name, **kwargs): return {'links': [], 'subscriptions': []} return resp.deserialized_content + + +def ping(transport, request, callback=None): + """Check the health of web head for load balancing + + :param transport: Transport instance to use + :type transport: `transport.base.Transport` + :param request: Request instance ready to be sent. + :type request: `transport.request.Request` + :param callback: Optional callable to use as callback. + If specified, this request will be sent asynchronously. + (IGNORED UNTIL ASYNC SUPPORT IS COMPLETE) + :type callback: Callable object. + """ + + request.operation = 'ping' + try: + transport.send(request) + return True + except Exception: + return False + + +def health(transport, request, callback=None): + """Get detailed health status of Zaqar server + + :param transport: Transport instance to use + :type transport: `transport.base.Transport` + :param request: Request instance ready to be sent. + :type request: `transport.request.Request` + :param callback: Optional callable to use as callback. + If specified, this request will be sent asynchronously. + (IGNORED UNTIL ASYNC SUPPORT IS COMPLETE) + :type callback: Callable object. + """ + + request.operation = 'health' + resp = transport.send(request) + return resp.deserialized_content diff --git a/zaqarclient/tests/queues/health.py b/zaqarclient/tests/queues/health.py new file mode 100644 index 00000000..9d6dd38c --- /dev/null +++ b/zaqarclient/tests/queues/health.py @@ -0,0 +1,51 @@ +# Copyright (c) 2016 Catalyst IT Ltd. +# +# 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 json +import mock + +from zaqarclient.tests.queues import base +from zaqarclient.transport import response + + +class QueuesV2HealthUnitTest(base.QueuesTestBase): + + def test_health(self): + expect_health = {u'catalog_reachable': True, + u'redis': {u'operation_status': {}, + u'storage_reachable': True} + } + + with mock.patch.object(self.transport, 'send', + autospec=True) as send_method: + + health_content = json.dumps(expect_health) + health_resp = response.Response(None, health_content) + send_method.side_effect = iter([health_resp]) + + health = self.client.health() + self.assertEqual(expect_health, health) + + +class QueuesV2HealthFunctionalTest(base.QueuesTestBase): + def test_ping(self): + # NOTE(flwang): If test env is not pingable, then the test should fail + self.assertTrue(self.client.ping()) + + def test_health(self): + health = self.client.health() + # NOTE(flwang): If everything is ok, then zaqar server will return a + # JSON(dict). + self.assertTrue(isinstance(health, dict))