From 627b616227badd893ff2d8d7addf162d605b2299 Mon Sep 17 00:00:00 2001 From: Qiu Yu Date: Sun, 14 Jul 2013 23:18:22 +0800 Subject: [PATCH] Add os-services extension support Implement client bindings for Cinder os-services API extension, so client would be able to list services, enable or disable particular services. Usage: cinder service-list [--host ] [--binary ] cinder service-enable cinder service-disable This change is depended on following change at Cinder side I7f3fa889294ca6caebdf46b8689345bcac1cdf54 Implements blueprint os-services-extension Change-Id: I4a53fd545ed3b446441302d00a429168a996a34a --- cinderclient/tests/v1/fakes.py | 48 ++++++++++++++++++++ cinderclient/tests/v1/test_services.py | 62 ++++++++++++++++++++++++++ cinderclient/tests/v2/fakes.py | 48 ++++++++++++++++++++ cinderclient/tests/v2/test_services.py | 62 ++++++++++++++++++++++++++ cinderclient/v1/client.py | 2 + cinderclient/v1/services.py | 56 +++++++++++++++++++++++ cinderclient/v1/shell.py | 28 ++++++++++++ cinderclient/v2/client.py | 2 + cinderclient/v2/services.py | 56 +++++++++++++++++++++++ cinderclient/v2/shell.py | 28 ++++++++++++ 10 files changed, 392 insertions(+) create mode 100644 cinderclient/tests/v1/test_services.py create mode 100644 cinderclient/tests/v2/test_services.py create mode 100644 cinderclient/v1/services.py create mode 100644 cinderclient/v2/services.py diff --git a/cinderclient/tests/v1/fakes.py b/cinderclient/tests/v1/fakes.py index 56f086b0e..73ad1a91b 100644 --- a/cinderclient/tests/v1/fakes.py +++ b/cinderclient/tests/v1/fakes.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import datetime + try: import urlparse except ImportError: @@ -510,3 +512,49 @@ class FakeHTTPClient(base_client.HTTPClient): transfer1 = '5678' return (200, {}, {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) + + # + # Services + # + def get_os_services(self, **kw): + host = kw.get('host', None) + binary = kw.get('binary', None) + services = [ + { + 'binary': 'cinder-volume', + 'host': 'host1', + 'zone': 'cinder', + 'status': 'enabled', + 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 2) + }, + { + 'binary': 'cinder-volume', + 'host': 'host2', + 'zone': 'cinder', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime(2012, 9, 18, 8, 3, 38) + }, + { + 'binary': 'cinder-scheduler', + 'host': 'host2', + 'zone': 'cinder', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime(2012, 9, 18, 8, 3, 38) + }, + ] + if host: + services = filter(lambda i: i['host'] == host, services) + if binary: + services = filter(lambda i: i['binary'] == binary, services) + return (200, {}, {'services': services}) + + def put_os_services_enable(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'disabled'}) + + def put_os_services_disable(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'enabled'}) diff --git a/cinderclient/tests/v1/test_services.py b/cinderclient/tests/v1/test_services.py new file mode 100644 index 000000000..2320a2656 --- /dev/null +++ b/cinderclient/tests/v1/test_services.py @@ -0,0 +1,62 @@ +# Copyright 2013 OpenStack LLC. +# 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. + +from cinderclient.tests import utils +from cinderclient.tests.v1 import fakes +from cinderclient.v1 import services + + +cs = fakes.FakeClient() + + +class ServicesTest(utils.TestCase): + + def test_list_services(self): + svs = cs.services.list() + cs.assert_called('GET', '/os-services') + self.assertEqual(len(svs), 3) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + + def test_list_services_with_hostname(self): + svs = cs.services.list(host='host2') + cs.assert_called('GET', '/os-services?host=host2') + self.assertEqual(len(svs), 2) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + [self.assertEqual(s.host, 'host2') for s in svs] + + def test_list_services_with_binary(self): + svs = cs.services.list(binary='cinder-volume') + cs.assert_called('GET', '/os-services?binary=cinder-volume') + self.assertEqual(len(svs), 2) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + [self.assertEqual(s.binary, 'cinder-volume') for s in svs] + + def test_list_services_with_host_binary(self): + svs = cs.services.list('host2', 'cinder-volume') + cs.assert_called('GET', '/os-services?host=host2&binary=cinder-volume') + self.assertEqual(len(svs), 1) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + [self.assertEqual(s.host, 'host2') for s in svs] + [self.assertEqual(s.binary, 'cinder-volume') for s in svs] + + def test_services_enable(self): + cs.services.enable('host1', 'cinder-volume') + values = {"host": "host1", 'binary': 'cinder-volume'} + cs.assert_called('PUT', '/os-services/enable', values) + + def test_services_disable(self): + cs.services.disable('host1', 'cinder-volume') + values = {"host": "host1", 'binary': 'cinder-volume'} + cs.assert_called('PUT', '/os-services/disable', values) diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py index e46b82239..a1400821a 100644 --- a/cinderclient/tests/v2/fakes.py +++ b/cinderclient/tests/v2/fakes.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import datetime + try: import urlparse except ImportError: @@ -517,3 +519,49 @@ class FakeHTTPClient(base_client.HTTPClient): transfer1 = '5678' return (200, {}, {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) + + # + # Services + # + def get_os_services(self, **kw): + host = kw.get('host', None) + binary = kw.get('binary', None) + services = [ + { + 'binary': 'cinder-volume', + 'host': 'host1', + 'zone': 'cinder', + 'status': 'enabled', + 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 2) + }, + { + 'binary': 'cinder-volume', + 'host': 'host2', + 'zone': 'cinder', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime(2012, 9, 18, 8, 3, 38) + }, + { + 'binary': 'cinder-scheduler', + 'host': 'host2', + 'zone': 'cinder', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime(2012, 9, 18, 8, 3, 38) + }, + ] + if host: + services = filter(lambda i: i['host'] == host, services) + if binary: + services = filter(lambda i: i['binary'] == binary, services) + return (200, {}, {'services': services}) + + def put_os_services_enable(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'disabled'}) + + def put_os_services_disable(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'enabled'}) diff --git a/cinderclient/tests/v2/test_services.py b/cinderclient/tests/v2/test_services.py new file mode 100644 index 000000000..e4bce290e --- /dev/null +++ b/cinderclient/tests/v2/test_services.py @@ -0,0 +1,62 @@ +# Copyright 2013 OpenStack LLC. +# 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. + +from cinderclient.tests import utils +from cinderclient.tests.v2 import fakes +from cinderclient.v2 import services + + +cs = fakes.FakeClient() + + +class ServicesTest(utils.TestCase): + + def test_list_services(self): + svs = cs.services.list() + cs.assert_called('GET', '/os-services') + self.assertEqual(len(svs), 3) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + + def test_list_services_with_hostname(self): + svs = cs.services.list(host='host2') + cs.assert_called('GET', '/os-services?host=host2') + self.assertEqual(len(svs), 2) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + [self.assertEqual(s.host, 'host2') for s in svs] + + def test_list_services_with_binary(self): + svs = cs.services.list(binary='cinder-volume') + cs.assert_called('GET', '/os-services?binary=cinder-volume') + self.assertEqual(len(svs), 2) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + [self.assertEqual(s.binary, 'cinder-volume') for s in svs] + + def test_list_services_with_host_binary(self): + svs = cs.services.list('host2', 'cinder-volume') + cs.assert_called('GET', '/os-services?host=host2&binary=cinder-volume') + self.assertEqual(len(svs), 1) + [self.assertTrue(isinstance(s, services.Service)) for s in svs] + [self.assertEqual(s.host, 'host2') for s in svs] + [self.assertEqual(s.binary, 'cinder-volume') for s in svs] + + def test_services_enable(self): + cs.services.enable('host1', 'cinder-volume') + values = {"host": "host1", 'binary': 'cinder-volume'} + cs.assert_called('PUT', '/os-services/enable', values) + + def test_services_disable(self): + cs.services.disable('host1', 'cinder-volume') + values = {"host": "host1", 'binary': 'cinder-volume'} + cs.assert_called('PUT', '/os-services/disable', values) diff --git a/cinderclient/v1/client.py b/cinderclient/v1/client.py index 4f8b92d4a..7dd84e128 100644 --- a/cinderclient/v1/client.py +++ b/cinderclient/v1/client.py @@ -17,6 +17,7 @@ from cinderclient import client from cinderclient.v1 import limits from cinderclient.v1 import quota_classes from cinderclient.v1 import quotas +from cinderclient.v1 import services from cinderclient.v1 import volumes from cinderclient.v1 import volume_snapshots from cinderclient.v1 import volume_types @@ -62,6 +63,7 @@ class Client(object): self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) self.transfers = volume_transfers.VolumeTransferManager(self) + self.services = services.ServiceManager(self) # Add in any extensions... if extensions: diff --git a/cinderclient/v1/services.py b/cinderclient/v1/services.py new file mode 100644 index 000000000..b2427dde5 --- /dev/null +++ b/cinderclient/v1/services.py @@ -0,0 +1,56 @@ +# Copyright 2013 OpenStack LLC. +# 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. + +""" +service interface +""" +from cinderclient import base + + +class Service(base.Resource): + + def __repr__(self): + return "" % self.service + + +class ServiceManager(base.ManagerWithFind): + resource_class = Service + + def list(self, host=None, binary=None): + """ + Describes service list for host. + + :param host: destination host name. + :param binary: service binary. + """ + url = "/os-services" + filters = [] + if host: + filters.append("host=%s" % host) + if binary: + filters.append("binary=%s" % binary) + if filters: + url = "%s?%s" % (url, "&".join(filters)) + return self._list(url, "services") + + def enable(self, host, binary): + """Enable the service specified by hostname and binary.""" + body = {"host": host, "binary": binary} + self._update("/os-services/enable", body) + + def disable(self, host, binary): + """Enable the service specified by hostname and binary.""" + body = {"host": host, "binary": binary} + self._update("/os-services/disable", body) diff --git a/cinderclient/v1/shell.py b/cinderclient/v1/shell.py index eeb30ba11..af16ed043 100644 --- a/cinderclient/v1/shell.py +++ b/cinderclient/v1/shell.py @@ -831,3 +831,31 @@ def do_extend(cs, args): """Attempt to extend the size of an existing volume.""" volume = _find_volume(cs, args.volume) cs.volumes.extend(volume, args.new_size) + + +@utils.arg('--host', metavar='', default=None, + help='Name of host.') +@utils.arg('--binary', metavar='', default=None, + help='Service binary.') +@utils.service_type('volume') +def do_service_list(cs, args): + """List all the services. Filter by host & service binary.""" + result = cs.services.list(host=args.host, binary=args.binary) + columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"] + utils.print_list(result, columns) + + +@utils.arg('host', metavar='', help='Name of host.') +@utils.arg('binary', metavar='', help='Service binary.') +@utils.service_type('volume') +def do_service_enable(cs, args): + """Enable the service.""" + cs.services.enable(args.host, args.binary) + + +@utils.arg('host', metavar='', help='Name of host.') +@utils.arg('binary', metavar='', help='Service binary.') +@utils.service_type('volume') +def do_service_disable(cs, args): + """Disable the service.""" + cs.services.disable(args.host, args.binary) diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py index 261f848f6..003ecae98 100644 --- a/cinderclient/v2/client.py +++ b/cinderclient/v2/client.py @@ -17,6 +17,7 @@ from cinderclient import client from cinderclient.v2 import limits from cinderclient.v2 import quota_classes from cinderclient.v2 import quotas +from cinderclient.v2 import services from cinderclient.v2 import volumes from cinderclient.v2 import volume_snapshots from cinderclient.v2 import volume_types @@ -60,6 +61,7 @@ class Client(object): self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) self.transfers = volume_transfers.VolumeTransferManager(self) + self.services = services.ServiceManager(self) # Add in any extensions... if extensions: diff --git a/cinderclient/v2/services.py b/cinderclient/v2/services.py new file mode 100644 index 000000000..b2427dde5 --- /dev/null +++ b/cinderclient/v2/services.py @@ -0,0 +1,56 @@ +# Copyright 2013 OpenStack LLC. +# 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. + +""" +service interface +""" +from cinderclient import base + + +class Service(base.Resource): + + def __repr__(self): + return "" % self.service + + +class ServiceManager(base.ManagerWithFind): + resource_class = Service + + def list(self, host=None, binary=None): + """ + Describes service list for host. + + :param host: destination host name. + :param binary: service binary. + """ + url = "/os-services" + filters = [] + if host: + filters.append("host=%s" % host) + if binary: + filters.append("binary=%s" % binary) + if filters: + url = "%s?%s" % (url, "&".join(filters)) + return self._list(url, "services") + + def enable(self, host, binary): + """Enable the service specified by hostname and binary.""" + body = {"host": host, "binary": binary} + self._update("/os-services/enable", body) + + def disable(self, host, binary): + """Enable the service specified by hostname and binary.""" + body = {"host": host, "binary": binary} + self._update("/os-services/disable", body) diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index 98883d6ba..7f57358e5 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -916,3 +916,31 @@ def do_extend(cs, args): """Attempt to extend the size of an existing volume.""" volume = _find_volume(cs, args.volume) cs.volumes.extend(volume, args.new_size) + + +@utils.arg('--host', metavar='', default=None, + help='Name of host.') +@utils.arg('--binary', metavar='', default=None, + help='Service binary.') +@utils.service_type('volume') +def do_service_list(cs, args): + """List all the services. Filter by host & service binary.""" + result = cs.services.list(host=args.host, binary=args.binary) + columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"] + utils.print_list(result, columns) + + +@utils.arg('host', metavar='', help='Name of host.') +@utils.arg('binary', metavar='', help='Service binary.') +@utils.service_type('volume') +def do_service_enable(cs, args): + """Enable the service.""" + cs.services.enable(args.host, args.binary) + + +@utils.arg('host', metavar='', help='Name of host.') +@utils.arg('binary', metavar='', help='Service binary.') +@utils.service_type('volume') +def do_service_disable(cs, args): + """Disable the service.""" + cs.services.disable(args.host, args.binary)