Skip to content

Commit f43e2ed

Browse files
committed
compute: Fix formatting of 'server show'
In change Ic253184ee5f911ec2052419d328260dc4664b273, we switched to using the SDK for the 'server show' command. There were a couple of issues with this change, which we address here: - openstacksdk uses different names for fields than the nova API. We opted to output both the original names and the openstacksdk aliases in the output. With testing, however, it's become obvious that the resulting output is very long and rather unfriendly from a UX perspective. We opt to only show fields with their original names. - A number of fields included in the output are only valid in requests and will never be present in responses. These are removed. - A number of fields are not present in later API microversions or are only present under certain conditions. These are removed from output when not included in responses. - The image and flavor fields both had errant logic that resulted in unnecessary or incorrect information being show. This logic is corrected. With these changes, the output now resembles the output seen before the migration to openstacksdk. In the future we may wish to build on this further and switch from a blacklist model (removing the fields we do not wish to show from output) to a whitelist model (specifically stating which fields to show) but that's a change for another day. Change-Id: I7e3eaa0149bff202c8fd4538356cbc75b4f7e708 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
1 parent 31ae635 commit f43e2ed

File tree

2 files changed

+76
-43
lines changed

2 files changed

+76
-43
lines changed

openstackclient/compute/v2/server.py

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,13 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
150150
# Some commands using this routine were originally implemented with the
151151
# nova python wrappers, and were later migrated to use the SDK. Map the
152152
# SDK's property names to the original property names to maintain backward
153-
# compatibility for existing users. Data is duplicated under both the old
154-
# and new name so users can consume the data by either name.
153+
# compatibility for existing users.
155154
column_map = {
156155
'access_ipv4': 'accessIPv4',
157156
'access_ipv6': 'accessIPv6',
158157
'admin_password': 'adminPass',
159-
'admin_password': 'adminPass',
160-
'volumes': 'os-extended-volumes:volumes_attached',
158+
'attached_volumes': 'volumes_attached',
161159
'availability_zone': 'OS-EXT-AZ:availability_zone',
162-
'block_device_mapping': 'block_device_mapping_v2',
163160
'compute_host': 'OS-EXT-SRV-ATTR:host',
164161
'created_at': 'created',
165162
'disk_config': 'OS-DCF:diskConfig',
@@ -169,7 +166,6 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
169166
'fault': 'fault',
170167
'hostname': 'OS-EXT-SRV-ATTR:hostname',
171168
'hypervisor_hostname': 'OS-EXT-SRV-ATTR:hypervisor_hostname',
172-
'image_id': 'imageRef',
173169
'instance_name': 'OS-EXT-SRV-ATTR:instance_name',
174170
'is_locked': 'locked',
175171
'kernel_id': 'OS-EXT-SRV-ATTR:kernel_id',
@@ -180,21 +176,56 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
180176
'ramdisk_id': 'OS-EXT-SRV-ATTR:ramdisk_id',
181177
'reservation_id': 'OS-EXT-SRV-ATTR:reservation_id',
182178
'root_device_name': 'OS-EXT-SRV-ATTR:root_device_name',
183-
'scheduler_hints': 'OS-SCH-HNT:scheduler_hints',
184179
'task_state': 'OS-EXT-STS:task_state',
185180
'terminated_at': 'OS-SRV-USG:terminated_at',
186181
'updated_at': 'updated',
187182
'user_data': 'OS-EXT-SRV-ATTR:user_data',
188183
'vm_state': 'OS-EXT-STS:vm_state',
189184
}
185+
# Some columns returned by openstacksdk should not be shown because they're
186+
# either irrelevant or duplicates
187+
ignored_columns = {
188+
# computed columns
189+
'interface_ip',
190+
'location',
191+
'private_v4',
192+
'private_v6',
193+
'public_v4',
194+
'public_v6',
195+
# create-only columns
196+
'block_device_mapping',
197+
'image_id',
198+
'max_count',
199+
'min_count',
200+
'scheduler_hints',
201+
# aliases
202+
'volumes',
203+
# unnecessary
204+
'links',
205+
}
206+
# Some columns are only present in certain responses and should not be
207+
# shown otherwise.
208+
optional_columns = {
209+
'admin_password', # removed in 2.14
210+
'fault', # only present in errored servers
211+
'flavor_id', # removed in 2.47
212+
'networks', # only present in create responses
213+
'security_groups', # only present in create, detail responses
214+
}
190215

191-
info.update(
192-
{
193-
column_map[column]: data
194-
for column, data in info.items()
195-
if column in column_map
196-
}
197-
)
216+
data = {}
217+
for key, value in info.items():
218+
if key in ignored_columns:
219+
continue
220+
221+
if key in optional_columns:
222+
if info[key] is None:
223+
continue
224+
225+
alias = column_map.get(key)
226+
data[alias or key] = value
227+
228+
info = data
198229

199230
# Convert the image blob to a name
200231
image_info = info.get('image', {})
@@ -215,46 +246,57 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
215246
# Convert the flavor blob to a name
216247
flavor_info = info.get('flavor', {})
217248
# Microversion 2.47 puts the embedded flavor into the server response
218-
# body but omits the id, so if not present we just expose the flavor
219-
# dict in the server output.
220-
if 'id' in flavor_info:
249+
# body. The presence of the 'original_name' attribute indicates this.
250+
if flavor_info.get('original_name') is None: # microversion < 2.47
221251
flavor_id = flavor_info.get('id', '')
222252
try:
223253
flavor = utils.find_resource(compute_client.flavors, flavor_id)
224254
info['flavor'] = "%s (%s)" % (flavor.name, flavor_id)
225255
except Exception:
226256
info['flavor'] = flavor_id
227-
else:
257+
else: # microversion >= 2.47
228258
info['flavor'] = format_columns.DictColumn(flavor_info)
229259

230-
if 'os-extended-volumes:volumes_attached' in info:
260+
# there's a lot of redundant information in BDMs - strip it
261+
if 'volumes_attached' in info:
231262
info.update(
232263
{
233264
'volumes_attached': format_columns.ListDictColumn(
234-
info.pop('os-extended-volumes:volumes_attached')
265+
[
266+
{
267+
k: v
268+
for k, v in volume.items()
269+
if v is not None and k != 'location'
270+
}
271+
for volume in info.pop('volumes_attached') or []
272+
]
235273
)
236274
}
237275
)
276+
238277
if 'security_groups' in info:
239278
info.update(
240279
{
241280
'security_groups': format_columns.ListDictColumn(
242-
info.pop('security_groups')
281+
info.pop('security_groups'),
243282
)
244283
}
245284
)
285+
246286
if 'tags' in info:
247287
info.update({'tags': format_columns.ListColumn(info.pop('tags'))})
248288

249-
# NOTE(dtroyer): novaclient splits these into separate entries...
250-
# Format addresses in a useful way
251-
info['addresses'] = (
252-
AddressesColumn(info['addresses'])
253-
if 'addresses' in info
254-
else format_columns.DictListColumn(info.get('networks'))
255-
)
289+
# Map 'networks' to 'addresses', if present. Note that the 'networks' key
290+
# is used for create responses, otherwise it's 'addresses'. We know it'll
291+
# be set because this is one of our optional columns.
292+
if 'networks' in info:
293+
info['addresses'] = format_columns.DictListColumn(
294+
info.pop('networks', {}),
295+
)
296+
else:
297+
info['addresses'] = AddressesColumn(info.get('addresses', {}))
256298

257-
# Map 'metadata' field to 'properties'
299+
# Map 'metadata' field to 'properties' and format
258300
info['properties'] = format_columns.DictColumn(info.pop('metadata'))
259301

260302
# Migrate tenant_id to project_id naming
@@ -267,9 +309,6 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
267309
info['OS-EXT-STS:power_state']
268310
)
269311

270-
# Remove values that are long and not too useful
271-
info.pop('links', None)
272-
273312
return info
274313

275314

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,7 +1380,6 @@ class TestServerCreate(TestServer):
13801380
'id',
13811381
'image',
13821382
'name',
1383-
'networks',
13841383
'properties',
13851384
)
13861385

@@ -1394,7 +1393,6 @@ def datalist(self):
13941393
self.new_server.id,
13951394
self.image.name + ' (' + self.new_server.image.get('id') + ')',
13961395
self.new_server.name,
1397-
self.new_server.networks,
13981396
format_columns.DictColumn(self.new_server.metadata),
13991397
)
14001398
return datalist
@@ -2399,7 +2397,7 @@ def test_server_create_with_wait_ok(self, mock_wait_for_status):
23992397
self.new_server.name, self.image, self.flavor, **kwargs
24002398
)
24012399
self.assertEqual(self.columns, columns)
2402-
self.assertEqual(self.datalist(), data)
2400+
self.assertTupleEqual(self.datalist(), data)
24032401

24042402
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
24052403
def test_server_create_with_wait_fails(self, mock_wait_for_status):
@@ -8245,7 +8243,7 @@ def setUp(self):
82458243
'image': {'id': self.image.id},
82468244
'flavor': {'id': self.flavor.id},
82478245
'tenant_id': 'tenant-id-xxx',
8248-
'networks': {'public': ['10.20.30.40', '2001:db8::f']},
8246+
'addresses': {'public': ['10.20.30.40', '2001:db8::f']},
82498247
}
82508248
self.sdk_client.get_server_diagnostics.return_value = {'test': 'test'}
82518249
server_method = {
@@ -8270,7 +8268,6 @@ def setUp(self):
82708268
'id',
82718269
'image',
82728270
'name',
8273-
'networks',
82748271
'project_id',
82758272
'properties',
82768273
)
@@ -8279,12 +8276,11 @@ def setUp(self):
82798276
server.PowerStateColumn(
82808277
getattr(self.server, 'OS-EXT-STS:power_state')
82818278
),
8282-
format_columns.DictListColumn(self.server.networks),
82838279
self.flavor.name + " (" + self.flavor.id + ")",
82848280
self.server.id,
82858281
self.image.name + " (" + self.image.id + ")",
82868282
self.server.name,
8287-
{'public': ['10.20.30.40', '2001:db8::f']},
8283+
server.AddressesColumn({'public': ['10.20.30.40', '2001:db8::f']}),
82888284
'tenant-id-xxx',
82898285
format_columns.DictColumn({}),
82908286
)
@@ -9095,7 +9091,7 @@ def test_prep_server_detail(self, find_resource):
90959091
'image': {u'id': _image.id},
90969092
'flavor': {u'id': _flavor.id},
90979093
'tenant_id': u'tenant-id-xxx',
9098-
'networks': {u'public': [u'10.20.30.40', u'2001:db8::f']},
9094+
'addresses': {u'public': [u'10.20.30.40', u'2001:db8::f']},
90999095
'links': u'http://xxx.yyy.com',
91009096
'properties': '',
91019097
'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}],
@@ -9115,7 +9111,7 @@ def test_prep_server_detail(self, find_resource):
91159111
),
91169112
'properties': '',
91179113
'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}],
9118-
'addresses': format_columns.DictListColumn(_server.networks),
9114+
'addresses': format_columns.DictListColumn(_server.addresses),
91199115
'project_id': 'tenant-id-xxx',
91209116
}
91219117

@@ -9125,8 +9121,6 @@ def test_prep_server_detail(self, find_resource):
91259121
self.app.client_manager.image,
91269122
_server,
91279123
)
9128-
# 'networks' is used to create _server. Remove it.
9129-
server_detail.pop('networks')
91309124

91319125
# Check the results.
91329126
self.assertCountEqual(info, server_detail)

0 commit comments

Comments
 (0)