Skip to content

Commit 70dbb01

Browse files
committed
Use the SDK for server show
Use the SDK for the server show command. This change modifies a helper function that is used by server show as well as other commands that print information about an individual server. The helper still uses novaclient APIs when additional OpenStack requests are needed since some of its callers are still using the nova client. Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/864340 Change-Id: Ic253184ee5f911ec2052419d328260dc4664b273
1 parent 10453e0 commit 70dbb01

4 files changed

Lines changed: 109 additions & 26 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def human_readable(self):
7777
except Exception:
7878
return 'N/A'
7979

80+
def machine_readable(self):
81+
return {k: [i['addr'] for i in v if 'addr' in i]
82+
for k, v in self._value.items()}
83+
8084

8185
class HostColumn(cliff_columns.FormattableColumn):
8286
"""Generate a formatted string of a hostname."""
@@ -133,14 +137,61 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
133137
the latest details of a server after creating it.
134138
:rtype: a dict of server details
135139
"""
140+
# Note: Some callers of this routine pass a novaclient server, and others
141+
# pass an SDK server. Column names may be different across those cases.
136142
info = server.to_dict()
137143
if refresh:
138144
server = utils.find_resource(compute_client.servers, info['id'])
139145
info.update(server.to_dict())
140146

147+
# Some commands using this routine were originally implemented with the
148+
# nova python wrappers, and were later migrated to use the SDK. Map the
149+
# SDK's property names to the original property names to maintain backward
150+
# compatibility for existing users. Data is duplicated under both the old
151+
# and new name so users can consume the data by either name.
152+
column_map = {
153+
'access_ipv4': 'accessIPv4',
154+
'access_ipv6': 'accessIPv6',
155+
'admin_password': 'adminPass',
156+
'admin_password': 'adminPass',
157+
'volumes': 'os-extended-volumes:volumes_attached',
158+
'availability_zone': 'OS-EXT-AZ:availability_zone',
159+
'block_device_mapping': 'block_device_mapping_v2',
160+
'compute_host': 'OS-EXT-SRV-ATTR:host',
161+
'created_at': 'created',
162+
'disk_config': 'OS-DCF:diskConfig',
163+
'flavor_id': 'flavorRef',
164+
'has_config_drive': 'config_drive',
165+
'host_id': 'hostId',
166+
'fault': 'fault',
167+
'hostname': 'OS-EXT-SRV-ATTR:hostname',
168+
'hypervisor_hostname': 'OS-EXT-SRV-ATTR:hypervisor_hostname',
169+
'image_id': 'imageRef',
170+
'instance_name': 'OS-EXT-SRV-ATTR:instance_name',
171+
'is_locked': 'locked',
172+
'kernel_id': 'OS-EXT-SRV-ATTR:kernel_id',
173+
'launch_index': 'OS-EXT-SRV-ATTR:launch_index',
174+
'launched_at': 'OS-SRV-USG:launched_at',
175+
'power_state': 'OS-EXT-STS:power_state',
176+
'project_id': 'tenant_id',
177+
'ramdisk_id': 'OS-EXT-SRV-ATTR:ramdisk_id',
178+
'reservation_id': 'OS-EXT-SRV-ATTR:reservation_id',
179+
'root_device_name': 'OS-EXT-SRV-ATTR:root_device_name',
180+
'scheduler_hints': 'OS-SCH-HNT:scheduler_hints',
181+
'task_state': 'OS-EXT-STS:task_state',
182+
'terminated_at': 'OS-SRV-USG:terminated_at',
183+
'updated_at': 'updated',
184+
'user_data': 'OS-EXT-SRV-ATTR:user_data',
185+
'vm_state': 'OS-EXT-STS:vm_state',
186+
}
187+
188+
info.update({
189+
column_map[column]: data for column, data in info.items()
190+
if column in column_map})
191+
141192
# Convert the image blob to a name
142193
image_info = info.get('image', {})
143-
if image_info:
194+
if image_info and any(image_info.values()):
144195
image_id = image_info.get('id', '')
145196
try:
146197
image = image_client.get_image(image_id)
@@ -188,7 +239,9 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
188239

189240
# NOTE(dtroyer): novaclient splits these into separate entries...
190241
# Format addresses in a useful way
191-
info['addresses'] = format_columns.DictListColumn(server.networks)
242+
info['addresses'] = (
243+
AddressesColumn(info['addresses']) if 'addresses' in info
244+
else format_columns.DictListColumn(info.get('networks')))
192245

193246
# Map 'metadata' field to 'properties'
194247
info['properties'] = format_columns.DictColumn(info.pop('metadata'))
@@ -4319,32 +4372,34 @@ def get_parser(self, prog_name):
43194372
return parser
43204373

43214374
def take_action(self, parsed_args):
4322-
compute_client = self.app.client_manager.compute
4323-
server = utils.find_resource(
4324-
compute_client.servers, parsed_args.server)
4375+
compute_client = self.app.client_manager.sdk_connection.compute
4376+
4377+
# Find by name or ID, then get the full details of the server
4378+
server = compute_client.find_server(
4379+
parsed_args.server, ignore_missing=False)
4380+
server = compute_client.get_server(server)
43254381

43264382
if parsed_args.diagnostics:
4327-
(resp, data) = server.diagnostics()
4328-
if not resp.status_code == 200:
4329-
self.app.stderr.write(_(
4330-
"Error retrieving diagnostics data\n"
4331-
))
4332-
return ({}, {})
4383+
data = compute_client.get_server_diagnostics(server)
43334384
return zip(*sorted(data.items()))
43344385

43354386
topology = None
43364387
if parsed_args.topology:
4337-
if compute_client.api_version < api_versions.APIVersion('2.78'):
4388+
if not sdk_utils.supports_microversion(compute_client, '2.78'):
43384389
msg = _(
43394390
'--os-compute-api-version 2.78 or greater is required to '
43404391
'support the --topology option'
43414392
)
43424393
raise exceptions.CommandError(msg)
43434394

4344-
topology = server.topology()
4395+
topology = server.fetch_topology(compute_client)
43454396

43464397
data = _prep_server_detail(
4347-
compute_client, self.app.client_manager.image, server,
4398+
# TODO(dannosliwcd): Replace these clients with SDK clients after
4399+
# all callers of _prep_server_detail() are using the SDK.
4400+
self.app.client_manager.compute,
4401+
self.app.client_manager.image,
4402+
server,
43484403
refresh=False)
43494404

43504405
if topology:

openstackclient/tests/functional/compute/v2/test_server.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# under the License.
1212

1313
import itertools
14+
import json
1415
import time
1516
import uuid
1617

@@ -288,6 +289,33 @@ def test_server_set(self):
288289
)
289290
self.assertOutput("", raw_output)
290291

292+
def test_server_show(self):
293+
"""Test server show"""
294+
cmd_output = self.server_create()
295+
name = cmd_output['name']
296+
297+
# Simple show
298+
cmd_output = json.loads(self.openstack(
299+
f'server show -f json {name}'
300+
))
301+
self.assertEqual(
302+
name,
303+
cmd_output["name"],
304+
)
305+
306+
# Show diagnostics
307+
cmd_output = json.loads(self.openstack(
308+
f'server show -f json {name} --diagnostics'
309+
))
310+
self.assertIn('driver', cmd_output)
311+
312+
# Show topology
313+
cmd_output = json.loads(self.openstack(
314+
f'server show -f json {name} --topology '
315+
f'--os-compute-api-version 2.78'
316+
))
317+
self.assertIn('topology', cmd_output)
318+
291319
def test_server_actions(self):
292320
"""Test server action pairs
293321

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7885,20 +7885,15 @@ def setUp(self):
78857885
'tenant_id': 'tenant-id-xxx',
78867886
'networks': {'public': ['10.20.30.40', '2001:db8::f']},
78877887
}
7888-
# Fake the server.diagnostics() method. The return value contains http
7889-
# response and data. The data is a dict. Sincce this method itself is
7890-
# faked, we don't need to fake everything of the return value exactly.
7891-
resp = mock.Mock()
7892-
resp.status_code = 200
7888+
self.sdk_client.get_server_diagnostics.return_value = {'test': 'test'}
78937889
server_method = {
7894-
'diagnostics': (resp, {'test': 'test'}),
7895-
'topology': self.topology,
7890+
'fetch_topology': self.topology,
78967891
}
78977892
self.server = compute_fakes.FakeServer.create_one_server(
78987893
attrs=server_info, methods=server_method)
78997894

79007895
# This is the return value for utils.find_resource()
7901-
self.servers_mock.get.return_value = self.server
7896+
self.sdk_client.get_server.return_value = self.server
79027897
self.get_image_mock.return_value = self.image
79037898
self.flavors_mock.get.return_value = self.flavor
79047899

@@ -7999,8 +7994,7 @@ def test_show_diagnostics(self):
79997994
self.assertEqual(('test',), data)
80007995

80017996
def test_show_topology(self):
8002-
self.app.client_manager.compute.api_version = \
8003-
api_versions.APIVersion('2.78')
7997+
self._set_mock_microversion('2.78')
80047998

80057999
arglist = [
80068000
'--topology',
@@ -8022,8 +8016,7 @@ def test_show_topology(self):
80228016
self.assertCountEqual(self.data, data)
80238017

80248018
def test_show_topology_pre_v278(self):
8025-
self.app.client_manager.compute.api_version = \
8026-
api_versions.APIVersion('2.77')
8019+
self._set_mock_microversion('2.77')
80278020

80288021
arglist = [
80298022
'--topology',
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- |
4+
The ``server show`` command now uses the OpenStack SDK instead of the
5+
Python nova bindings. The command prints data fields both by their
6+
novaclient names used in previous releases as well as the names used in the
7+
SDK.

0 commit comments

Comments
 (0)