From 4a3c5207c1b2d628d1ec8b30e28f56e5956bd2e0 Mon Sep 17 00:00:00 2001
From: melanie witt <melwittt@gmail.com>
Date: Mon, 27 Jul 2020 22:04:47 +0000
Subject: [PATCH] Show words indicating booted from volume for server image

For a server booted from a volume, nova API does not store an image_id
and instead returns an empty string. Currently, openstackclient
similarly shows an empty string for Image Name and Image ID for servers
booted from volumes.

To aid CLI users in understanding the meaning of no image_id, we can
display the string "N/A (booted from volume)" in the image field if the
server was booted from a volume.

Change-Id: I9c62cf6fe23b2e934dcbf5ebbf706b2705d2e424
---
 openstackclient/compute/v2/server.py          | 16 ++++++++++++++--
 .../functional/compute/v2/test_server.py      | 19 +++++++++++++++++--
 .../tests/unit/compute/v2/test_server.py      |  8 ++++----
 3 files changed, 35 insertions(+), 8 deletions(-)

diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 93e9f966ae..ff40565a6b 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -38,6 +38,8 @@ from openstackclient.network import common as network_common
 
 LOG = logging.getLogger(__name__)
 
+IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)'
+
 
 def _format_servers_list_networks(networks):
     """Return a formatted string of a server's networks
@@ -147,6 +149,12 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
             info['image'] = "%s (%s)" % (image.name, image_id)
         except Exception:
             info['image'] = image_id
+    else:
+        # NOTE(melwitt): An server booted from a volume will have no image
+        # associated with it. We fill in the image with "N/A (booted from
+        # volume)" to help users who want to be able to grep for
+        # boot-from-volume servers when using the CLI.
+        info['image'] = IMAGE_STRING_FOR_BFV
 
     # Convert the flavor blob to a name
     flavor_info = info.get('flavor', {})
@@ -1520,8 +1528,12 @@ class ListServer(command.Lister):
                     s.image_name = image.name
                 s.image_id = s.image['id']
             else:
-                s.image_name = ''
-                s.image_id = ''
+                # NOTE(melwitt): An server booted from a volume will have no
+                # image associated with it. We fill in the Image Name and ID
+                # with "N/A (booted from volume)" to help users who want to be
+                # able to grep for boot-from-volume servers when using the CLI.
+                s.image_name = IMAGE_STRING_FOR_BFV
+                s.image_id = IMAGE_STRING_FOR_BFV
             if 'id' in s.flavor:
                 flavor = flavors.get(s.flavor['id'])
                 if flavor:
diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py
index 6e080e9ba2..3123057c8e 100644
--- a/openstackclient/tests/functional/compute/v2/test_server.py
+++ b/openstackclient/tests/functional/compute/v2/test_server.py
@@ -16,6 +16,7 @@ import uuid
 
 from tempest.lib import exceptions
 
+from openstackclient.compute.v2 import server as v2_server
 from openstackclient.tests.functional.compute.v2 import common
 from openstackclient.tests.functional.volume.v2 import common as volume_common
 
@@ -509,6 +510,20 @@ class ServerTests(common.ComputeTestCase):
             server['name'],
         )
 
+        # check that image indicates server "booted from volume"
+        self.assertEqual(
+            v2_server.IMAGE_STRING_FOR_BFV,
+            server['image'],
+        )
+        # check server list too
+        servers = json.loads(self.openstack(
+            'server list -f json'
+        ))
+        self.assertEqual(
+            v2_server.IMAGE_STRING_FOR_BFV,
+            servers[0]['Image']
+        )
+
         # check volumes
         cmd_output = json.loads(self.openstack(
             'volume show -f json ' +
@@ -779,8 +794,8 @@ class ServerTests(common.ComputeTestCase):
         self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id)
 
         # Since the server is volume-backed the GET /servers/{server_id}
-        # response will have image=''.
-        self.assertEqual('', cmd_output['image'])
+        # response will have image='N/A (booted from volume)'.
+        self.assertEqual(v2_server.IMAGE_STRING_FOR_BFV, cmd_output['image'])
 
         # check the volume that attached on server
         cmd_output = json.loads(self.openstack(
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 7e4c71c50c..405e05d160 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -2609,7 +2609,7 @@ class TestServerList(TestServer):
                 s.status,
                 server._format_servers_list_networks(s.networks),
                 # Image will be an empty string if boot-from-volume
-                self.image.name if s.image else s.image,
+                self.image.name if s.image else server.IMAGE_STRING_FOR_BFV,
                 self.flavor.name,
             ))
             self.data_long.append((
@@ -2622,8 +2622,8 @@ class TestServerList(TestServer):
                 ),
                 server._format_servers_list_networks(s.networks),
                 # Image will be an empty string if boot-from-volume
-                self.image.name if s.image else s.image,
-                s.image['id'] if s.image else s.image,
+                self.image.name if s.image else server.IMAGE_STRING_FOR_BFV,
+                s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV,
                 self.flavor.name,
                 s.flavor['id'],
                 getattr(s, 'OS-EXT-AZ:availability_zone'),
@@ -2636,7 +2636,7 @@ class TestServerList(TestServer):
                 s.status,
                 server._format_servers_list_networks(s.networks),
                 # Image will be an empty string if boot-from-volume
-                s.image['id'] if s.image else s.image,
+                s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV,
                 s.flavor['id']
             ))