Test attach scsi disk with config drive enabled

There were recently issues discovered in nova as it
used to consider only an instance's existing volumes
when finding the next available SCSI address when
attaching the volume, so for example if the instance
has config drive in the shape of a SCSI CDROM, it's
SCSI address gets ignored in the new address calculation
and thus there will be address collision in the xml and
libvirt will complain and hence the attachment to volume
failed with the error that the doamin already contains
the disk with that address.

This test will actually covers the same scenario by
enabling the config drive and attach/detach the volume
to the instance that is boot up using the custom image
meta property hw_cdrom_bus and hw_disk_bus as scsi
with virtio-scsi mode.

There is an additional create_image_with custom prop.
method created that will copy the default image used
in the conf. and then updating it with the custom
property mentioned in the attach scsi volume test.

Also to create an image with the custom property needs
admin priviliges for image_client so hence adding the
test here in admin tests with updating the image_client
with admin privilege in compute admin test class.

Related-Bug: #1867075

Change-Id: I9c7be55aaa363860a78d2efe2a4685433777625b
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
new file mode 100644
index 0000000..487337e
--- /dev/null
+++ b/tempest/api/compute/admin/test_volume.py
@@ -0,0 +1,104 @@
+# Copyright 2020 Red Hat Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import six
+
+from tempest.api.compute import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class BaseAttachSCSIVolumeTest(base.BaseV2ComputeAdminTest):
+    """Base class for the admin volume tests in this module."""
+    create_default_network = True
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaseAttachSCSIVolumeTest, cls).skip_checks()
+        if not CONF.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.prepare_instance_network()
+        super(BaseAttachSCSIVolumeTest, cls).setup_credentials()
+
+    def _create_image_with_custom_property(self, **kwargs):
+        """Wrapper utility that returns the custom image.
+
+        Creates a new image by downloading the default image's bits and
+        uploading them to a new image. Any kwargs are set as image properties
+        on the new image.
+
+        :param return image_id: The UUID of the newly created image.
+        """
+        image = self.image_client.show_image(CONF.compute.image_ref)
+        image_data = self.image_client.show_image_file(
+            CONF.compute.image_ref).data
+        image_file = six.BytesIO(image_data)
+        create_dict = {
+            'container_format': image['container_format'],
+            'disk_format': image['disk_format'],
+            'min_disk': image['min_disk'],
+            'min_ram': image['min_ram'],
+            'visibility': 'public',
+        }
+        create_dict.update(kwargs)
+        new_image = self.image_client.create_image(**create_dict)
+        self.addCleanup(self.image_client.delete_image, new_image['id'])
+        self.image_client.store_image_file(new_image['id'], image_file)
+
+        return new_image['id']
+
+
+class AttachSCSIVolumeTestJSON(BaseAttachSCSIVolumeTest):
+    """Test attaching scsi volume to server"""
+
+    @decorators.idempotent_id('777e468f-17ca-4da4-b93d-b7dbf56c0494')
+    def test_attach_scsi_disk_with_config_drive(self):
+        """Test the attach/detach volume with config drive/scsi disk
+
+        Enable the config drive, followed by booting an instance
+        from an image with meta properties hw_cdrom: scsi and use
+        virtio-scsi mode with further asserting list volume attachments
+        in instance after attach and detach of the volume.
+        """
+        custom_img = self._create_image_with_custom_property(
+            hw_scsi_model='virtio-scsi',
+            hw_disk_bus='scsi',
+            hw_cdrom_bus='scsi')
+        server = self.create_test_server(image_id=custom_img,
+                                         config_drive=True,
+                                         wait_until='ACTIVE')
+        volume = self.create_volume()
+        attachment = self.attach_volume(server, volume)
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, attachment['volumeId'], 'in-use')
+        volume_after_attach = self.servers_client.list_volume_attachments(
+            server['id'])['volumeAttachments']
+        self.assertEqual(1, len(volume_after_attach),
+                         "Failed to attach volume")
+        self.servers_client.detach_volume(
+            server['id'], attachment['volumeId'])
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, attachment['volumeId'], 'available')
+        volume_after_detach = self.servers_client.list_volume_attachments(
+            server['id'])['volumeAttachments']
+        self.assertEqual(0, len(volume_after_detach),
+                         "Failed to detach volume")
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 8b847fc..8f4ed8d 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -634,6 +634,7 @@
             cls.os_admin.availability_zone_client)
         cls.admin_flavors_client = cls.os_admin.flavors_client
         cls.admin_servers_client = cls.os_admin.servers_client
+        cls.image_client = cls.os_admin.image_client_v2
 
     def create_flavor(self, ram, vcpus, disk, name=None,
                       is_public='True', **kwargs):