Migrate 'extension list' to SDK

Migrate the block storage aspects of this command to SDK. This means the
'extension list' command, like the 'availability zone list' command
previously, is now using SDK entirely.

While we're here, we also make some fixes to the unit tests for the
network and compute aspects of the command. While we migrated the
network and compute extension commands from neutronclient and novaclient
respectively some time back, we never fully updated the tests. Do this
now.

Change-Id: I631a6a09dfd9d614b1dd7b322dcee8490a52cc43
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/885132
This commit is contained in:
Stephen Finucane 2023-06-02 13:17:29 +01:00
parent 44cf963d8e
commit b87b57551b
7 changed files with 124 additions and 148 deletions

View File

@ -22,15 +22,24 @@ from osc_lib import utils
from openstackclient.i18n import _ from openstackclient.i18n import _
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def _get_extension_columns(item):
column_map = {
'updated': 'updated_at',
}
hidden_columns = ['id', 'links', 'location']
return utils.get_osc_show_columns_for_sdk_resource(
item, column_map, hidden_columns
)
class ListExtension(command.Lister): class ListExtension(command.Lister):
_description = _("List API extensions") _description = _("List API extensions")
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(ListExtension, self).get_parser(prog_name) parser = super().get_parser(prog_name)
parser.add_argument( parser.add_argument(
'--compute', '--compute',
action='store_true', action='store_true',
@ -70,7 +79,7 @@ class ListExtension(command.Lister):
'Alias', 'Alias',
'Description', 'Description',
'Namespace', 'Namespace',
'Updated', 'Updated At',
'Links', 'Links',
) )
else: else:
@ -105,12 +114,12 @@ class ListExtension(command.Lister):
LOG.warning(message) LOG.warning(message)
if parsed_args.volume or show_all: if parsed_args.volume or show_all:
volume_client = self.app.client_manager.volume volume_client = self.app.client_manager.sdk_connection.volume
try: try:
data += volume_client.list_extensions.show_all() data += volume_client.extensions()
except Exception: except Exception:
message = _( message = _(
"Extensions list not supported by " "Block Storage API" "Extensions list not supported by Block Storage API"
) )
LOG.warning(message) LOG.warning(message)
@ -120,7 +129,7 @@ class ListExtension(command.Lister):
data += network_client.extensions() data += network_client.extensions()
except Exception: except Exception:
message = _( message = _(
"Failed to retrieve extensions list " "from Network API" "Failed to retrieve extensions list from Network API"
) )
LOG.warning(message) LOG.warning(message)
@ -153,7 +162,12 @@ class ShowExtension(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
client = self.app.client_manager.network client = self.app.client_manager.network
ext = str(parsed_args.extension)
obj = client.find_extension(ext, ignore_missing=False).to_dict()
return zip(*sorted(obj.items())) extension = client.find_extension(
parsed_args.extension,
ignore_missing=False,
)
display_columns, columns = _get_extension_columns(extension)
data = utils.get_dict_properties(extension, columns)
return display_columns, data

View File

@ -20,7 +20,7 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit import utils from openstackclient.tests.unit import utils
from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit import utils as tests_utils
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes
class TestExtension(utils.TestCommand): class TestExtension(utils.TestCommand):
@ -37,26 +37,16 @@ class TestExtension(utils.TestCommand):
sdk_connection = mock.Mock() sdk_connection = mock.Mock()
self.app.client_manager.sdk_connection = sdk_connection self.app.client_manager.sdk_connection = sdk_connection
self.compute_extensions_mock = sdk_connection.compute.extensions self.compute_extensions_mock = sdk_connection.compute.extensions
self.compute_extensions_mock.reset_mock() self.compute_extensions_mock.reset_mock()
volume_client = volume_fakes.FakeVolumeClient( self.volume_extensions_mock = sdk_connection.volume.extensions
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
self.app.client_manager.volume = volume_client
volume_client.list_extensions = mock.Mock()
self.volume_extensions_mock = volume_client.list_extensions
self.volume_extensions_mock.reset_mock() self.volume_extensions_mock.reset_mock()
network_client = network_fakes.FakeNetworkV2Client( self.app.client_manager.network = mock.Mock()
endpoint=fakes.AUTH_URL, self.network_client = self.app.client_manager.network
token=fakes.AUTH_TOKEN, self.network_client.extensions = mock.Mock()
)
self.app.client_manager.network = network_client
network_client.extensions = mock.Mock()
self.network_extensions_mock = network_client.extensions
self.network_extensions_mock.reset_mock()
class TestExtensionList(TestExtension): class TestExtensionList(TestExtension):
@ -66,14 +56,14 @@ class TestExtensionList(TestExtension):
'Alias', 'Alias',
'Description', 'Description',
'Namespace', 'Namespace',
'Updated', 'Updated At',
'Links', 'Links',
) )
volume_extension = volume_fakes.create_one_extension() volume_extension = volume_fakes.create_one_extension()
identity_extension = identity_fakes.FakeExtension.create_one_extension() identity_extension = identity_fakes.FakeExtension.create_one_extension()
compute_extension = compute_fakes.create_one_extension() compute_extension = compute_fakes.create_one_extension()
network_extension = network_fakes.FakeExtension.create_one_extension() network_extension = network_fakes.create_one_extension()
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -82,10 +72,8 @@ class TestExtensionList(TestExtension):
self.identity_extension self.identity_extension
] ]
self.compute_extensions_mock.return_value = [self.compute_extension] self.compute_extensions_mock.return_value = [self.compute_extension]
self.volume_extensions_mock.show_all.return_value = [ self.volume_extensions_mock.return_value = [self.volume_extension]
self.volume_extension self.network_client.extensions.return_value = [self.network_extension]
]
self.network_extensions_mock.return_value = [self.network_extension]
# Get the command object to test # Get the command object to test
self.cmd = extension.ListExtension(self.app, None) self.cmd = extension.ListExtension(self.app, None)
@ -134,8 +122,8 @@ class TestExtensionList(TestExtension):
self._test_extension_list_helper(arglist, verifylist, datalist) self._test_extension_list_helper(arglist, verifylist, datalist)
self.identity_extensions_mock.list.assert_called_with() self.identity_extensions_mock.list.assert_called_with()
self.compute_extensions_mock.assert_called_with() self.compute_extensions_mock.assert_called_with()
self.volume_extensions_mock.show_all.assert_called_with() self.volume_extensions_mock.assert_called_with()
self.network_extensions_mock.assert_called_with() self.network_client.extensions.assert_called_with()
def test_extension_list_long(self): def test_extension_list_long(self):
arglist = [ arglist = [
@ -150,7 +138,7 @@ class TestExtensionList(TestExtension):
self.identity_extension.alias, self.identity_extension.alias,
self.identity_extension.description, self.identity_extension.description,
self.identity_extension.namespace, self.identity_extension.namespace,
self.identity_extension.updated, '',
self.identity_extension.links, self.identity_extension.links,
), ),
( (
@ -158,31 +146,31 @@ class TestExtensionList(TestExtension):
self.compute_extension.alias, self.compute_extension.alias,
self.compute_extension.description, self.compute_extension.description,
self.compute_extension.namespace, self.compute_extension.namespace,
self.compute_extension.updated, self.compute_extension.updated_at,
self.compute_extension.links, self.compute_extension.links,
), ),
( (
self.volume_extension.name, self.volume_extension.name,
self.volume_extension.alias, self.volume_extension.alias,
self.volume_extension.description, self.volume_extension.description,
self.volume_extension.namespace, '',
self.volume_extension.updated, self.volume_extension.updated_at,
self.volume_extension.links, self.volume_extension.links,
), ),
( (
self.network_extension.name, self.network_extension.name,
self.network_extension.alias, self.network_extension.alias,
self.network_extension.description, self.network_extension.description,
self.network_extension.namespace, '',
self.network_extension.updated, self.network_extension.updated_at,
self.network_extension.links, self.network_extension.links,
), ),
) )
self._test_extension_list_helper(arglist, verifylist, datalist, True) self._test_extension_list_helper(arglist, verifylist, datalist, True)
self.identity_extensions_mock.list.assert_called_with() self.identity_extensions_mock.list.assert_called_with()
self.compute_extensions_mock.assert_called_with() self.compute_extensions_mock.assert_called_with()
self.volume_extensions_mock.show_all.assert_called_with() self.volume_extensions_mock.assert_called_with()
self.network_extensions_mock.assert_called_with() self.network_client.extensions.assert_called_with()
def test_extension_list_identity(self): def test_extension_list_identity(self):
arglist = [ arglist = [
@ -216,7 +204,7 @@ class TestExtensionList(TestExtension):
), ),
) )
self._test_extension_list_helper(arglist, verifylist, datalist) self._test_extension_list_helper(arglist, verifylist, datalist)
self.network_extensions_mock.assert_called_with() self.network_client.extensions.assert_called_with()
def test_extension_list_network_with_long(self): def test_extension_list_network_with_long(self):
arglist = [ arglist = [
@ -232,15 +220,15 @@ class TestExtensionList(TestExtension):
self.network_extension.name, self.network_extension.name,
self.network_extension.alias, self.network_extension.alias,
self.network_extension.description, self.network_extension.description,
self.network_extension.namespace, '',
self.network_extension.updated, self.network_extension.updated_at,
self.network_extension.links, self.network_extension.links,
), ),
) )
self._test_extension_list_helper( self._test_extension_list_helper(
arglist, verifylist, datalist, long=True arglist, verifylist, datalist, long=True
) )
self.network_extensions_mock.assert_called_with() self.network_client.extensions.assert_called_with()
def test_extension_list_compute(self): def test_extension_list_compute(self):
arglist = [ arglist = [
@ -282,7 +270,7 @@ class TestExtensionList(TestExtension):
) )
self._test_extension_list_helper(arglist, verifylist, datalist) self._test_extension_list_helper(arglist, verifylist, datalist)
self.compute_extensions_mock.assert_called_with() self.compute_extensions_mock.assert_called_with()
self.network_extensions_mock.assert_called_with() self.network_client.extensions.assert_called_with()
def test_extension_list_volume(self): def test_extension_list_volume(self):
arglist = [ arglist = [
@ -299,28 +287,24 @@ class TestExtensionList(TestExtension):
), ),
) )
self._test_extension_list_helper(arglist, verifylist, datalist) self._test_extension_list_helper(arglist, verifylist, datalist)
self.volume_extensions_mock.show_all.assert_called_with() self.volume_extensions_mock.assert_called_with()
class TestExtensionShow(TestExtension): class TestExtensionShow(TestExtension):
extension_details = network_fakes.FakeExtension.create_one_extension() extension_details = network_fakes.create_one_extension()
columns = ( columns = (
'alias', 'alias',
'description', 'description',
'links',
'name', 'name',
'namespace', 'updated_at',
'updated',
) )
data = ( data = (
extension_details.alias, extension_details.alias,
extension_details.description, extension_details.description,
extension_details.links,
extension_details.name, extension_details.name,
extension_details.namespace, extension_details.updated_at,
extension_details.updated,
) )
def setUp(self): def setUp(self):

View File

@ -21,6 +21,7 @@ import uuid
from novaclient import api_versions from novaclient import api_versions
from openstack.compute.v2 import aggregate as _aggregate from openstack.compute.v2 import aggregate as _aggregate
from openstack.compute.v2 import availability_zone as _availability_zone from openstack.compute.v2 import availability_zone as _availability_zone
from openstack.compute.v2 import extension as _extension
from openstack.compute.v2 import flavor as _flavor from openstack.compute.v2 import flavor as _flavor
from openstack.compute.v2 import hypervisor as _hypervisor from openstack.compute.v2 import hypervisor as _hypervisor
from openstack.compute.v2 import keypair as _keypair from openstack.compute.v2 import keypair as _keypair
@ -288,35 +289,33 @@ def create_agents(attrs=None, count=2):
def create_one_extension(attrs=None): def create_one_extension(attrs=None):
"""Create a fake extension. """Create a fake extension.
:param dict attrs: :param dict attrs: A dictionary with all attributes
A dictionary with all attributes :return: A fake openstack.compute.v2.extension.Extension object
:return:
A FakeResource object with name, namespace, etc.
""" """
attrs = attrs or {} attrs = attrs or {}
# Set default attributes. # Set default attributes.
extension_info = { extension_info = {
'alias': 'NMN',
'description': 'description-' + uuid.uuid4().hex,
'links': [
{
"href": "https://github.com/openstack/compute-api",
"type": "text/html",
"rel": "describedby",
}
],
'name': 'name-' + uuid.uuid4().hex, 'name': 'name-' + uuid.uuid4().hex,
'namespace': ( 'namespace': (
'http://docs.openstack.org/compute/ext/multinic/api/v1.1' 'http://docs.openstack.org/compute/ext/multinic/api/v1.1'
), ),
'description': 'description-' + uuid.uuid4().hex, 'updated_at': '2014-01-07T12:00:0-00:00',
'updated': '2014-01-07T12:00:0-00:00',
'alias': 'NMN',
'links': (
'[{"href":'
'"https://github.com/openstack/compute-api", "type":'
' "text/html", "rel": "describedby"}]'
),
} }
# Overwrite default attributes. # Overwrite default attributes.
extension_info.update(attrs) extension_info.update(attrs)
extension = fakes.FakeResource( extension = _extension.Extension(**extension_info)
info=copy.deepcopy(extension_info), loaded=True
)
return extension return extension

View File

@ -23,6 +23,7 @@ from openstack.network.v2 import address_scope as _address_scope
from openstack.network.v2 import agent as network_agent from openstack.network.v2 import agent as network_agent
from openstack.network.v2 import auto_allocated_topology as allocated_topology from openstack.network.v2 import auto_allocated_topology as allocated_topology
from openstack.network.v2 import availability_zone as _availability_zone from openstack.network.v2 import availability_zone as _availability_zone
from openstack.network.v2 import extension as _extension
from openstack.network.v2 import flavor as _flavor from openstack.network.v2 import flavor as _flavor
from openstack.network.v2 import local_ip as _local_ip from openstack.network.v2 import local_ip as _local_ip
from openstack.network.v2 import local_ip_association as _local_ip_association from openstack.network.v2 import local_ip_association as _local_ip_association
@ -101,21 +102,12 @@ class FakeNetworkV2Client(object):
class TestNetworkV2(utils.TestCommand): class TestNetworkV2(utils.TestCommand):
def setUp(self): def setUp(self):
super(TestNetworkV2, self).setUp() super().setUp()
self.namespace = argparse.Namespace() self.namespace = argparse.Namespace()
self.app.client_manager.session = mock.Mock() self.app.client_manager.session = mock.Mock()
self.app.client_manager.network = mock.Mock()
self.app.client_manager.network = FakeNetworkV2Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
self.app.client_manager.sdk_connection = mock.Mock()
self.app.client_manager.sdk_connection.network = (
self.app.client_manager.network
)
self.app.client_manager.identity = ( self.app.client_manager.identity = (
identity_fakes_v3.FakeIdentityv3Client( identity_fakes_v3.FakeIdentityv3Client(
@ -125,37 +117,30 @@ class TestNetworkV2(utils.TestCommand):
) )
class FakeExtension(object): def create_one_extension(attrs=None):
"""Fake one or more extension.""" """Create a fake extension.
@staticmethod :param Dictionary attrs:
def create_one_extension(attrs=None): A dictionary with all attributes
"""Create a fake extension. :return:
An Extension object with name, namespace, etc.
"""
attrs = attrs or {}
:param Dictionary attrs: # Set default attributes.
A dictionary with all attributes extension_info = {
:return: 'name': 'name-' + uuid.uuid4().hex,
A FakeResource object with name, namespace, etc. 'description': 'description-' + uuid.uuid4().hex,
""" 'alias': 'Dystopian',
attrs = attrs or {} 'links': [],
'updated_at': '2013-07-09T12:00:0-00:00',
}
# Set default attributes. # Overwrite default attributes.
extension_info = { extension_info.update(attrs)
'name': 'name-' + uuid.uuid4().hex,
'namespace': 'http://docs.openstack.org/network/',
'description': 'description-' + uuid.uuid4().hex,
'updated': '2013-07-09T12:00:0-00:00',
'alias': 'Dystopian',
'links': '[{"href":' '"https://github.com/os/network", "type"}]',
}
# Overwrite default attributes. extension = _extension.Extension(**extension_info)
extension_info.update(attrs) return extension
extension = fakes.FakeResource(
info=copy.deepcopy(extension_info), loaded=True
)
return extension
class FakeNetworkQosPolicy(object): class FakeNetworkQosPolicy(object):

View File

@ -485,7 +485,7 @@ class TestDeleteRouter(TestRouter):
class TestListRouter(TestRouter): class TestListRouter(TestRouter):
# The routers going to be listed up. # The routers going to be listed up.
routers = network_fakes.FakeRouter.create_routers(count=3) routers = network_fakes.FakeRouter.create_routers(count=3)
_extensions = network_fakes.FakeExtension.create_one_extension() extensions = network_fakes.create_one_extension()
columns = ( columns = (
'ID', 'ID',
@ -572,7 +572,7 @@ class TestListRouter(TestRouter):
return_value=self.routers return_value=self.routers
) )
self.network.routers = mock.Mock(return_value=self.routers) self.network.routers = mock.Mock(return_value=self.routers)
self.network.find_extension = mock.Mock(return_value=self._extensions) self.network.find_extension = mock.Mock(return_value=self.extensions)
self.network.find_router = mock.Mock(return_value=self.routers[0]) self.network.find_router = mock.Mock(return_value=self.routers[0])
self._testagent = network_fakes.create_one_network_agent() self._testagent = network_fakes.create_one_network_agent()
self.network.get_agent = mock.Mock(return_value=self._testagent) self.network.get_agent = mock.Mock(return_value=self._testagent)

View File

@ -56,8 +56,6 @@ class FakeVolumeClient:
self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) self.cgsnapshots.resource_class = fakes.FakeResource(None, {})
self.consistencygroups = mock.Mock() self.consistencygroups = mock.Mock()
self.consistencygroups.resource_class = fakes.FakeResource(None, {}) self.consistencygroups.resource_class = fakes.FakeResource(None, {})
self.extensions = mock.Mock()
self.extensions.resource_class = fakes.FakeResource(None, {})
self.limits = mock.Mock() self.limits = mock.Mock()
self.limits.resource_class = fakes.FakeResource(None, {}) self.limits.resource_class = fakes.FakeResource(None, {})
self.pools = mock.Mock() self.pools = mock.Mock()
@ -727,42 +725,6 @@ def get_consistency_group_snapshots(snapshots=None, count=2):
return mock.Mock(side_effect=snapshots) return mock.Mock(side_effect=snapshots)
def create_one_extension(attrs=None):
"""Create a fake extension.
:param dict attrs:
A dictionary with all attributes
:return:
A FakeResource object with name, namespace, etc.
"""
attrs = attrs or {}
# Set default attributes.
extension_info = {
'name': 'name-' + uuid.uuid4().hex,
'namespace': (
'http://docs.openstack.org/'
'block-service/ext/scheduler-hints/api/v2'
),
'description': 'description-' + uuid.uuid4().hex,
'updated': '2013-04-18T00:00:00+00:00',
'alias': 'OS-SCH-HNT',
'links': (
'[{"href":'
'"https://github.com/openstack/block-api", "type":'
' "text/html", "rel": "describedby"}]'
),
}
# Overwrite default attributes.
extension_info.update(attrs)
extension = fakes.FakeResource(
info=copy.deepcopy(extension_info), loaded=True
)
return extension
def create_one_qos(attrs=None): def create_one_qos(attrs=None):
"""Create a fake Qos specification. """Create a fake Qos specification.

View File

@ -17,6 +17,7 @@ import uuid
from cinderclient import api_versions from cinderclient import api_versions
from openstack.block_storage.v3 import availability_zone as _availability_zone from openstack.block_storage.v3 import availability_zone as _availability_zone
from openstack.block_storage.v3 import block_storage_summary as _summary from openstack.block_storage.v3 import block_storage_summary as _summary
from openstack.block_storage.v3 import extension as _extension
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
from openstackclient.tests.unit import fakes from openstackclient.tests.unit import fakes
@ -116,6 +117,37 @@ def create_availability_zones(attrs=None, count=2):
return availability_zones return availability_zones
def create_one_extension(attrs=None):
"""Create a fake extension.
:param dict attrs: A dictionary with all attributes
:return: A fake
openstack.block_storage.v3.extension.Extension object
"""
attrs = attrs or {}
# Set default attributes.
extension_info = {
'alias': 'OS-SCH-HNT',
'description': 'description-' + uuid.uuid4().hex,
'links': [
{
"href": "https://github.com/openstack/block-api",
"type": "text/html",
"rel": "describedby",
}
],
'name': 'name-' + uuid.uuid4().hex,
'updated_at': '2013-04-18T00:00:00+00:00',
}
# Overwrite default attributes.
extension_info.update(attrs)
extension = _extension.Extension(**extension_info)
return extension
def create_one_cluster(attrs=None): def create_one_cluster(attrs=None):
"""Create a fake service cluster. """Create a fake service cluster.