Coverage for nova/virt/vmwareapi/volumeops.py: 0%
352 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
2# Copyright (c) 2012 VMware, Inc.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""
17Management class for Storage-related functions (attach, detach, etc).
18"""
20from oslo_log import log as logging
21from oslo_vmware import exceptions as oslo_vmw_exceptions
22from oslo_vmware import vim_util as vutil
24from nova.compute import power_state
25import nova.conf
26from nova import exception
27from nova.i18n import _
28from nova.virt.vmwareapi import constants
29from nova.virt.vmwareapi import session
30from nova.virt.vmwareapi import vm_util
32CONF = nova.conf.CONF
33LOG = logging.getLogger(__name__)
36class VolumeMoRefProxy(session.StableMoRefProxy):
37 def __init__(self, connection_info_data):
38 volume_ref_value = connection_info_data.get('volume')
39 ref = None
40 if volume_ref_value:
41 ref = vutil.get_moref(volume_ref_value, 'VirtualMachine')
42 super(VolumeMoRefProxy, self).__init__(ref)
43 self._connection_info_data = connection_info_data
45 def fetch_moref(self, session):
46 volume_id = self._connection_info_data.get('volume_id')
47 if not volume_id:
48 volume_id = self._connection_info_data.get('name')
49 if volume_id:
50 self.moref = vm_util._get_vm_ref_from_vm_uuid(session, volume_id)
53class VMwareVolumeOps(object):
54 """Management class for Volume-related tasks."""
56 def __init__(self, session, cluster=None):
57 self._session = session
58 self._cluster = cluster
60 def attach_disk_to_vm(self, vm_ref, instance,
61 adapter_type, disk_type, vmdk_path=None,
62 disk_size=None, linked_clone=False,
63 device_name=None, disk_io_limits=None):
64 """Attach disk to VM by reconfiguration."""
65 instance_name = instance.name
66 client_factory = self._session.vim.client.factory
67 devices = vm_util.get_hardware_devices(self._session, vm_ref)
68 (controller_key, unit_number,
69 controller_spec) = vm_util.allocate_controller_key_and_unit_number(
70 client_factory,
71 devices,
72 adapter_type)
74 vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_spec(
75 client_factory, disk_type, vmdk_path,
76 disk_size, linked_clone, controller_key,
77 unit_number, device_name, disk_io_limits)
78 if controller_spec:
79 vmdk_attach_config_spec.deviceChange.append(controller_spec)
81 LOG.debug("Reconfiguring VM instance %(instance_name)s to attach "
82 "disk %(vmdk_path)s or device %(device_name)s with type "
83 "%(disk_type)s",
84 {'instance_name': instance_name, 'vmdk_path': vmdk_path,
85 'device_name': device_name, 'disk_type': disk_type},
86 instance=instance)
87 vm_util.reconfigure_vm(self._session, vm_ref, vmdk_attach_config_spec)
88 LOG.debug("Reconfigured VM instance %(instance_name)s to attach "
89 "disk %(vmdk_path)s or device %(device_name)s with type "
90 "%(disk_type)s",
91 {'instance_name': instance_name, 'vmdk_path': vmdk_path,
92 'device_name': device_name, 'disk_type': disk_type},
93 instance=instance)
95 def _update_volume_details(self, vm_ref, volume_uuid, device_uuid):
96 # Store the uuid of the volume_device
97 volume_option = 'volume-%s' % volume_uuid
98 extra_opts = {volume_option: device_uuid}
100 client_factory = self._session.vim.client.factory
101 extra_config_specs = vm_util.get_vm_extra_config_spec(
102 client_factory, extra_opts)
103 vm_util.reconfigure_vm(self._session, vm_ref, extra_config_specs)
105 def _get_volume_uuid(self, vm_ref, volume_uuid):
106 prop = 'config.extraConfig["volume-%s"]' % volume_uuid
107 opt_val = self._session._call_method(vutil,
108 'get_object_property',
109 vm_ref,
110 prop)
111 if opt_val is not None:
112 return opt_val.value
114 def detach_disk_from_vm(self, vm_ref, instance, device,
115 destroy_disk=False):
116 """Detach disk from VM by reconfiguration."""
117 instance_name = instance.name
118 client_factory = self._session.vim.client.factory
119 vmdk_detach_config_spec = vm_util.get_vmdk_detach_config_spec(
120 client_factory, device, destroy_disk)
121 disk_key = device.key
122 LOG.debug("Reconfiguring VM instance %(instance_name)s to detach "
123 "disk %(disk_key)s",
124 {'instance_name': instance_name, 'disk_key': disk_key},
125 instance=instance)
126 vm_util.reconfigure_vm(self._session, vm_ref, vmdk_detach_config_spec)
127 LOG.debug("Reconfigured VM instance %(instance_name)s to detach "
128 "disk %(disk_key)s",
129 {'instance_name': instance_name, 'disk_key': disk_key},
130 instance=instance)
132 def _iscsi_get_target(self, data):
133 """Return the iSCSI Target given a volume info."""
134 target_portal = data['target_portal']
135 target_iqn = data['target_iqn']
136 host_mor = vm_util.get_host_ref(self._session, self._cluster)
138 lst_properties = ["config.storageDevice.hostBusAdapter",
139 "config.storageDevice.scsiTopology",
140 "config.storageDevice.scsiLun"]
141 prop_dict = self._session._call_method(vutil,
142 "get_object_properties_dict",
143 host_mor,
144 lst_properties)
145 result = (None, None)
146 hbas_ret = None
147 scsi_topology = None
148 scsi_lun_ret = None
149 if prop_dict:
150 hbas_ret = prop_dict.get('config.storageDevice.hostBusAdapter')
151 scsi_topology = prop_dict.get('config.storageDevice.scsiTopology')
152 scsi_lun_ret = prop_dict.get('config.storageDevice.scsiLun')
154 # Meaning there are no host bus adapters on the host
155 if hbas_ret is None:
156 return result
157 host_hbas = hbas_ret.HostHostBusAdapter
158 if not host_hbas:
159 return result
160 for hba in host_hbas:
161 if hba.__class__.__name__ == 'HostInternetScsiHba':
162 hba_key = hba.key
163 break
164 else:
165 return result
167 if scsi_topology is None:
168 return result
169 host_adapters = scsi_topology.adapter
170 if not host_adapters:
171 return result
172 scsi_lun_key = None
173 for adapter in host_adapters:
174 if adapter.adapter == hba_key:
175 if not getattr(adapter, 'target', None):
176 return result
177 for target in adapter.target:
178 if (getattr(target.transport, 'address', None) and
179 target.transport.address[0] == target_portal and
180 target.transport.iScsiName == target_iqn):
181 if not target.lun:
182 return result
183 for lun in target.lun:
184 if 'host.ScsiDisk' in lun.scsiLun:
185 scsi_lun_key = lun.scsiLun
186 break
187 break
188 break
190 if scsi_lun_key is None:
191 return result
193 if scsi_lun_ret is None:
194 return result
195 host_scsi_luns = scsi_lun_ret.ScsiLun
196 if not host_scsi_luns:
197 return result
198 for scsi_lun in host_scsi_luns:
199 if scsi_lun.key == scsi_lun_key:
200 return (scsi_lun.deviceName, scsi_lun.uuid)
202 return result
204 def _iscsi_add_send_target_host(self, storage_system_mor, hba_device,
205 target_portal):
206 """Adds the iscsi host to send target host list."""
207 client_factory = self._session.vim.client.factory
208 send_tgt = client_factory.create('ns0:HostInternetScsiHbaSendTarget')
209 (send_tgt.address, send_tgt.port) = target_portal.split(':')
210 LOG.debug("Adding iSCSI host %s to send targets", send_tgt.address)
211 self._session._call_method(
212 self._session.vim, "AddInternetScsiSendTargets",
213 storage_system_mor, iScsiHbaDevice=hba_device, targets=[send_tgt])
215 def _iscsi_rescan_hba(self, target_portal):
216 """Rescan the iSCSI HBA to discover iSCSI targets."""
217 host_mor = vm_util.get_host_ref(self._session, self._cluster)
218 storage_system_mor = self._session._call_method(
219 vutil,
220 "get_object_property",
221 host_mor,
222 "configManager.storageSystem")
223 hbas_ret = self._session._call_method(
224 vutil,
225 "get_object_property",
226 storage_system_mor,
227 "storageDeviceInfo.hostBusAdapter")
228 # Meaning there are no host bus adapters on the host
229 if hbas_ret is None:
230 return
231 host_hbas = hbas_ret.HostHostBusAdapter
232 if not host_hbas:
233 return
234 for hba in host_hbas:
235 if hba.__class__.__name__ == 'HostInternetScsiHba':
236 hba_device = hba.device
237 if target_portal:
238 # Check if iscsi host is already in the send target host
239 # list
240 send_targets = getattr(hba, 'configuredSendTarget', [])
241 send_tgt_portals = ['%s:%s' % (s.address, s.port) for s in
242 send_targets]
243 if target_portal not in send_tgt_portals:
244 self._iscsi_add_send_target_host(storage_system_mor,
245 hba_device,
246 target_portal)
247 break
248 else:
249 return
250 LOG.debug("Rescanning HBA %s", hba_device)
251 self._session._call_method(self._session.vim,
252 "RescanHba", storage_system_mor, hbaDevice=hba_device)
253 LOG.debug("Rescanned HBA %s ", hba_device)
255 def _iscsi_discover_target(self, data):
256 """Get iSCSI target, rescanning the HBA if necessary."""
257 target_portal = data['target_portal']
258 target_iqn = data['target_iqn']
259 LOG.debug("Discovering iSCSI target %(target_iqn)s from "
260 "%(target_portal)s.",
261 {'target_iqn': target_iqn, 'target_portal': target_portal})
262 device_name, uuid = self._iscsi_get_target(data)
263 if device_name:
264 LOG.debug("Storage target found. No need to discover")
265 return (device_name, uuid)
267 # Rescan iSCSI HBA with iscsi target host
268 self._iscsi_rescan_hba(target_portal)
270 # Find iSCSI Target again
271 device_name, uuid = self._iscsi_get_target(data)
272 if device_name:
273 LOG.debug("Discovered iSCSI target %(target_iqn)s from "
274 "%(target_portal)s.",
275 {'target_iqn': target_iqn,
276 'target_portal': target_portal})
277 else:
278 LOG.debug("Unable to discovered iSCSI target %(target_iqn)s "
279 "from %(target_portal)s.",
280 {'target_iqn': target_iqn,
281 'target_portal': target_portal})
282 return (device_name, uuid)
284 def _iscsi_get_host_iqn(self, instance):
285 """Return the host iSCSI IQN."""
286 try:
287 host_mor = vm_util.get_host_ref_for_vm(self._session, instance)
288 except exception.InstanceNotFound:
289 host_mor = vm_util.get_host_ref(self._session, self._cluster)
291 hbas_ret = self._session._call_method(
292 vutil,
293 "get_object_property",
294 host_mor,
295 "config.storageDevice.hostBusAdapter")
297 # Meaning there are no host bus adapters on the host
298 if hbas_ret is None:
299 return
300 host_hbas = hbas_ret.HostHostBusAdapter
301 if not host_hbas:
302 return
303 for hba in host_hbas:
304 if hba.__class__.__name__ == 'HostInternetScsiHba':
305 return hba.iScsiName
307 def get_volume_connector(self, instance):
308 """Return volume connector information."""
309 try:
310 vm_ref = vm_util.get_vm_ref(self._session, instance)
311 except exception.InstanceNotFound:
312 vm_ref = None
313 iqn = self._iscsi_get_host_iqn(instance)
314 connector = {'ip': CONF.vmware.host_ip,
315 'initiator': iqn,
316 'host': CONF.vmware.host_ip}
317 if vm_ref:
318 connector['instance'] = vutil.get_moref_value(vm_ref)
319 return connector
321 @staticmethod
322 def _get_volume_ref(connection_info_data):
323 """Get the volume moref from the "data" field in connection_info ."""
324 return VolumeMoRefProxy(connection_info_data)
326 def _get_vmdk_base_volume_device(self, volume_ref):
327 # Get the vmdk file name that the VM is pointing to
328 hardware_devices = vm_util.get_hardware_devices(self._session,
329 volume_ref)
330 return vm_util.get_vmdk_volume_disk(hardware_devices)
332 def _attach_volume_vmdk(self, connection_info, instance,
333 adapter_type=None):
334 """Attach vmdk volume storage to VM instance."""
335 vm_ref = vm_util.get_vm_ref(self._session, instance)
336 LOG.debug("_attach_volume_vmdk: %s", connection_info,
337 instance=instance)
338 data = connection_info['data']
339 volume_ref = self._get_volume_ref(data)
341 # Get details required for adding disk device such as
342 # adapter_type, disk_type
343 vmdk = vm_util.get_vmdk_info(self._session, volume_ref)
344 adapter_type = adapter_type or vmdk.adapter_type
346 # IDE does not support disk hotplug
347 if adapter_type == constants.ADAPTER_TYPE_IDE:
348 state = vm_util.get_vm_state(self._session, instance)
349 if state != power_state.SHUTDOWN:
350 raise exception.Invalid(_('%s does not support disk '
351 'hotplug.') % adapter_type)
353 # Attach the disk to virtual machine instance
354 self.attach_disk_to_vm(vm_ref, instance, adapter_type, vmdk.disk_type,
355 vmdk_path=vmdk.path)
357 # Store the uuid of the volume_device
358 self._update_volume_details(vm_ref, data['volume_id'],
359 vmdk.device.backing.uuid)
361 LOG.debug("Attached VMDK: %s", connection_info, instance=instance)
363 def _attach_volume_iscsi(self, connection_info, instance,
364 adapter_type=None):
365 """Attach iscsi volume storage to VM instance."""
366 vm_ref = vm_util.get_vm_ref(self._session, instance)
367 # Attach Volume to VM
368 LOG.debug("_attach_volume_iscsi: %s", connection_info,
369 instance=instance)
371 data = connection_info['data']
373 # Discover iSCSI Target
374 device_name = self._iscsi_discover_target(data)[0]
375 if device_name is None:
376 raise exception.StorageError(
377 reason=_("Unable to find iSCSI Target"))
378 if adapter_type is None:
379 # Get the vmdk file name that the VM is pointing to
380 hardware_devices = vm_util.get_hardware_devices(self._session,
381 vm_ref)
382 adapter_type = vm_util.get_scsi_adapter_type(hardware_devices)
384 self.attach_disk_to_vm(vm_ref, instance,
385 adapter_type, 'rdmp',
386 device_name=device_name)
387 LOG.debug("Attached ISCSI: %s", connection_info, instance=instance)
389 def _get_controller_key_and_unit(self, vm_ref, adapter_type):
390 LOG.debug("_get_controller_key_and_unit vm: %(vm_ref)s, adapter: "
391 "%(adapter)s.",
392 {'vm_ref': vm_ref, 'adapter': adapter_type})
393 client_factory = self._session.vim.client.factory
394 devices = self._session._call_method(vutil,
395 "get_object_property",
396 vm_ref,
397 "config.hardware.device")
398 return vm_util.allocate_controller_key_and_unit_number(
399 client_factory, devices, adapter_type)
401 def _attach_fcd(self, vm_ref, adapter_type, fcd_id, ds_ref_val):
402 (controller_key, unit_number,
403 controller_spec) = self._get_controller_key_and_unit(
404 vm_ref, adapter_type)
406 if controller_spec:
407 # No controller available to attach, create one first.
408 config_spec = self._session.vim.client.factory.create(
409 'ns0:VirtualMachineConfigSpec')
410 config_spec.deviceChange = [controller_spec]
411 vm_util.reconfigure_vm(self._session, vm_ref, config_spec)
412 (controller_key, unit_number,
413 controller_spec) = self._get_controller_key_and_unit(
414 vm_ref, adapter_type)
416 vm_util.attach_fcd(
417 self._session, vm_ref, fcd_id, ds_ref_val, controller_key,
418 unit_number)
420 def _attach_volume_fcd(self, connection_info, instance):
421 """Attach fcd volume storage to VM instance."""
422 LOG.debug("_attach_volume_fcd: %s", connection_info, instance=instance)
423 vm_ref = vm_util.get_vm_ref(self._session, instance)
424 data = connection_info['data']
425 adapter_type = data['adapter_type']
427 if adapter_type == constants.ADAPTER_TYPE_IDE:
428 state = vm_util.get_vm_state(self._session, instance)
429 if state != power_state.SHUTDOWN:
430 raise exception.Invalid(_('%s does not support disk '
431 'hotplug.') % adapter_type)
433 self._attach_fcd(vm_ref, adapter_type, data['id'], data['ds_ref_val'])
434 LOG.debug("Attached fcd: %s", connection_info, instance=instance)
436 def attach_volume(self, connection_info, instance, adapter_type=None):
437 """Attach volume storage to VM instance."""
438 driver_type = connection_info['driver_volume_type']
439 LOG.debug("Volume attach. Driver type: %s", driver_type,
440 instance=instance)
441 if driver_type == constants.DISK_FORMAT_VMDK:
442 self._attach_volume_vmdk(connection_info, instance, adapter_type)
443 elif driver_type == constants.DISK_FORMAT_ISCSI:
444 self._attach_volume_iscsi(connection_info, instance, adapter_type)
445 elif driver_type == constants.DISK_FORMAT_FCD:
446 self._attach_volume_fcd(connection_info, instance)
447 else:
448 raise exception.VolumeDriverNotFound(driver_type=driver_type)
450 def _get_host_of_vm(self, vm_ref):
451 """Get the ESX host of given VM."""
452 return self._session._call_method(vutil, 'get_object_property',
453 vm_ref, 'runtime').host
455 def _get_res_pool_of_host(self, host):
456 """Get the resource pool of given host's cluster."""
457 # Get the compute resource, the host belongs to
458 compute_res = self._session._call_method(vutil,
459 'get_object_property',
460 host,
461 'parent')
462 # Get resource pool from the compute resource
463 return self._session._call_method(vutil,
464 'get_object_property',
465 compute_res,
466 'resourcePool')
468 def _get_res_pool_of_vm(self, vm_ref):
469 """Get resource pool to which the VM belongs."""
470 # Get the host, the VM belongs to
471 host = self._get_host_of_vm(vm_ref)
472 # Get the resource pool of host's cluster.
473 return self._get_res_pool_of_host(host)
475 def _consolidate_vmdk_volume(self, instance, vm_ref, device, volume_ref,
476 adapter_type=None, disk_type=None):
477 """Consolidate volume backing VMDK files if needed.
479 The volume's VMDK file attached to an instance can be moved by SDRS
480 if enabled on the cluster.
481 By this the VMDK files can get copied onto another datastore and the
482 copy on this new location will be the latest version of the VMDK file.
483 So at the time of detach, we need to consolidate the current backing
484 VMDK file with the VMDK file in the new location.
486 We need to ensure that the VMDK chain (snapshots) remains intact during
487 the consolidation. SDRS retains the chain when it copies VMDK files
488 over, so for consolidation we relocate the backing with move option
489 as moveAllDiskBackingsAndAllowSharing and then delete the older version
490 of the VMDK file attaching the new version VMDK file.
492 In the case of a volume boot the we need to ensure that the volume
493 is on the datastore of the instance.
494 """
496 original_device = self._get_vmdk_base_volume_device(volume_ref)
498 original_device_path = original_device.backing.fileName
499 current_device_path = device.backing.fileName
501 if original_device_path == current_device_path:
502 # The volume is not moved from its original location.
503 # No consolidation is required.
504 LOG.debug("The volume has not been displaced from "
505 "its original location: %s. No consolidation "
506 "needed.", current_device_path)
507 return
509 # The volume has been moved from its original location.
510 # Need to consolidate the VMDK files.
511 LOG.info("The volume's backing has been relocated to %s. Need to "
512 "consolidate backing disk file.", current_device_path)
514 # Pick the host and resource pool on which the instance resides.
515 # Move the volume to the datastore where the new VMDK file is present.
516 host = self._get_host_of_vm(vm_ref)
517 res_pool = self._get_res_pool_of_host(host)
518 datastore = device.backing.datastore
519 detached = False
520 LOG.debug("Relocating volume's backing: %(backing)s to resource "
521 "pool: %(rp)s, datastore: %(ds)s, host: %(host)s.",
522 {'backing': volume_ref, 'rp': res_pool, 'ds': datastore,
523 'host': host})
524 try:
525 vm_util.relocate_vm(self._session, volume_ref, res_pool, datastore,
526 host)
527 except oslo_vmw_exceptions.FileNotFoundException:
528 # Volume's vmdk was moved; remove the device so that we can
529 # relocate the volume.
530 LOG.warning("Virtual disk: %s of volume's backing not found.",
531 original_device_path, exc_info=True)
532 LOG.debug("Removing disk device of volume's backing and "
533 "reattempting relocate.")
534 self.detach_disk_from_vm(volume_ref, instance, original_device)
535 detached = True
536 vm_util.relocate_vm(self._session, volume_ref, res_pool, datastore,
537 host)
539 # Volume's backing is relocated now; detach the old vmdk if not done
540 # already.
541 if not detached:
542 try:
543 self.detach_disk_from_vm(volume_ref, instance,
544 original_device, destroy_disk=True)
545 except oslo_vmw_exceptions.FileNotFoundException:
546 LOG.debug("Original volume backing %s is missing, no need "
547 "to detach it", original_device.backing.fileName)
549 # Attach the current volume to the volume_ref
550 self.attach_disk_to_vm(volume_ref, instance,
551 adapter_type, disk_type,
552 vmdk_path=current_device_path)
554 def _get_vmdk_backed_disk_device(self, vm_ref, connection_info_data):
555 # Get the vmdk file name that the VM is pointing to
556 hardware_devices = vm_util.get_hardware_devices(self._session, vm_ref)
558 # Get disk uuid
559 disk_uuid = self._get_volume_uuid(vm_ref,
560 connection_info_data['volume_id'])
561 device = vm_util.get_vmdk_backed_disk_device(hardware_devices,
562 disk_uuid)
563 if not device:
564 raise exception.DiskNotFound(message=_("Unable to find volume"))
565 return device
567 def _detach_volume_vmdk(self, connection_info, instance):
568 """Detach volume storage to VM instance."""
569 vm_ref = vm_util.get_vm_ref(self._session, instance)
570 # Detach Volume from VM
571 LOG.debug("_detach_volume_vmdk: %s", connection_info,
572 instance=instance)
573 data = connection_info['data']
574 volume_ref = self._get_volume_ref(data)
576 device = self._get_vmdk_backed_disk_device(vm_ref, data)
578 hardware_devices = vm_util.get_hardware_devices(self._session, vm_ref)
579 adapter_type = None
580 for hw_device in hardware_devices:
581 if hw_device.key == device.controllerKey:
582 adapter_type = vm_util.CONTROLLER_TO_ADAPTER_TYPE.get(
583 hw_device.__class__.__name__)
584 break
586 # IDE does not support disk hotplug
587 if adapter_type == constants.ADAPTER_TYPE_IDE:
588 state = vm_util.get_vm_state(self._session, instance)
589 if state != power_state.SHUTDOWN:
590 raise exception.Invalid(_('%s does not support disk '
591 'hotplug.') % adapter_type)
593 disk_type = vm_util._get_device_disk_type(device)
595 self._consolidate_vmdk_volume(instance, vm_ref, device, volume_ref,
596 adapter_type=adapter_type,
597 disk_type=disk_type)
599 self.detach_disk_from_vm(vm_ref, instance, device)
601 # Remove key-value pair <volume_id, vmdk_uuid> from instance's
602 # extra config. Setting value to empty string will remove the key.
603 self._update_volume_details(vm_ref, data['volume_id'], "")
605 LOG.debug("Detached VMDK: %s", connection_info, instance=instance)
607 def _detach_volume_iscsi(self, connection_info, instance):
608 """Detach volume storage to VM instance."""
609 vm_ref = vm_util.get_vm_ref(self._session, instance)
610 # Detach Volume from VM
611 LOG.debug("_detach_volume_iscsi: %s", connection_info,
612 instance=instance)
613 data = connection_info['data']
615 # Discover iSCSI Target
616 device_name, uuid = self._iscsi_get_target(data)
617 if device_name is None:
618 raise exception.StorageError(
619 reason=_("Unable to find iSCSI Target"))
621 # Get the vmdk file name that the VM is pointing to
622 hardware_devices = vm_util.get_hardware_devices(self._session, vm_ref)
623 device = vm_util.get_rdm_disk(hardware_devices, uuid)
624 if device is None:
625 raise exception.DiskNotFound(message=_("Unable to find volume"))
626 self.detach_disk_from_vm(vm_ref, instance, device, destroy_disk=True)
627 LOG.debug("Detached ISCSI: %s", connection_info, instance=instance)
629 def _detach_volume_fcd(self, connection_info, instance):
630 """Detach fcd volume storage to VM instance."""
631 vm_ref = vm_util.get_vm_ref(self._session, instance)
632 data = connection_info['data']
633 adapter_type = data['adapter_type']
635 if adapter_type == constants.ADAPTER_TYPE_IDE:
636 state = vm_util.get_vm_state(self._session, instance)
637 if state != power_state.SHUTDOWN:
638 raise exception.Invalid(_('%s does not support disk '
639 'hotplug.') % adapter_type)
641 vm_util.detach_fcd(self._session, vm_ref, data['id'])
643 def detach_volume(self, connection_info, instance):
644 """Detach volume storage to VM instance."""
645 driver_type = connection_info['driver_volume_type']
646 LOG.debug("Volume detach. Driver type: %s", driver_type,
647 instance=instance)
648 if driver_type == constants.DISK_FORMAT_VMDK:
649 self._detach_volume_vmdk(connection_info, instance)
650 elif driver_type == constants.DISK_FORMAT_ISCSI:
651 self._detach_volume_iscsi(connection_info, instance)
652 elif driver_type == constants.DISK_FORMAT_FCD:
653 self._detach_volume_fcd(connection_info, instance)
654 else:
655 raise exception.VolumeDriverNotFound(driver_type=driver_type)
657 def attach_root_volume(self, connection_info, instance,
658 datastore, adapter_type=None):
659 """Attach a root volume to the VM instance."""
660 driver_type = connection_info['driver_volume_type']
661 LOG.debug("Root volume attach. Driver type: %s", driver_type,
662 instance=instance)
663 if driver_type == constants.DISK_FORMAT_VMDK:
664 vm_ref = vm_util.get_vm_ref(self._session, instance)
665 data = connection_info['data']
666 # Get the volume ref
667 volume_ref = self._get_volume_ref(data)
668 # Pick the resource pool on which the instance resides. Move the
669 # volume to the datastore of the instance.
670 res_pool = self._get_res_pool_of_vm(vm_ref)
671 vm_util.relocate_vm(self._session, volume_ref, res_pool, datastore)
673 self.attach_volume(connection_info, instance, adapter_type)