From 52d2ef021fab8513c68bbf40a9e3990c09920f33 Mon Sep 17 00:00:00 2001
From: Vipin Balachandran <vbala@vmware.com>
Date: Fri, 15 Dec 2017 18:09:41 -0800
Subject: [PATCH] VMware: Support volume adapter type change

Adding support for volume adapter type change using volume
retype.

Change-Id: I7731eaf9561965d4e0f87c0597bfdb784ef6ca7a
---
 .../volume/drivers/vmware/test_vmware_vmdk.py | 44 ++++++++++++-
 .../drivers/vmware/test_vmware_volumeops.py   | 63 ++++++++++++++++++-
 cinder/volume/drivers/vmware/vmdk.py          | 17 ++++-
 cinder/volume/drivers/vmware/volumeops.py     | 35 +++++++++--
 ..._retype_adapter_type-dbd8935b8d3bcb1b.yaml |  6 ++
 5 files changed, 154 insertions(+), 11 deletions(-)
 create mode 100644 releasenotes/notes/vmware_retype_adapter_type-dbd8935b8d3bcb1b.yaml

diff --git a/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py b/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py
index ee4dda53b7e..d17a95068e3 100644
--- a/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py
+++ b/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py
@@ -1238,10 +1238,22 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
     @mock.patch.object(VMDK_DRIVER, '_get_extra_spec_storage_profile')
     @mock.patch.object(VMDK_DRIVER, 'ds_sel')
     @mock.patch.object(VMDK_DRIVER, '_select_datastore')
+    @mock.patch.object(
+        VMDK_DRIVER, '_get_adapter_type', return_value='lsiLogic')
+    @mock.patch.object(
+        VMDK_DRIVER, '_get_extra_spec_adapter_type', return_value='lsiLogic')
     def test_retype_with_diff_profile_and_ds_compliance(
-            self, select_datastore, ds_sel, get_extra_spec_storage_profile,
-            get_storage_profile, get_extra_spec_disk_type, get_disk_type,
-            vops, in_use):
+            self,
+            _get_extra_spec_adapter_type,
+            _get_adapter_type,
+            select_datastore,
+            ds_sel,
+            get_extra_spec_storage_profile,
+            get_storage_profile,
+            get_extra_spec_disk_type,
+            get_disk_type,
+            vops,
+            in_use):
         backing = mock.sentinel.backing
         vops.get_backing.return_value = backing
 
@@ -1337,8 +1349,14 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
     @mock.patch.object(VMDK_DRIVER, '_select_datastore')
     @mock.patch.object(VMDK_DRIVER, '_get_dc')
     @mock.patch.object(VMDK_DRIVER, '_get_volume_group_folder')
+    @mock.patch.object(
+        VMDK_DRIVER, '_get_adapter_type', return_value='lsiLogic')
+    @mock.patch.object(
+        VMDK_DRIVER, '_get_extra_spec_adapter_type', return_value='lsiLogic')
     def test_retype_with_diff_extra_spec_and_vol_snapshot(
             self,
+            get_extra_spec_adapter_type,
+            get_adapter_type,
             get_volume_group_folder,
             get_dc,
             select_datastore,
@@ -1418,8 +1436,12 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
     @mock.patch.object(VMDK_DRIVER, '_get_volume_group_folder')
     @mock.patch('oslo_utils.uuidutils.generate_uuid')
     @mock.patch.object(VMDK_DRIVER, '_delete_temp_backing')
+    @mock.patch.object(VMDK_DRIVER, '_get_adapter_type')
+    @mock.patch.object(VMDK_DRIVER, '_get_extra_spec_adapter_type')
     def _test_retype_with_diff_extra_spec_and_ds_compliance(
             self,
+            get_extra_spec_adapter_type,
+            get_adapter_type,
             delete_temp_backing,
             generate_uuid,
             get_volume_group_folder,
@@ -1475,6 +1497,17 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
             new_backing = mock.sentinel.new_backing
             vops.clone_backing.return_value = new_backing
 
+        adapter_type = 'lsiLogic'
+        get_adapter_type.return_value = adapter_type
+        new_adapter_type = 'paraVirtual'
+        get_extra_spec_adapter_type.return_value = new_adapter_type
+
+        capacity = self.VOL_SIZE * units.Mi
+        filename = mock.sentinel.filename
+        disk_backing = mock.Mock(filename=filename)
+        disk_device = mock.Mock(capacityInKB=capacity, backing=disk_backing)
+        vops._get_disk_device.return_value = disk_device
+
         context = mock.sentinel.context
         volume = self._create_volume_dict(status='retyping')
         new_type = {'id': 'f04a65e0-d10c-4db7-b4a5-f933d57aa2b5'}
@@ -1510,6 +1543,11 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
             vops.update_backing_disk_uuid.assert_called_once_with(
                 new_backing, volume['id'])
             delete_temp_backing.assert_called_once_with(backing)
+            vops.detach_disk_from_backing.assert_called_once_with(
+                new_backing, disk_device)
+            vops.attach_disk_to_backing.assert_called_once_with(
+                new_backing, disk_device.capacityInKB, new_disk_type,
+                new_adapter_type, None, disk_device.backing.fileName)
             vops.change_backing_profile.assert_called_once_with(new_backing,
                                                                 new_profile_id)
 
diff --git a/cinder/tests/unit/volume/drivers/vmware/test_vmware_volumeops.py b/cinder/tests/unit/volume/drivers/vmware/test_vmware_volumeops.py
index de467f22564..d61b0116e11 100644
--- a/cinder/tests/unit/volume/drivers/vmware/test_vmware_volumeops.py
+++ b/cinder/tests/unit/volume/drivers/vmware/test_vmware_volumeops.py
@@ -1184,9 +1184,32 @@ class VolumeOpsTestCase(test.TestCase):
     def test_clone_backing_with_empty_folder(self):
         self._test_clone_backing('linked', None)
 
+    def _create_controller_device(self, controller_type):
+        dev = mock.Mock()
+        dev.__class__.__name__ = controller_type
+        return dev
+
+    def test_get_controller(self):
+        disk = self._create_disk_device('foo.vmdk')
+        controller1 = self._create_controller_device(
+            volumeops.ControllerType.LSI_LOGIC)
+        controller2 = self._create_controller_device(
+            volumeops.ControllerType.PARA_VIRTUAL)
+        self.session.invoke_api.return_value = [disk, controller1, controller2]
+
+        backing = mock.sentinel.backing
+        ret = self.vops._get_controller(
+            backing, volumeops.VirtualDiskAdapterType.PARA_VIRTUAL)
+        self.assertEqual(controller2, ret)
+        self.session.invoke_api.assert_called_once_with(
+            vim_util, 'get_object_property', self.session.vim, backing,
+            'config.hardware.device')
+
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                '_get_controller', return_value=None)
     @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
                 '_create_specs_for_disk_add')
-    def test_attach_disk_to_backing(self, create_spec):
+    def test_attach_disk_to_backing(self, create_spec, get_controller):
         reconfig_spec = mock.Mock()
         self.session.vim.client.factory.create.return_value = reconfig_spec
         disk_add_config_specs = mock.Mock()
@@ -1204,6 +1227,7 @@ class VolumeOpsTestCase(test.TestCase):
                                          adapter_type, profile_id,
                                          vmdk_ds_file_path)
 
+        get_controller.assert_called_once_with(backing, adapter_type)
         self.assertEqual(disk_add_config_specs, reconfig_spec.deviceChange)
         create_spec.assert_called_once_with(
             size_in_kb, disk_type, adapter_type, profile_id,
@@ -1214,6 +1238,43 @@ class VolumeOpsTestCase(test.TestCase):
                                                         spec=reconfig_spec)
         self.session.wait_for_task.assert_called_once_with(task)
 
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                '_get_controller')
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                '_create_virtual_disk_config_spec')
+    def test_attach_disk_to_backing_existing_controller(
+            self, create_disk_spec, get_controller):
+        key = mock.sentinel.key
+        controller = mock.Mock(key=key)
+        get_controller.return_value = controller
+
+        reconfig_spec = mock.Mock()
+        self.session.vim.client.factory.create.return_value = reconfig_spec
+        disk_spec = mock.Mock()
+        create_disk_spec.return_value = disk_spec
+        task = mock.Mock()
+        self.session.invoke_api.return_value = task
+
+        backing = mock.Mock()
+        size_in_kb = units.Ki
+        disk_type = "thin"
+        adapter_type = "ide"
+        profile_id = mock.sentinel.profile_id
+        vmdk_ds_file_path = mock.sentinel.vmdk_ds_file_path
+        self.vops.attach_disk_to_backing(backing, size_in_kb, disk_type,
+                                         adapter_type, profile_id,
+                                         vmdk_ds_file_path)
+
+        get_controller.assert_called_once_with(backing, adapter_type)
+        self.assertEqual([disk_spec], reconfig_spec.deviceChange)
+        create_disk_spec.assert_called_once_with(
+            size_in_kb, disk_type, key, profile_id, vmdk_ds_file_path)
+        self.session.invoke_api.assert_called_once_with(self.session.vim,
+                                                        "ReconfigVM_Task",
+                                                        backing,
+                                                        spec=reconfig_spec)
+        self.session.wait_for_task.assert_called_once_with(task)
+
     def test_create_spec_for_disk_remove(self):
         disk_spec = mock.Mock()
         self.session.vim.client.factory.create.return_value = disk_spec
diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py
index de0cb9d1753..72ee32fc5bb 100644
--- a/cinder/volume/drivers/vmware/vmdk.py
+++ b/cinder/volume/drivers/vmware/vmdk.py
@@ -250,7 +250,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
     #         add storage profile ID to connection info
     #         support for revert-to-snapshot
     #         improve scalability of querying volumes in backend (bug 1600754)
-    VERSION = '3.0.0'
+    # 3.1.0 - support adapter type change using retype
+    VERSION = '3.1.0'
 
     # ThirdPartySystems wiki page
     CI_WIKI_NAME = "VMware_CI"
@@ -1555,6 +1556,20 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
                                              'new_name': tmp_name,
                                              'old_name': volume['name']})
 
+        adapter_type = self._get_adapter_type(volume)
+        new_adapter_type = self._get_extra_spec_adapter_type(new_type['id'])
+        if new_adapter_type != adapter_type:
+            LOG.debug("Changing volume: %(name)s adapter type from "
+                      "%(adapter_type)s to %(new_adapter_type)s.",
+                      {'name': volume['name'],
+                       'adapter_type': adapter_type,
+                       'new_adapter_type': new_adapter_type})
+            disk_device = self.volumeops._get_disk_device(backing)
+            self.volumeops.detach_disk_from_backing(backing, disk_device)
+            self.volumeops.attach_disk_to_backing(
+                backing, disk_device.capacityInKB, new_disk_type,
+                new_adapter_type, None, disk_device.backing.fileName)
+
         # Update the backing's storage profile if needed.
         if need_profile_change:
             LOG.debug("Backing: %(backing)s needs a profile change to:"
diff --git a/cinder/volume/drivers/vmware/volumeops.py b/cinder/volume/drivers/vmware/volumeops.py
index c059708b771..110c96116f5 100644
--- a/cinder/volume/drivers/vmware/volumeops.py
+++ b/cinder/volume/drivers/vmware/volumeops.py
@@ -1235,6 +1235,18 @@ class VMwareVolumeOps(object):
                   reconfig_task)
         self._session.wait_for_task(reconfig_task)
 
+    def _get_controller(self, backing, adapter_type):
+        devices = self._session.invoke_api(vim_util,
+                                           'get_object_property',
+                                           self._session.vim,
+                                           backing,
+                                           'config.hardware.device')
+
+        controller_type = ControllerType.get_controller_type(adapter_type)
+        for device in devices:
+            if device.__class__.__name__ == controller_type:
+                return device
+
     def attach_disk_to_backing(self, backing, size_in_kb, disk_type,
                                adapter_type, profile_id, vmdk_ds_file_path):
         """Attach an existing virtual disk to the backing VM.
@@ -1256,12 +1268,23 @@ class VMwareVolumeOps(object):
                    'adapter_type': adapter_type})
         cf = self._session.vim.client.factory
         reconfig_spec = cf.create('ns0:VirtualMachineConfigSpec')
-        specs = self._create_specs_for_disk_add(
-            size_in_kb,
-            disk_type,
-            adapter_type,
-            profile_id,
-            vmdk_ds_file_path=vmdk_ds_file_path)
+
+        controller = self._get_controller(backing, adapter_type)
+        if controller:
+            disk_spec = self._create_virtual_disk_config_spec(
+                size_in_kb,
+                disk_type,
+                controller.key,
+                profile_id,
+                vmdk_ds_file_path)
+            specs = [disk_spec]
+        else:
+            specs = self._create_specs_for_disk_add(
+                size_in_kb,
+                disk_type,
+                adapter_type,
+                profile_id,
+                vmdk_ds_file_path=vmdk_ds_file_path)
         reconfig_spec.deviceChange = specs
         self._reconfigure_backing(backing, reconfig_spec)
         LOG.debug("Backing VM: %s reconfigured with new disk.", backing)
diff --git a/releasenotes/notes/vmware_retype_adapter_type-dbd8935b8d3bcb1b.yaml b/releasenotes/notes/vmware_retype_adapter_type-dbd8935b8d3bcb1b.yaml
new file mode 100644
index 00000000000..21c40c8510e
--- /dev/null
+++ b/releasenotes/notes/vmware_retype_adapter_type-dbd8935b8d3bcb1b.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    VMware VMDK driver now supports changing adpater type using retype.
+    To change the adapter type, set ``vmware:adapter_type`` in the
+    new volume type.