Merge "Use the SDK for server show"
This commit is contained in:
commit
a103b6ca34
openstackclient
releasenotes/notes
@ -77,6 +77,10 @@ class AddressesColumn(cliff_columns.FormattableColumn):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return 'N/A'
|
return 'N/A'
|
||||||
|
|
||||||
|
def machine_readable(self):
|
||||||
|
return {k: [i['addr'] for i in v if 'addr' in i]
|
||||||
|
for k, v in self._value.items()}
|
||||||
|
|
||||||
|
|
||||||
class HostColumn(cliff_columns.FormattableColumn):
|
class HostColumn(cliff_columns.FormattableColumn):
|
||||||
"""Generate a formatted string of a hostname."""
|
"""Generate a formatted string of a hostname."""
|
||||||
@ -133,14 +137,61 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
|
|||||||
the latest details of a server after creating it.
|
the latest details of a server after creating it.
|
||||||
:rtype: a dict of server details
|
:rtype: a dict of server details
|
||||||
"""
|
"""
|
||||||
|
# Note: Some callers of this routine pass a novaclient server, and others
|
||||||
|
# pass an SDK server. Column names may be different across those cases.
|
||||||
info = server.to_dict()
|
info = server.to_dict()
|
||||||
if refresh:
|
if refresh:
|
||||||
server = utils.find_resource(compute_client.servers, info['id'])
|
server = utils.find_resource(compute_client.servers, info['id'])
|
||||||
info.update(server.to_dict())
|
info.update(server.to_dict())
|
||||||
|
|
||||||
|
# Some commands using this routine were originally implemented with the
|
||||||
|
# nova python wrappers, and were later migrated to use the SDK. Map the
|
||||||
|
# SDK's property names to the original property names to maintain backward
|
||||||
|
# compatibility for existing users. Data is duplicated under both the old
|
||||||
|
# and new name so users can consume the data by either name.
|
||||||
|
column_map = {
|
||||||
|
'access_ipv4': 'accessIPv4',
|
||||||
|
'access_ipv6': 'accessIPv6',
|
||||||
|
'admin_password': 'adminPass',
|
||||||
|
'admin_password': 'adminPass',
|
||||||
|
'volumes': 'os-extended-volumes:volumes_attached',
|
||||||
|
'availability_zone': 'OS-EXT-AZ:availability_zone',
|
||||||
|
'block_device_mapping': 'block_device_mapping_v2',
|
||||||
|
'compute_host': 'OS-EXT-SRV-ATTR:host',
|
||||||
|
'created_at': 'created',
|
||||||
|
'disk_config': 'OS-DCF:diskConfig',
|
||||||
|
'flavor_id': 'flavorRef',
|
||||||
|
'has_config_drive': 'config_drive',
|
||||||
|
'host_id': 'hostId',
|
||||||
|
'fault': 'fault',
|
||||||
|
'hostname': 'OS-EXT-SRV-ATTR:hostname',
|
||||||
|
'hypervisor_hostname': 'OS-EXT-SRV-ATTR:hypervisor_hostname',
|
||||||
|
'image_id': 'imageRef',
|
||||||
|
'instance_name': 'OS-EXT-SRV-ATTR:instance_name',
|
||||||
|
'is_locked': 'locked',
|
||||||
|
'kernel_id': 'OS-EXT-SRV-ATTR:kernel_id',
|
||||||
|
'launch_index': 'OS-EXT-SRV-ATTR:launch_index',
|
||||||
|
'launched_at': 'OS-SRV-USG:launched_at',
|
||||||
|
'power_state': 'OS-EXT-STS:power_state',
|
||||||
|
'project_id': 'tenant_id',
|
||||||
|
'ramdisk_id': 'OS-EXT-SRV-ATTR:ramdisk_id',
|
||||||
|
'reservation_id': 'OS-EXT-SRV-ATTR:reservation_id',
|
||||||
|
'root_device_name': 'OS-EXT-SRV-ATTR:root_device_name',
|
||||||
|
'scheduler_hints': 'OS-SCH-HNT:scheduler_hints',
|
||||||
|
'task_state': 'OS-EXT-STS:task_state',
|
||||||
|
'terminated_at': 'OS-SRV-USG:terminated_at',
|
||||||
|
'updated_at': 'updated',
|
||||||
|
'user_data': 'OS-EXT-SRV-ATTR:user_data',
|
||||||
|
'vm_state': 'OS-EXT-STS:vm_state',
|
||||||
|
}
|
||||||
|
|
||||||
|
info.update({
|
||||||
|
column_map[column]: data for column, data in info.items()
|
||||||
|
if column in column_map})
|
||||||
|
|
||||||
# Convert the image blob to a name
|
# Convert the image blob to a name
|
||||||
image_info = info.get('image', {})
|
image_info = info.get('image', {})
|
||||||
if image_info:
|
if image_info and any(image_info.values()):
|
||||||
image_id = image_info.get('id', '')
|
image_id = image_info.get('id', '')
|
||||||
try:
|
try:
|
||||||
image = image_client.get_image(image_id)
|
image = image_client.get_image(image_id)
|
||||||
@ -188,7 +239,9 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
|
|||||||
|
|
||||||
# NOTE(dtroyer): novaclient splits these into separate entries...
|
# NOTE(dtroyer): novaclient splits these into separate entries...
|
||||||
# Format addresses in a useful way
|
# Format addresses in a useful way
|
||||||
info['addresses'] = format_columns.DictListColumn(server.networks)
|
info['addresses'] = (
|
||||||
|
AddressesColumn(info['addresses']) if 'addresses' in info
|
||||||
|
else format_columns.DictListColumn(info.get('networks')))
|
||||||
|
|
||||||
# Map 'metadata' field to 'properties'
|
# Map 'metadata' field to 'properties'
|
||||||
info['properties'] = format_columns.DictColumn(info.pop('metadata'))
|
info['properties'] = format_columns.DictColumn(info.pop('metadata'))
|
||||||
@ -4327,32 +4380,34 @@ class ShowServer(command.ShowOne):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
compute_client = self.app.client_manager.compute
|
compute_client = self.app.client_manager.sdk_connection.compute
|
||||||
server = utils.find_resource(
|
|
||||||
compute_client.servers, parsed_args.server)
|
# Find by name or ID, then get the full details of the server
|
||||||
|
server = compute_client.find_server(
|
||||||
|
parsed_args.server, ignore_missing=False)
|
||||||
|
server = compute_client.get_server(server)
|
||||||
|
|
||||||
if parsed_args.diagnostics:
|
if parsed_args.diagnostics:
|
||||||
(resp, data) = server.diagnostics()
|
data = compute_client.get_server_diagnostics(server)
|
||||||
if not resp.status_code == 200:
|
|
||||||
self.app.stderr.write(_(
|
|
||||||
"Error retrieving diagnostics data\n"
|
|
||||||
))
|
|
||||||
return ({}, {})
|
|
||||||
return zip(*sorted(data.items()))
|
return zip(*sorted(data.items()))
|
||||||
|
|
||||||
topology = None
|
topology = None
|
||||||
if parsed_args.topology:
|
if parsed_args.topology:
|
||||||
if compute_client.api_version < api_versions.APIVersion('2.78'):
|
if not sdk_utils.supports_microversion(compute_client, '2.78'):
|
||||||
msg = _(
|
msg = _(
|
||||||
'--os-compute-api-version 2.78 or greater is required to '
|
'--os-compute-api-version 2.78 or greater is required to '
|
||||||
'support the --topology option'
|
'support the --topology option'
|
||||||
)
|
)
|
||||||
raise exceptions.CommandError(msg)
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
topology = server.topology()
|
topology = server.fetch_topology(compute_client)
|
||||||
|
|
||||||
data = _prep_server_detail(
|
data = _prep_server_detail(
|
||||||
compute_client, self.app.client_manager.image, server,
|
# TODO(dannosliwcd): Replace these clients with SDK clients after
|
||||||
|
# all callers of _prep_server_detail() are using the SDK.
|
||||||
|
self.app.client_manager.compute,
|
||||||
|
self.app.client_manager.image,
|
||||||
|
server,
|
||||||
refresh=False)
|
refresh=False)
|
||||||
|
|
||||||
if topology:
|
if topology:
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -288,6 +289,33 @@ class ServerTests(common.ComputeTestCase):
|
|||||||
)
|
)
|
||||||
self.assertOutput("", raw_output)
|
self.assertOutput("", raw_output)
|
||||||
|
|
||||||
|
def test_server_show(self):
|
||||||
|
"""Test server show"""
|
||||||
|
cmd_output = self.server_create()
|
||||||
|
name = cmd_output['name']
|
||||||
|
|
||||||
|
# Simple show
|
||||||
|
cmd_output = json.loads(self.openstack(
|
||||||
|
f'server show -f json {name}'
|
||||||
|
))
|
||||||
|
self.assertEqual(
|
||||||
|
name,
|
||||||
|
cmd_output["name"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Show diagnostics
|
||||||
|
cmd_output = json.loads(self.openstack(
|
||||||
|
f'server show -f json {name} --diagnostics'
|
||||||
|
))
|
||||||
|
self.assertIn('driver', cmd_output)
|
||||||
|
|
||||||
|
# Show topology
|
||||||
|
cmd_output = json.loads(self.openstack(
|
||||||
|
f'server show -f json {name} --topology '
|
||||||
|
f'--os-compute-api-version 2.78'
|
||||||
|
))
|
||||||
|
self.assertIn('topology', cmd_output)
|
||||||
|
|
||||||
def test_server_actions(self):
|
def test_server_actions(self):
|
||||||
"""Test server action pairs
|
"""Test server action pairs
|
||||||
|
|
||||||
|
@ -7931,20 +7931,15 @@ class TestServerShow(TestServer):
|
|||||||
'tenant_id': 'tenant-id-xxx',
|
'tenant_id': 'tenant-id-xxx',
|
||||||
'networks': {'public': ['10.20.30.40', '2001:db8::f']},
|
'networks': {'public': ['10.20.30.40', '2001:db8::f']},
|
||||||
}
|
}
|
||||||
# Fake the server.diagnostics() method. The return value contains http
|
self.sdk_client.get_server_diagnostics.return_value = {'test': 'test'}
|
||||||
# response and data. The data is a dict. Sincce this method itself is
|
|
||||||
# faked, we don't need to fake everything of the return value exactly.
|
|
||||||
resp = mock.Mock()
|
|
||||||
resp.status_code = 200
|
|
||||||
server_method = {
|
server_method = {
|
||||||
'diagnostics': (resp, {'test': 'test'}),
|
'fetch_topology': self.topology,
|
||||||
'topology': self.topology,
|
|
||||||
}
|
}
|
||||||
self.server = compute_fakes.FakeServer.create_one_server(
|
self.server = compute_fakes.FakeServer.create_one_server(
|
||||||
attrs=server_info, methods=server_method)
|
attrs=server_info, methods=server_method)
|
||||||
|
|
||||||
# This is the return value for utils.find_resource()
|
# This is the return value for utils.find_resource()
|
||||||
self.servers_mock.get.return_value = self.server
|
self.sdk_client.get_server.return_value = self.server
|
||||||
self.get_image_mock.return_value = self.image
|
self.get_image_mock.return_value = self.image
|
||||||
self.flavors_mock.get.return_value = self.flavor
|
self.flavors_mock.get.return_value = self.flavor
|
||||||
|
|
||||||
@ -8045,8 +8040,7 @@ class TestServerShow(TestServer):
|
|||||||
self.assertEqual(('test',), data)
|
self.assertEqual(('test',), data)
|
||||||
|
|
||||||
def test_show_topology(self):
|
def test_show_topology(self):
|
||||||
self.app.client_manager.compute.api_version = \
|
self._set_mock_microversion('2.78')
|
||||||
api_versions.APIVersion('2.78')
|
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--topology',
|
'--topology',
|
||||||
@ -8068,8 +8062,7 @@ class TestServerShow(TestServer):
|
|||||||
self.assertCountEqual(self.data, data)
|
self.assertCountEqual(self.data, data)
|
||||||
|
|
||||||
def test_show_topology_pre_v278(self):
|
def test_show_topology_pre_v278(self):
|
||||||
self.app.client_manager.compute.api_version = \
|
self._set_mock_microversion('2.77')
|
||||||
api_versions.APIVersion('2.77')
|
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--topology',
|
'--topology',
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ``server show`` command now uses the OpenStack SDK instead of the
|
||||||
|
Python nova bindings. The command prints data fields both by their
|
||||||
|
novaclient names used in previous releases as well as the names used in the
|
||||||
|
SDK.
|
Loading…
x
Reference in New Issue
Block a user