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 <hostname>] [--binary <binary>]
cinder service-enable <hostname> <binary>
cinder service-disable <hostname> <binary>

This change is depended on following change at Cinder side
I7f3fa889294ca6caebdf46b8689345bcac1cdf54

Implements blueprint os-services-extension

Change-Id: I4a53fd545ed3b446441302d00a429168a996a34a
This commit is contained in:
Qiu Yu
2013-07-14 23:18:22 +08:00
parent aef613f994
commit 627b616227
10 changed files with 392 additions and 0 deletions

View File

@@ -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'})

View File

@@ -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)

View File

@@ -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'})

View File

@@ -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)

View File

@@ -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:

View File

@@ -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 "<Service: %s>" % 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)

View File

@@ -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='<hostname>', default=None,
help='Name of host.')
@utils.arg('--binary', metavar='<binary>', 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='<hostname>', help='Name of host.')
@utils.arg('binary', metavar='<binary>', 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='<hostname>', help='Name of host.')
@utils.arg('binary', metavar='<binary>', help='Service binary.')
@utils.service_type('volume')
def do_service_disable(cs, args):
"""Disable the service."""
cs.services.disable(args.host, args.binary)

View File

@@ -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:

View File

@@ -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 "<Service: %s>" % 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)

View File

@@ -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='<hostname>', default=None,
help='Name of host.')
@utils.arg('--binary', metavar='<binary>', 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='<hostname>', help='Name of host.')
@utils.arg('binary', metavar='<binary>', 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='<hostname>', help='Name of host.')
@utils.arg('binary', metavar='<binary>', help='Service binary.')
@utils.service_type('volume')
def do_service_disable(cs, args):
"""Disable the service."""
cs.services.disable(args.host, args.binary)