Browse Source

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
tags/2.5.0
Sheel Rana 3 years ago
parent
commit
4072554608
10 changed files with 660 additions and 11 deletions
  1. +31
    -0
      doc/source/command-objects/volume-service.rst
  2. +1
    -0
      doc/source/commands.rst
  3. +91
    -0
      openstackclient/tests/volume/v1/fakes.py
  4. +141
    -0
      openstackclient/tests/volume/v1/test_service.py
  5. +102
    -11
      openstackclient/tests/volume/v2/fakes.py
  6. +141
    -0
      openstackclient/tests/volume/v2/test_service.py
  7. +70
    -0
      openstackclient/volume/v1/service.py
  8. +70
    -0
      openstackclient/volume/v2/service.py
  9. +9
    -0
      releasenotes/notes/volume_service-5e352a71dfbc828d.yaml
  10. +4
    -0
      setup.cfg

+ 31
- 0
doc/source/command-objects/volume-service.rst 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

+ 1
- 0
doc/source/commands.rst View File

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


Plugin Objects

+ 91
- 0
openstackclient/tests/volume/v1/fakes.py 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):

def __init__(self, **kwargs):

+ 141
- 0
openstackclient/tests/volume/v1/test_service.py 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,
)

+ 102
- 11
openstackclient/tests/volume/v2/fakes.py 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):

def __init__(self, **kwargs):
@@ -243,6 +334,8 @@ class FakeVolumeClient(object):
self.backups.resource_class = fakes.FakeResource(None, {})
self.volume_types = mock.Mock()
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.resource_class = fakes.FakeResource(None, {})
self.qos_specs = mock.Mock()
@@ -279,7 +372,7 @@ class FakeVolume(object):
"""

@staticmethod
def create_one_volume(attrs={}):
def create_one_volume(attrs=None):
"""Create a fake volume.

:param Dictionary attrs:
@@ -287,6 +380,8 @@ class FakeVolume(object):
:retrun:
A FakeResource object with id, name, status, etc.
"""
attrs = attrs or {}

# Set default attribute
volume_info = {
'id': 'volume-id' + uuid.uuid4().hex,
@@ -318,7 +413,7 @@ class FakeVolume(object):
return volume

@staticmethod
def create_volumes(attrs={}, count=2):
def create_volumes(attrs=None, count=2):
"""Create multiple fake volumes.

:param Dictionary attrs:
@@ -359,16 +454,16 @@ class FakeAvailabilityZone(object):
"""Fake one or more volume availability zones (AZs)."""

@staticmethod
def create_one_availability_zone(attrs={}, methods={}):
def create_one_availability_zone(attrs=None):
"""Create a fake AZ.

:param Dictionary attrs:
A dictionary with all attributes
:param Dictionary methods:
A dictionary with all methods
:return:
A FakeResource object with zoneName, zoneState, etc.
"""
attrs = attrs or {}

# Set default attributes.
availability_zone = {
'zoneName': uuid.uuid4().hex,
@@ -380,18 +475,15 @@ class FakeAvailabilityZone(object):

availability_zone = fakes.FakeResource(
info=copy.deepcopy(availability_zone),
methods=methods,
loaded=True)
return availability_zone

@staticmethod
def create_availability_zones(attrs={}, methods={}, count=2):
def create_availability_zones(attrs=None, count=2):
"""Create multiple fake AZs.

:param Dictionary attrs:
A dictionary with all attributes
:param Dictionary methods:
A dictionary with all methods
:param int count:
The number of AZs to fake
:return:
@@ -400,8 +492,7 @@ class FakeAvailabilityZone(object):
availability_zones = []
for i in range(0, count):
availability_zone = \
FakeAvailabilityZone.create_one_availability_zone(
attrs, methods)
FakeAvailabilityZone.create_one_availability_zone(attrs)
availability_zones.append(availability_zone)

return availability_zones

+ 141
- 0
openstackclient/tests/volume/v2/test_service.py 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,
)

+ 70
- 0
openstackclient/volume/v1/service.py 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))

+ 70
- 0
openstackclient/volume/v2/service.py 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))

+ 9
- 0
releasenotes/notes/volume_service-5e352a71dfbc828d.yaml 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'_]

+ 4
- 0
setup.cfg View File

@@ -421,6 +421,8 @@ openstack.volume.v1 =
volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos
volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos

volume_service_list = openstackclient.volume.v1.service:ListService

openstack.volume.v2 =
backup_create = openstackclient.volume.v2.backup:CreateBackup
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_unset = openstackclient.volume.v2.qos_specs:UnsetQos

volume_service_list = openstackclient.volume.v2.service:ListService

[build_sphinx]
source-dir = doc/source
build-dir = doc/build

Loading…
Cancel
Save