Coverage for nova/virt/vmwareapi/volumeops.py: 0%

352 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +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. 

15 

16""" 

17Management class for Storage-related functions (attach, detach, etc). 

18""" 

19 

20from oslo_log import log as logging 

21from oslo_vmware import exceptions as oslo_vmw_exceptions 

22from oslo_vmware import vim_util as vutil 

23 

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 

31 

32CONF = nova.conf.CONF 

33LOG = logging.getLogger(__name__) 

34 

35 

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 

44 

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) 

51 

52 

53class VMwareVolumeOps(object): 

54 """Management class for Volume-related tasks.""" 

55 

56 def __init__(self, session, cluster=None): 

57 self._session = session 

58 self._cluster = cluster 

59 

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) 

73 

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) 

80 

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) 

94 

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} 

99 

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) 

104 

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 

113 

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) 

131 

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) 

137 

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') 

153 

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 

166 

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 

189 

190 if scsi_lun_key is None: 

191 return result 

192 

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) 

201 

202 return result 

203 

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]) 

214 

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) 

254 

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) 

266 

267 # Rescan iSCSI HBA with iscsi target host 

268 self._iscsi_rescan_hba(target_portal) 

269 

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) 

283 

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) 

290 

291 hbas_ret = self._session._call_method( 

292 vutil, 

293 "get_object_property", 

294 host_mor, 

295 "config.storageDevice.hostBusAdapter") 

296 

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 

306 

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 

320 

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) 

325 

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) 

331 

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) 

340 

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 

345 

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) 

352 

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) 

356 

357 # Store the uuid of the volume_device 

358 self._update_volume_details(vm_ref, data['volume_id'], 

359 vmdk.device.backing.uuid) 

360 

361 LOG.debug("Attached VMDK: %s", connection_info, instance=instance) 

362 

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) 

370 

371 data = connection_info['data'] 

372 

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) 

383 

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) 

388 

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) 

400 

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) 

405 

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) 

415 

416 vm_util.attach_fcd( 

417 self._session, vm_ref, fcd_id, ds_ref_val, controller_key, 

418 unit_number) 

419 

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'] 

426 

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) 

432 

433 self._attach_fcd(vm_ref, adapter_type, data['id'], data['ds_ref_val']) 

434 LOG.debug("Attached fcd: %s", connection_info, instance=instance) 

435 

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) 

449 

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 

454 

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') 

467 

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) 

474 

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. 

478 

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. 

485 

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. 

491 

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 """ 

495 

496 original_device = self._get_vmdk_base_volume_device(volume_ref) 

497 

498 original_device_path = original_device.backing.fileName 

499 current_device_path = device.backing.fileName 

500 

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 

508 

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) 

513 

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) 

538 

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) 

548 

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) 

553 

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) 

557 

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 

566 

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) 

575 

576 device = self._get_vmdk_backed_disk_device(vm_ref, data) 

577 

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 

585 

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) 

592 

593 disk_type = vm_util._get_device_disk_type(device) 

594 

595 self._consolidate_vmdk_volume(instance, vm_ref, device, volume_ref, 

596 adapter_type=adapter_type, 

597 disk_type=disk_type) 

598 

599 self.detach_disk_from_vm(vm_ref, instance, device) 

600 

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'], "") 

604 

605 LOG.debug("Detached VMDK: %s", connection_info, instance=instance) 

606 

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'] 

614 

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")) 

620 

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) 

628 

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'] 

634 

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) 

640 

641 vm_util.detach_fcd(self._session, vm_ref, data['id']) 

642 

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) 

656 

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) 

672 

673 self.attach_volume(connection_info, instance, adapter_type)