Support for volume service list

OSC does not support to list volume services.
This patch will provide support for adding volume service related
support.

Closes-bug:#1550999

Implements: bp cinder-command-support

Change-Id: I50ac14aeb96c4b8ddbf7b33e519feea0d126f752
This commit is contained in:
Sheel Rana 2016-03-30 17:05:09 +05:30
parent 9e7f0cf1a5
commit 4072554608
10 changed files with 660 additions and 11 deletions

View File

@ -0,0 +1,31 @@
==============
volume service
==============
Volume v1, v2
volume service list
-------------------
List volume service
.. program:: volume service list
.. code:: bash
os volume service list
[--host <host>]
[--service <service>]
[--long]
.. _volume-service-list:
.. option:: --host <host>
List services on specified host (name only)
.. option:: --service <service>
List only specified service (name only)
.. option:: --long
List additional fields in output

View File

@ -127,6 +127,7 @@ referring to both Compute and Volume quotas.
* ``user role``: (**Identity**) roles assigned to a user * ``user role``: (**Identity**) roles assigned to a user
* ``volume``: (**Volume**) block volumes * ``volume``: (**Volume**) block volumes
* ``volume type``: (**Volume**) deployment-specific types of volumes available * ``volume type``: (**Volume**) deployment-specific types of volumes available
* ``volume service``: (**Volume**) services to manage block storage operations
Plugin Objects Plugin Objects

View File

@ -129,6 +129,97 @@ QOS_WITH_ASSOCIATIONS = {
} }
class FakeServiceClient(object):
def __init__(self, **kwargs):
self.services = mock.Mock()
self.services.resource_class = fakes.FakeResource(None, {})
class TestService(utils.TestCommand):
def setUp(self):
super(TestService, self).setUp()
self.app.client_manager.volume = FakeServiceClient(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
class FakeService(object):
"""Fake one or more Services."""
@staticmethod
def create_one_service(attrs=None):
"""Create a fake service.
:param Dictionary attrs:
A dictionary with all attributes of service
:retrun:
A FakeResource object with host, status, etc.
"""
# Set default attribute
service_info = {
'host': 'host_test',
'binary': 'cinder_test',
'status': 'enabled',
'disabled_reason': 'LongHoliday-GoldenWeek',
'zone': 'fake_zone',
'updated_at': 'fake_date',
'state': 'fake_state',
}
# Overwrite default attributes if there are some attributes set
if attrs is None:
attrs = {}
service_info.update(attrs)
service = fakes.FakeResource(
None,
service_info,
loaded=True)
return service
@staticmethod
def create_services(attrs=None, count=2):
"""Create multiple fake services.
:param Dictionary attrs:
A dictionary with all attributes of service
:param Integer count:
The number of services to be faked
:return:
A list of FakeResource objects
"""
services = []
for n in range(0, count):
services.append(FakeService.create_one_service(attrs))
return services
@staticmethod
def get_services(services=None, count=2):
"""Get an iterable MagicMock object with a list of faked services.
If services list is provided, then initialize the Mock object with the
list. Otherwise create one.
:param List services:
A list of FakeResource objects faking services
:param Integer count:
The number of services to be faked
:return
An iterable Mock object with side_effect set to a list of faked
services
"""
if services is None:
services = FakeService.create_services(count)
return mock.MagicMock(side_effect=services)
class FakeImagev1Client(object): class FakeImagev1Client(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -0,0 +1,141 @@
#
# 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 openstackclient.tests.volume.v1 import fakes as service_fakes
from openstackclient.volume.v1 import service
class TestService(service_fakes.TestService):
def setUp(self):
super(TestService, self).setUp()
# Get a shortcut to the ServiceManager Mock
self.service_mock = self.app.client_manager.volume.services
self.service_mock.reset_mock()
class TestServiceList(TestService):
# The service to be listed
services = service_fakes.FakeService.create_one_service()
def setUp(self):
super(TestServiceList, self).setUp()
self.service_mock.list.return_value = [self.services]
# Get the command object to test
self.cmd = service.ListService(self.app, None)
def test_service_list(self):
arglist = [
'--host', self.services.host,
'--service', self.services.binary,
]
verifylist = [
('host', self.services.host),
('service', self.services.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class Lister in cliff, abstract method take_action()
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
expected_columns = [
'Binary',
'Host',
'Zone',
'Status',
'State',
'Updated At',
]
# confirming if all expected columns are present in the result.
self.assertEqual(expected_columns, columns)
datalist = ((
self.services.binary,
self.services.host,
self.services.zone,
self.services.status,
self.services.state,
self.services.updated_at,
), )
# confirming if all expected values are present in the result.
self.assertEqual(datalist, tuple(data))
# checking if proper call was made to list services
self.service_mock.list.assert_called_with(
self.services.host,
self.services.binary,
)
# checking if prohibited columns are present in output
self.assertNotIn("Disabled Reason", columns)
self.assertNotIn(self.services.disabled_reason,
tuple(data))
def test_service_list_with_long_option(self):
arglist = [
'--host', self.services.host,
'--service', self.services.binary,
'--long'
]
verifylist = [
('host', self.services.host),
('service', self.services.binary),
('long', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class Lister in cliff, abstract method take_action()
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
expected_columns = [
'Binary',
'Host',
'Zone',
'Status',
'State',
'Updated At',
'Disabled Reason'
]
# confirming if all expected columns are present in the result.
self.assertEqual(expected_columns, columns)
datalist = ((
self.services.binary,
self.services.host,
self.services.zone,
self.services.status,
self.services.state,
self.services.updated_at,
self.services.disabled_reason,
), )
# confirming if all expected values are present in the result.
self.assertEqual(datalist, tuple(data))
self.service_mock.list.assert_called_with(
self.services.host,
self.services.binary,
)

View File

@ -232,6 +232,97 @@ EXTENSION = {
} }
class FakeServiceClient(object):
def __init__(self, **kwargs):
self.services = mock.Mock()
self.services.resource_class = fakes.FakeResource(None, {})
class TestService(utils.TestCommand):
def setUp(self):
super(TestService, self).setUp()
self.app.client_manager.volume = FakeServiceClient(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
class FakeService(object):
"""Fake one or more Services."""
@staticmethod
def create_one_service(attrs=None):
"""Create a fake service.
:param Dictionary attrs:
A dictionary with all attributes of service
:retrun:
A FakeResource object with host, status, etc.
"""
# Set default attribute
service_info = {
'host': 'host_test',
'binary': 'cinder_test',
'status': 'enabled',
'disabled_reason': 'LongHoliday-GoldenWeek',
'zone': 'fake_zone',
'updated_at': 'fake_date',
'state': 'fake_state',
}
# Overwrite default attributes if there are some attributes set
if attrs is None:
attrs = {}
service_info.update(attrs)
service = fakes.FakeResource(
None,
service_info,
loaded=True)
return service
@staticmethod
def create_services(attrs=None, count=2):
"""Create multiple fake services.
:param Dictionary attrs:
A dictionary with all attributes of service
:param Integer count:
The number of services to be faked
:return:
A list of FakeResource objects
"""
services = []
for n in range(0, count):
services.append(FakeService.create_one_service(attrs))
return services
@staticmethod
def get_services(services=None, count=2):
"""Get an iterable MagicMock object with a list of faked services.
If services list is provided, then initialize the Mock object with the
list. Otherwise create one.
:param List services:
A list of FakeResource objects faking services
:param Integer count:
The number of services to be faked
:return
An iterable Mock object with side_effect set to a list of faked
services
"""
if services is None:
services = FakeService.create_services(count)
return mock.MagicMock(side_effect=services)
class FakeVolumeClient(object): class FakeVolumeClient(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -243,6 +334,8 @@ class FakeVolumeClient(object):
self.backups.resource_class = fakes.FakeResource(None, {}) self.backups.resource_class = fakes.FakeResource(None, {})
self.volume_types = mock.Mock() self.volume_types = mock.Mock()
self.volume_types.resource_class = fakes.FakeResource(None, {}) self.volume_types.resource_class = fakes.FakeResource(None, {})
self.volume_type_access = mock.Mock()
self.volume_type_access.resource_class = fakes.FakeResource(None, {})
self.restores = mock.Mock() self.restores = mock.Mock()
self.restores.resource_class = fakes.FakeResource(None, {}) self.restores.resource_class = fakes.FakeResource(None, {})
self.qos_specs = mock.Mock() self.qos_specs = mock.Mock()
@ -279,7 +372,7 @@ class FakeVolume(object):
""" """
@staticmethod @staticmethod
def create_one_volume(attrs={}): def create_one_volume(attrs=None):
"""Create a fake volume. """Create a fake volume.
:param Dictionary attrs: :param Dictionary attrs:
@ -287,6 +380,8 @@ class FakeVolume(object):
:retrun: :retrun:
A FakeResource object with id, name, status, etc. A FakeResource object with id, name, status, etc.
""" """
attrs = attrs or {}
# Set default attribute # Set default attribute
volume_info = { volume_info = {
'id': 'volume-id' + uuid.uuid4().hex, 'id': 'volume-id' + uuid.uuid4().hex,
@ -318,7 +413,7 @@ class FakeVolume(object):
return volume return volume
@staticmethod @staticmethod
def create_volumes(attrs={}, count=2): def create_volumes(attrs=None, count=2):
"""Create multiple fake volumes. """Create multiple fake volumes.
:param Dictionary attrs: :param Dictionary attrs:
@ -359,16 +454,16 @@ class FakeAvailabilityZone(object):
"""Fake one or more volume availability zones (AZs).""" """Fake one or more volume availability zones (AZs)."""
@staticmethod @staticmethod
def create_one_availability_zone(attrs={}, methods={}): def create_one_availability_zone(attrs=None):
"""Create a fake AZ. """Create a fake AZ.
:param Dictionary attrs: :param Dictionary attrs:
A dictionary with all attributes A dictionary with all attributes
:param Dictionary methods:
A dictionary with all methods
:return: :return:
A FakeResource object with zoneName, zoneState, etc. A FakeResource object with zoneName, zoneState, etc.
""" """
attrs = attrs or {}
# Set default attributes. # Set default attributes.
availability_zone = { availability_zone = {
'zoneName': uuid.uuid4().hex, 'zoneName': uuid.uuid4().hex,
@ -380,18 +475,15 @@ class FakeAvailabilityZone(object):
availability_zone = fakes.FakeResource( availability_zone = fakes.FakeResource(
info=copy.deepcopy(availability_zone), info=copy.deepcopy(availability_zone),
methods=methods,
loaded=True) loaded=True)
return availability_zone return availability_zone
@staticmethod @staticmethod
def create_availability_zones(attrs={}, methods={}, count=2): def create_availability_zones(attrs=None, count=2):
"""Create multiple fake AZs. """Create multiple fake AZs.
:param Dictionary attrs: :param Dictionary attrs:
A dictionary with all attributes A dictionary with all attributes
:param Dictionary methods:
A dictionary with all methods
:param int count: :param int count:
The number of AZs to fake The number of AZs to fake
:return: :return:
@ -400,8 +492,7 @@ class FakeAvailabilityZone(object):
availability_zones = [] availability_zones = []
for i in range(0, count): for i in range(0, count):
availability_zone = \ availability_zone = \
FakeAvailabilityZone.create_one_availability_zone( FakeAvailabilityZone.create_one_availability_zone(attrs)
attrs, methods)
availability_zones.append(availability_zone) availability_zones.append(availability_zone)
return availability_zones return availability_zones

View File

@ -0,0 +1,141 @@
#
# 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 openstackclient.tests.volume.v2 import fakes as service_fakes
from openstackclient.volume.v2 import service
class TestService(service_fakes.TestService):
def setUp(self):
super(TestService, self).setUp()
# Get a shortcut to the ServiceManager Mock
self.service_mock = self.app.client_manager.volume.services
self.service_mock.reset_mock()
class TestServiceList(TestService):
# The service to be listed
services = service_fakes.FakeService.create_one_service()
def setUp(self):
super(TestServiceList, self).setUp()
self.service_mock.list.return_value = [self.services]
# Get the command object to test
self.cmd = service.ListService(self.app, None)
def test_service_list(self):
arglist = [
'--host', self.services.host,
'--service', self.services.binary,
]
verifylist = [
('host', self.services.host),
('service', self.services.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class Lister in cliff, abstract method take_action()
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
expected_columns = [
'Binary',
'Host',
'Zone',
'Status',
'State',
'Updated At',
]
# confirming if all expected columns are present in the result.
self.assertEqual(expected_columns, columns)
datalist = ((
self.services.binary,
self.services.host,
self.services.zone,
self.services.status,
self.services.state,
self.services.updated_at,
), )
# confirming if all expected values are present in the result.
self.assertEqual(datalist, tuple(data))
# checking if proper call was made to list services
self.service_mock.list.assert_called_with(
self.services.host,
self.services.binary,
)
# checking if prohibited columns are present in output
self.assertNotIn("Disabled Reason", columns)
self.assertNotIn(self.services.disabled_reason,
tuple(data))
def test_service_list_with_long_option(self):
arglist = [
'--host', self.services.host,
'--service', self.services.binary,
'--long'
]
verifylist = [
('host', self.services.host),
('service', self.services.binary),
('long', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# In base command class Lister in cliff, abstract method take_action()
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
expected_columns = [
'Binary',
'Host',
'Zone',
'Status',
'State',
'Updated At',
'Disabled Reason'
]
# confirming if all expected columns are present in the result.
self.assertEqual(expected_columns, columns)
datalist = ((
self.services.binary,
self.services.host,
self.services.zone,
self.services.status,
self.services.state,
self.services.updated_at,
self.services.disabled_reason,
), )
# confirming if all expected values are present in the result.
self.assertEqual(datalist, tuple(data))
self.service_mock.list.assert_called_with(
self.services.host,
self.services.binary,
)

View File

@ -0,0 +1,70 @@
#
# 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 action implementations"""
from openstackclient.common import command
from openstackclient.common import utils
class ListService(command.Lister):
"""List service command"""
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
parser.add_argument(
"--host",
metavar="<host>",
help="List services on specified host (name only)")
parser.add_argument(
"--service",
metavar="<service>",
help="List only specified service (name only)")
parser.add_argument(
"--long",
action="store_true",
default=False,
help="List additional fields in output"
)
return parser
def take_action(self, parsed_args):
service_client = self.app.client_manager.volume
if parsed_args.long:
columns = [
"Binary",
"Host",
"Zone",
"Status",
"State",
"Updated At",
"Disabled Reason"
]
else:
columns = [
"Binary",
"Host",
"Zone",
"Status",
"State",
"Updated At"
]
data = service_client.services.list(parsed_args.host,
parsed_args.service)
return (columns,
(utils.get_item_properties(
s, columns,
) for s in data))

View File

@ -0,0 +1,70 @@
#
# 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 action implementations"""
from openstackclient.common import command
from openstackclient.common import utils
class ListService(command.Lister):
"""List service command"""
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
parser.add_argument(
"--host",
metavar="<host>",
help="List services on specified host (name only)")
parser.add_argument(
"--service",
metavar="<service>",
help="List only specified service (name only)")
parser.add_argument(
"--long",
action="store_true",
default=False,
help="List additional fields in output"
)
return parser
def take_action(self, parsed_args):
service_client = self.app.client_manager.volume
if parsed_args.long:
columns = [
"Binary",
"Host",
"Zone",
"Status",
"State",
"Updated At",
"Disabled Reason"
]
else:
columns = [
"Binary",
"Host",
"Zone",
"Status",
"State",
"Updated At"
]
data = service_client.services.list(parsed_args.host,
parsed_args.service)
return (columns,
(utils.get_item_properties(
s, columns,
) for s in data))

View File

@ -0,0 +1,9 @@
---
features:
- |
Adds support for volume service list.
An user can list available volume services by using
``volume service list``
[Bug 1550999 'https://bugs.launchpad.net/python-openstackclient/+bug/1550999'_]

View File

@ -421,6 +421,8 @@ openstack.volume.v1 =
volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos
volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos
volume_service_list = openstackclient.volume.v1.service:ListService
openstack.volume.v2 = openstack.volume.v2 =
backup_create = openstackclient.volume.v2.backup:CreateBackup backup_create = openstackclient.volume.v2.backup:CreateBackup
backup_delete = openstackclient.volume.v2.backup:DeleteBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup
@ -458,6 +460,8 @@ openstack.volume.v2 =
volume_qos_show = openstackclient.volume.v2.qos_specs:ShowQos volume_qos_show = openstackclient.volume.v2.qos_specs:ShowQos
volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos
volume_service_list = openstackclient.volume.v2.service:ListService
[build_sphinx] [build_sphinx]
source-dir = doc/source source-dir = doc/source
build-dir = doc/build build-dir = doc/build