]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMware: Relocate volume to compliant datastore
authorVipin Balachandran <vbala@vmware.com>
Wed, 27 Aug 2014 14:56:34 +0000 (20:26 +0530)
committerVipin Balachandran <vbala@vmware.com>
Thu, 25 Sep 2014 11:17:42 +0000 (16:47 +0530)
During attach to a nova instance, the backing VM corresponding to the
volume is relocated only if the nova instance's ESX host cannot access
the backing's current datastore. The storage profile is ignored and
the volume's virtual disk might end up in a non-compliant datastore.
This patch fixes the problem by checking storage profile compliance of
the current datastore.

Change-Id: I3865654e219c05dcec3aaab07c4cee0658fe181e
Closes-Bug: #1301348

cinder/tests/test_vmware_vmdk.py
cinder/tests/test_vmware_volumeops.py
cinder/volume/drivers/vmware/error_util.py
cinder/volume/drivers/vmware/vmdk.py
cinder/volume/drivers/vmware/volumeops.py

index b00868ffc1b25827221fb9463244a9227733c2ed..b1d25d48be6d88746ab5927238fd050f7b19c1f4 100644 (file)
@@ -375,30 +375,97 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         m.UnsetStubs()
         m.VerifyAll()
 
-    def test_init_conn_with_instance_and_backing(self):
-        """Test initialize_connection with instance and backing."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        m.StubOutWithMock(self._volumeops, 'get_backing')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        volume['id'] = 'volume_id'
-        volume['size'] = 1
-        connector = {'instance': 'my_instance'}
-        backing = FakeMor('VirtualMachine', 'my_back')
-        self._volumeops.get_backing(volume['name']).AndReturn(backing)
-        m.StubOutWithMock(self._volumeops, 'get_host')
-        host = FakeMor('HostSystem', 'my_host')
-        self._volumeops.get_host(mox.IgnoreArg()).AndReturn(host)
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_relocate_backing')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing')
+    def test_initialize_connection_with_instance_and_backing(
+            self, create_backing, relocate_backing, vops):
+        self._test_initialize_connection_with_instance_and_backing(
+            create_backing, relocate_backing, vops)
+
+    def _test_initialize_connection_with_instance_and_backing(
+            self, create_backing, relocate_backing, vops):
+        instance = mock.sentinel.instance
+        connector = {'instance': instance}
+
+        backing = mock.Mock(value=mock.sentinel.backing_value)
+        vops.get_backing.return_value = backing
 
-        m.ReplayAll()
+        host = mock.sentinel.host
+        vops.get_host.return_value = host
+
+        volume = {'name': 'vol-1', 'id': 1}
         conn_info = self._driver.initialize_connection(volume, connector)
-        self.assertEqual(conn_info['driver_volume_type'], 'vmdk')
-        self.assertEqual(conn_info['data']['volume'], 'my_back')
-        self.assertEqual(conn_info['data']['volume_id'], 'volume_id')
-        m.UnsetStubs()
-        m.VerifyAll()
+
+        relocate_backing.assert_called_once_with(volume, backing, host)
+        self.assertFalse(create_backing.called)
+
+        self.assertEqual('vmdk', conn_info['driver_volume_type'])
+        self.assertEqual(backing.value, conn_info['data']['volume'])
+        self.assertEqual(volume['id'],
+                         conn_info['data']['volume_id'])
+
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_relocate_backing')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing')
+    def test_initialize_connection_with_instance_and_no_backing(
+            self, create_backing, relocate_backing, vops):
+        self._test_initialize_connection_with_instance_and_no_backing(
+            create_backing, relocate_backing, vops)
+
+    def _test_initialize_connection_with_instance_and_no_backing(
+            self, create_backing, relocate_backing, vops):
+        instance = mock.sentinel.instance
+        connector = {'instance': instance}
+
+        vops.get_backing.return_value = None
+
+        host = mock.sentinel.host
+        vops.get_host.return_value = host
+
+        backing = mock.Mock(value=mock.sentinel.backing_value)
+        create_backing.return_value = backing
+
+        volume = {'name': 'vol-1', 'id': 1}
+        conn_info = self._driver.initialize_connection(volume, connector)
+
+        create_backing.assert_called_once_with(volume, host)
+        self.assertFalse(relocate_backing.called)
+
+        self.assertEqual('vmdk', conn_info['driver_volume_type'])
+        self.assertEqual(backing.value, conn_info['data']['volume'])
+        self.assertEqual(volume['id'],
+                         conn_info['data']['volume_id'])
+
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_relocate_backing')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
+    def test_initialize_connection_with_no_instance_and_no_backing(
+            self, create_backing_in_inventory, relocate_backing, vops):
+        self._test_initialize_connection_with_no_instance_and_no_backing(
+            create_backing_in_inventory, relocate_backing, vops)
+
+    def _test_initialize_connection_with_no_instance_and_no_backing(
+            self, create_backing_in_inventory, relocate_backing, vops):
+        vops.get_backing.return_value = None
+
+        host = mock.sentinel.host
+        vops.get_host.return_value = host
+
+        backing = mock.Mock(value=mock.sentinel.backing_value)
+        create_backing_in_inventory.return_value = backing
+
+        connector = {}
+        volume = {'name': 'vol-1', 'id': 1}
+        conn_info = self._driver.initialize_connection(volume, connector)
+
+        create_backing_in_inventory.assert_called_once_with(volume)
+        self.assertFalse(relocate_backing.called)
+
+        self.assertEqual('vmdk', conn_info['driver_volume_type'])
+        self.assertEqual(backing.value, conn_info['data']['volume'])
+        self.assertEqual(volume['id'],
+                         conn_info['data']['volume_id'])
 
     def test_get_volume_group_folder(self):
         """Test _get_volume_group_folder."""
@@ -529,71 +596,6 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
                           vmdk.VMwareEsxVmdkDriver._get_disk_type,
                           volume)
 
-    def test_init_conn_with_instance_no_backing(self):
-        """Test initialize_connection with instance and without backing."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        m.StubOutWithMock(self._volumeops, 'get_backing')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        volume['id'] = 'volume_id'
-        volume['size'] = 1
-        volume['volume_type_id'] = None
-        connector = {'instance': 'my_instance'}
-        self._volumeops.get_backing(volume['name'])
-        m.StubOutWithMock(self._volumeops, 'get_host')
-        host = FakeMor('HostSystem', 'my_host')
-        self._volumeops.get_host(mox.IgnoreArg()).AndReturn(host)
-        m.StubOutWithMock(self._volumeops, 'get_dss_rp')
-        resource_pool = FakeMor('ResourcePool', 'my_rp')
-        datastores = [FakeMor('Datastore', 'my_ds')]
-        self._volumeops.get_dss_rp(host).AndReturn((datastores, resource_pool))
-        m.StubOutWithMock(self._driver, '_get_folder_ds_summary')
-        folder = FakeMor('Folder', 'my_fol')
-        summary = FakeDatastoreSummary(1, 1)
-        self._driver._get_folder_ds_summary(volume, resource_pool,
-                                            datastores).AndReturn((folder,
-                                                                   summary))
-        backing = FakeMor('VirtualMachine', 'my_back')
-        m.StubOutWithMock(self._volumeops, 'create_backing')
-        self._volumeops.create_backing(volume['name'],
-                                       volume['size'] * units.Mi,
-                                       mox.IgnoreArg(), folder,
-                                       resource_pool, host,
-                                       mox.IgnoreArg(),
-                                       mox.IgnoreArg(),
-                                       mox.IgnoreArg()).AndReturn(backing)
-
-        m.ReplayAll()
-        conn_info = self._driver.initialize_connection(volume, connector)
-        self.assertEqual(conn_info['driver_volume_type'], 'vmdk')
-        self.assertEqual(conn_info['data']['volume'], 'my_back')
-        self.assertEqual(conn_info['data']['volume_id'], 'volume_id')
-        m.UnsetStubs()
-        m.VerifyAll()
-
-    def test_init_conn_without_instance(self):
-        """Test initialize_connection without instance and a backing."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        m.StubOutWithMock(self._volumeops, 'get_backing')
-        backing = FakeMor('VirtualMachine', 'my_back')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        volume['id'] = 'volume_id'
-        connector = {}
-        self._volumeops.get_backing(volume['name']).AndReturn(backing)
-
-        m.ReplayAll()
-        conn_info = self._driver.initialize_connection(volume, connector)
-        self.assertEqual(conn_info['driver_volume_type'], 'vmdk')
-        self.assertEqual(conn_info['data']['volume'], 'my_back')
-        self.assertEqual(conn_info['data']['volume_id'], 'volume_id')
-        m.UnsetStubs()
-        m.VerifyAll()
-
     def test_create_snapshot_without_backing(self):
         """Test vmdk.create_snapshot without backing."""
         m = self.mox
@@ -2003,37 +2005,29 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         self._test_create_backing_by_copying(volumeops, create_backing,
                                              extend_virtual_disk)
 
-    def test_init_conn_with_instance_and_backing(self):
-        """Test initialize_connection with instance and backing."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        m.StubOutWithMock(self._volumeops, 'get_backing')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        volume['id'] = 'volume_id'
-        volume['size'] = 1
-        connector = {'instance': 'my_instance'}
-        backing = FakeMor('VirtualMachine', 'my_back')
-        self._volumeops.get_backing(volume['name']).AndReturn(backing)
-        m.StubOutWithMock(self._volumeops, 'get_host')
-        host = FakeMor('HostSystem', 'my_host')
-        self._volumeops.get_host(mox.IgnoreArg()).AndReturn(host)
-        datastore = FakeMor('Datastore', 'my_ds')
-        resource_pool = FakeMor('ResourcePool', 'my_rp')
-        m.StubOutWithMock(self._volumeops, 'get_dss_rp')
-        self._volumeops.get_dss_rp(host).AndReturn(([datastore],
-                                                    resource_pool))
-        m.StubOutWithMock(self._volumeops, 'get_datastore')
-        self._volumeops.get_datastore(backing).AndReturn(datastore)
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_relocate_backing')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing')
+    def test_initialize_connection_with_instance_and_backing(
+            self, create_backing, relocate_backing, vops):
+        self._test_initialize_connection_with_instance_and_backing(
+            create_backing, relocate_backing, vops)
 
-        m.ReplayAll()
-        conn_info = self._driver.initialize_connection(volume, connector)
-        self.assertEqual(conn_info['driver_volume_type'], 'vmdk')
-        self.assertEqual(conn_info['data']['volume'], 'my_back')
-        self.assertEqual(conn_info['data']['volume_id'], 'volume_id')
-        m.UnsetStubs()
-        m.VerifyAll()
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_relocate_backing')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing')
+    def test_initialize_connection_with_instance_and_no_backing(
+            self, create_backing, relocate_backing, vops):
+        self._test_initialize_connection_with_instance_and_no_backing(
+            create_backing, relocate_backing, vops)
+
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_relocate_backing')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
+    def test_initialize_connection_with_no_instance_and_no_backing(
+            self, create_backing_in_inventory, relocate_backing, vops):
+        self._test_initialize_connection_with_no_instance_and_no_backing(
+            create_backing_in_inventory, relocate_backing, vops)
 
     def test_get_volume_group_folder(self):
         """Test _get_volume_group_folder."""
@@ -2052,50 +2046,6 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         m.UnsetStubs()
         m.VerifyAll()
 
-    def test_init_conn_with_instance_and_backing_and_relocation(self):
-        """Test initialize_connection with backing being relocated."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        m.StubOutWithMock(self._volumeops, 'get_backing')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        volume['id'] = 'volume_id'
-        volume['size'] = 1
-        connector = {'instance': 'my_instance'}
-        backing = FakeMor('VirtualMachine', 'my_back')
-        self._volumeops.get_backing(volume['name']).AndReturn(backing)
-        m.StubOutWithMock(self._volumeops, 'get_host')
-        host = FakeMor('HostSystem', 'my_host')
-        self._volumeops.get_host(mox.IgnoreArg()).AndReturn(host)
-        datastore1 = FakeMor('Datastore', 'my_ds_1')
-        datastore2 = FakeMor('Datastore', 'my_ds_2')
-        resource_pool = FakeMor('ResourcePool', 'my_rp')
-        m.StubOutWithMock(self._volumeops, 'get_dss_rp')
-        self._volumeops.get_dss_rp(host).AndReturn(([datastore1],
-                                                    resource_pool))
-        m.StubOutWithMock(self._volumeops, 'get_datastore')
-        self._volumeops.get_datastore(backing).AndReturn(datastore2)
-        m.StubOutWithMock(self._driver, '_get_folder_ds_summary')
-        folder = FakeMor('Folder', 'my_fol')
-        summary = FakeDatastoreSummary(1, 1, datastore1)
-        self._driver._get_folder_ds_summary(volume, resource_pool,
-                                            [datastore1]).AndReturn((folder,
-                                                                     summary))
-        m.StubOutWithMock(self._volumeops, 'relocate_backing')
-        self._volumeops.relocate_backing(backing, datastore1,
-                                         resource_pool, host)
-        m.StubOutWithMock(self._volumeops, 'move_backing_to_folder')
-        self._volumeops.move_backing_to_folder(backing, folder)
-
-        m.ReplayAll()
-        conn_info = self._driver.initialize_connection(volume, connector)
-        self.assertEqual(conn_info['driver_volume_type'], 'vmdk')
-        self.assertEqual(conn_info['data']['volume'], 'my_back')
-        self.assertEqual(conn_info['data']['volume_id'], 'volume_id')
-        m.UnsetStubs()
-        m.VerifyAll()
-
     @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
     @mock.patch.object(VMDK_DRIVER, 'volumeops')
     def test_clone_backing_linked(self, volume_ops, _extend_vmdk_virtual_disk):
@@ -2691,6 +2641,86 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
             close.assert_called_once_with(fd)
         delete_if_exists.assert_called_once_with(tmp)
 
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, 'ds_sel')
+    def test_relocate_backing_nop(self, ds_sel, vops):
+        volume = {'name': 'vol-1', 'size': 1}
+
+        datastore = mock.sentinel.datastore
+        vops.get_datastore.return_value = datastore
+
+        profile = mock.sentinel.profile
+        vops.get_profile.return_value = profile
+
+        vops.is_datastore_accessible.return_value = True
+        ds_sel.is_datastore_compliant.return_value = True
+
+        backing = mock.sentinel.backing
+        host = mock.sentinel.host
+        self._driver._relocate_backing(volume, backing, host)
+
+        vops.is_datastore_accessible.assert_called_once_with(datastore, host)
+        ds_sel.is_datastore_compliant.assert_called_once_with(datastore,
+                                                              profile)
+        self.assertFalse(vops.relocate_backing.called)
+
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, 'ds_sel')
+    def test_relocate_backing_with_no_datastore(
+            self, ds_sel, vops):
+        volume = {'name': 'vol-1', 'size': 1}
+
+        profile = mock.sentinel.profile
+        vops.get_profile.return_value = profile
+
+        vops.is_datastore_accessible.return_value = True
+        ds_sel.is_datastore_compliant.return_value = False
+
+        ds_sel.select_datastore.return_value = []
+
+        backing = mock.sentinel.backing
+        host = mock.sentinel.host
+
+        self.assertRaises(error_util.NoValidDatastoreException,
+                          self._driver._relocate_backing,
+                          volume,
+                          backing,
+                          host)
+        ds_sel.select_datastore.assert_called_once_with(
+            {hub.DatastoreSelector.SIZE_BYTES: volume['size'] * units.Gi,
+             hub.DatastoreSelector.PROFILE_NAME: profile}, hosts=[host])
+        self.assertFalse(vops.relocate_backing.called)
+
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    @mock.patch.object(VMDK_DRIVER, '_get_volume_group_folder')
+    @mock.patch.object(VMDK_DRIVER, 'ds_sel')
+    def test_relocate_backing(
+            self, ds_sel, get_volume_group_folder, vops):
+        volume = {'name': 'vol-1', 'size': 1}
+
+        vops.is_datastore_accessible.return_value = False
+        ds_sel.is_datastore_compliant.return_value = True
+
+        backing = mock.sentinel.backing
+        host = mock.sentinel.host
+
+        rp = mock.sentinel.rp
+        datastore = mock.sentinel.datastore
+        summary = mock.Mock(datastore=datastore)
+        ds_sel.select_datastore.return_value = (host, rp, summary)
+
+        folder = mock.sentinel.folder
+        get_volume_group_folder.return_value = folder
+
+        self._driver._relocate_backing(volume, backing, host)
+
+        vops.relocate_backing.assert_called_once_with(backing,
+                                                      datastore,
+                                                      rp,
+                                                      host)
+        vops.move_backing_to_folder.assert_called_once_with(backing,
+                                                            folder)
+
 
 class ImageDiskTypeTest(test.TestCase):
     """Unit tests for ImageDiskType."""
index 0817fb865ab5404543eebd1c8958dd775103568d..3ace853caa41622451cbd558b88fcf4c5ae71c95 100644 (file)
@@ -237,6 +237,30 @@ class VolumeOpsTestCase(test.TestCase):
             hosts = self.vops.get_connected_hosts(datastore)
             self.assertEqual([], hosts)
 
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                'get_connected_hosts')
+    def test_is_datastore_accessible(self, get_connected_hosts):
+        host_1 = mock.Mock(value=mock.sentinel.host_1)
+        host_2 = mock.Mock(value=mock.sentinel.host_2)
+        get_connected_hosts.return_value = [host_1, host_2]
+
+        ds = mock.sentinel.datastore
+        host = mock.Mock(value=mock.sentinel.host_1)
+        self.assertTrue(self.vops.is_datastore_accessible(ds, host))
+        get_connected_hosts.assert_called_once_with(ds)
+
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                'get_connected_hosts')
+    def test_is_datastore_accessible_with_inaccessible(self,
+                                                       get_connected_hosts):
+        host_1 = mock.Mock(value=mock.sentinel.host_1)
+        get_connected_hosts.return_value = [host_1]
+
+        ds = mock.sentinel.datastore
+        host = mock.Mock(value=mock.sentinel.host_2)
+        self.assertFalse(self.vops.is_datastore_accessible(ds, host))
+        get_connected_hosts.assert_called_once_with(ds)
+
     def test_is_valid(self):
         with mock.patch.object(self.vops, 'get_summary') as get_summary:
             summary = mock.Mock(spec=object)
@@ -1229,6 +1253,52 @@ class VolumeOpsTestCase(test.TestCase):
                                            datacenter=dc_ref)
         self.session.wait_for_task.assert_called_once_with(task)
 
+    def test_get_profile(self):
+        server_obj = mock.Mock()
+        self.session.pbm.client.factory.create.return_value = server_obj
+
+        profile_ids = [mock.sentinel.profile_id]
+        profile_name = mock.sentinel.profile_name
+        profile = mock.Mock()
+        profile.name = profile_name
+        self.session.invoke_api.side_effect = [profile_ids, [profile]]
+
+        value = mock.sentinel.value
+        backing = mock.Mock(value=value)
+        self.assertEqual(profile_name, self.vops.get_profile(backing))
+
+        pbm = self.session.pbm
+        profile_manager = pbm.service_content.profileManager
+        exp_calls = [mock.call(pbm, 'PbmQueryAssociatedProfile',
+                               profile_manager, entity=server_obj),
+                     mock.call(pbm, 'PbmRetrieveContent', profile_manager,
+                               profileIds=profile_ids)]
+        self.assertEqual(exp_calls, self.session.invoke_api.call_args_list)
+
+        self.assertEqual(value, server_obj.key)
+        self.assertEqual('virtualMachine', server_obj.objectType)
+        self.session.invoke_api.side_effect = None
+
+    def test_get_profile_with_no_profile(self):
+        server_obj = mock.Mock()
+        self.session.pbm.client.factory.create.return_value = server_obj
+
+        self.session.invoke_api.side_effect = [[]]
+
+        value = mock.sentinel.value
+        backing = mock.Mock(value=value)
+        self.assertIsNone(self.vops.get_profile(backing))
+
+        pbm = self.session.pbm
+        profile_manager = pbm.service_content.profileManager
+        exp_calls = [mock.call(pbm, 'PbmQueryAssociatedProfile',
+                               profile_manager, entity=server_obj)]
+        self.assertEqual(exp_calls, self.session.invoke_api.call_args_list)
+
+        self.assertEqual(value, server_obj.key)
+        self.assertEqual('virtualMachine', server_obj.objectType)
+        self.session.invoke_api.side_effect = None
+
     def test_extend_virtual_disk(self):
         """Test volumeops.extend_virtual_disk."""
         task = mock.sentinel.task
index 5696bc700bd734550b3bc3faa0788e13c3196bad..1790d019771100010187eecd69e9e538b2a5c00d 100644 (file)
@@ -93,3 +93,8 @@ class VirtualDiskNotFoundException(VMwareDriverException):
 class ProfileNotFoundException(VMwareDriverException):
     """Thrown when the given storage profile cannot be found."""
     message = _("Storage profile: %(storage_profile)s not found.")
+
+
+class NoValidDatastoreException(VMwareDriverException):
+    """Thrown when there are no valid datastores."""
+    message = _("There are no valid datastores.")
index b8f48ec517d3c82a024b4791f4e74e0453d49c05..c95056721c030919c4288d99edfbdeef225c9b0e 100644 (file)
@@ -1916,38 +1916,52 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
         return self.volumeops.create_folder(vm_folder, volume_folder)
 
     def _relocate_backing(self, volume, backing, host):
-        """Relocate volume backing under host and move to volume_group folder.
+        """Relocate volume backing to a datastore accessible to the given host.
 
-        If the volume backing is on a datastore that is visible to the host,
-        then need not do any operation.
+        The backing is not relocated if the current datastore is already
+        accessible to the host and compliant with the backing's storage
+        profile.
 
-        :param volume: volume to be relocated
+        :param volume: Volume to be relocated
         :param backing: Reference to the backing
         :param host: Reference to the host
         """
-        # Check if volume's datastore is visible to host managing
-        # the instance
-        (datastores, resource_pool) = self.volumeops.get_dss_rp(host)
+        # Check if current datastore is visible to host managing
+        # the instance and compliant with the storage profile.
         datastore = self.volumeops.get_datastore(backing)
-
-        visible_to_host = False
-        for _datastore in datastores:
-            if _datastore.value == datastore.value:
-                visible_to_host = True
-                break
-        if visible_to_host:
+        backing_profile = self.volumeops.get_profile(backing)
+        if (self.volumeops.is_datastore_accessible(datastore, host) and
+                self.ds_sel.is_datastore_compliant(datastore,
+                                                   backing_profile)):
+            LOG.debug("Datastore: %(datastore)s of backing: %(backing)s is "
+                      "already accessible to instance's host: %(host)s and "
+                      "compliant with storage profile: %(profile)s.",
+                      {'backing': backing,
+                       'datastore': datastore,
+                       'host': host,
+                       'profile': backing_profile})
             return
 
-        # The volume's backing is on a datastore that is not visible to the
-        # host managing the instance. We relocate the volume's backing.
+        # We need to relocate the backing to an accessible and profile
+        # compliant datastore.
+        req = {}
+        req[hub.DatastoreSelector.SIZE_BYTES] = (volume['size'] *
+                                                 units.Gi)
+        req[hub.DatastoreSelector.PROFILE_NAME] = backing_profile
+
+        # Select datastore satisfying the requirements.
+        best_candidate = self.ds_sel.select_datastore(req, hosts=[host])
+        if not best_candidate:
+            # No candidate datastore to relocate.
+            msg = _("There are no datastores matching volume requirements;"
+                    " can't relocate volume: %s.") % volume['name']
+            LOG.error(msg)
+            raise error_util.NoValidDatastoreException(msg)
+
+        (host, resource_pool, summary) = best_candidate
+        dc = self.volumeops.get_dc(resource_pool)
+        folder = self._get_volume_group_folder(dc)
 
-        # Pick a folder and datastore to relocate volume backing to
-        (folder, summary) = self._get_folder_ds_summary(volume,
-                                                        resource_pool,
-                                                        datastores)
-        LOG.info(_("Relocating volume: %(backing)s to %(ds)s and %(rp)s.") %
-                 {'backing': backing, 'ds': summary, 'rp': resource_pool})
-        # Relocate the backing to the datastore and folder
         self.volumeops.relocate_backing(backing, summary.datastore,
                                         resource_pool, host)
         self.volumeops.move_backing_to_folder(backing, folder)
index 4fb4a3b12b0ff904bb6c252695e7ff279f2fc1b9..307b2f33af745d6dd6663aca6241002c69919d80 100644 (file)
@@ -386,6 +386,15 @@ class VMwareVolumeOps(object):
 
         return connected_hosts
 
+    def is_datastore_accessible(self, datastore, host):
+        """Check if the datastore is accessible to the given host.
+
+        :param datastore: datastore reference
+        :return: True if the datastore is accessible
+        """
+        hosts = self.get_connected_hosts(datastore)
+        return host.value in [host_ref.value for host_ref in hosts]
+
     def _in_maintenance(self, summary):
         """Check if a datastore is entering maintenance or in maintenance.
 
@@ -1379,3 +1388,27 @@ class VMwareVolumeOps(object):
                                                  profile=profile_id)
         LOG.debug("Filtered hubs: %s", filtered_hubs)
         return filtered_hubs
+
+    def get_profile(self, backing):
+        """Query storage profile associated with the given backing.
+
+        :param backing: backing reference
+        :return: profile name
+        """
+        pbm = self._session.pbm
+        profile_manager = pbm.service_content.profileManager
+
+        object_ref = pbm.client.factory.create('ns0:PbmServerObjectRef')
+        object_ref.key = backing.value
+        object_ref.objectType = 'virtualMachine'
+
+        profile_ids = self._session.invoke_api(pbm,
+                                               'PbmQueryAssociatedProfile',
+                                               profile_manager,
+                                               entity=object_ref)
+        if profile_ids:
+            profiles = self._session.invoke_api(pbm,
+                                                'PbmRetrieveContent',
+                                                profile_manager,
+                                                profileIds=profile_ids)
+            return profiles[0].name