|
25 | 25 | import com.cloud.agent.api.storage.MigrateVolumeCommand; |
26 | 26 | import com.cloud.agent.api.to.DiskTO; |
27 | 27 | import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; |
| 28 | +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; |
28 | 29 | import com.cloud.hypervisor.kvm.resource.disconnecthook.VolumeMigrationCancelHook; |
| 30 | +import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper; |
29 | 31 | import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; |
30 | 32 | import com.cloud.hypervisor.kvm.storage.KVMStoragePool; |
31 | 33 | import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; |
|
37 | 39 | import java.io.File; |
38 | 40 | import java.io.IOException; |
39 | 41 | import java.io.StringWriter; |
| 42 | +import java.util.List; |
40 | 43 | import java.util.Map; |
41 | 44 | import java.util.UUID; |
42 | 45 |
|
@@ -74,18 +77,80 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo |
74 | 77 | @Override |
75 | 78 | public Answer execute(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { |
76 | 79 | VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); |
77 | | - PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore(); |
| 80 | + PrimaryDataStoreTO srcPrimaryDataStore = srcVolumeObjectTO != null ? (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore() : null; |
| 81 | + VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData(); |
| 82 | + PrimaryDataStoreTO destPrimaryDataStore = destVolumeObjectTO != null ? (PrimaryDataStoreTO)destVolumeObjectTO.getDataStore() : null; |
78 | 83 |
|
79 | 84 | MigrateVolumeAnswer answer; |
80 | | - if (srcPrimaryDataStore.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { |
| 85 | + if (srcPrimaryDataStore != null && srcPrimaryDataStore.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { |
81 | 86 | answer = migratePowerFlexVolume(command, libvirtComputingResource); |
| 87 | + } else if (shouldAttemptFiberChannelLiveMigration(command, srcPrimaryDataStore, destPrimaryDataStore)) { |
| 88 | + answer = migrateFiberChannelVolume(command, libvirtComputingResource); |
82 | 89 | } else { |
83 | 90 | answer = migrateRegularVolume(command, libvirtComputingResource); |
84 | 91 | } |
85 | 92 |
|
86 | 93 | return answer; |
87 | 94 | } |
88 | 95 |
|
| 96 | + private String resolveVolumeIdentifier(VolumeObjectTO volumeObjectTO, Map<String, String> details) { |
| 97 | + if (details != null && details.get(DiskTO.IQN) != null) { |
| 98 | + return details.get(DiskTO.IQN); |
| 99 | + } |
| 100 | + return volumeObjectTO != null ? volumeObjectTO.getPath() : null; |
| 101 | + } |
| 102 | + |
| 103 | + private DiskDef locateSourceDiskDefinition(List<DiskDef> disks, KVMPhysicalDisk srcPhysicalDisk, Map<String, String> srcDetails) { |
| 104 | + String expectedPath = srcPhysicalDisk != null ? srcPhysicalDisk.getPath() : null; |
| 105 | + String expectedSerial = srcDetails != null ? srcDetails.get(DiskTO.SCSI_NAA_DEVICE_ID) : null; |
| 106 | + |
| 107 | + if (StringUtils.isNotBlank(expectedPath)) { |
| 108 | + for (DiskDef disk : disks) { |
| 109 | + if (StringUtils.isNotBlank(disk.getDiskPath()) && pathsReferToSameDevice(expectedPath, disk.getDiskPath())) { |
| 110 | + return disk; |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + if (StringUtils.isNotBlank(expectedSerial)) { |
| 116 | + for (DiskDef disk : disks) { |
| 117 | + if (expectedSerial.equalsIgnoreCase(disk.getSerial())) { |
| 118 | + return disk; |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + return null; |
| 124 | + } |
| 125 | + |
| 126 | + private boolean pathsReferToSameDevice(String expectedPath, String candidatePath) { |
| 127 | + if (StringUtils.equals(expectedPath, candidatePath)) { |
| 128 | + return true; |
| 129 | + } |
| 130 | + String expectedToken = extractLastToken(expectedPath); |
| 131 | + String candidateToken = extractLastToken(candidatePath); |
| 132 | + return StringUtils.isNotBlank(expectedToken) && StringUtils.equalsIgnoreCase(expectedToken, candidateToken); |
| 133 | + } |
| 134 | + |
| 135 | + private String extractLastToken(String path) { |
| 136 | + if (StringUtils.isBlank(path)) { |
| 137 | + return null; |
| 138 | + } |
| 139 | + int idx = path.lastIndexOf('/'); |
| 140 | + return idx >= 0 ? path.substring(idx + 1) : path; |
| 141 | + } |
| 142 | + |
| 143 | + private String buildFiberChannelDestinationDiskXml(DiskDef sourceDiskDef, String destDevicePath) { |
| 144 | + StringBuilder diskXml = new StringBuilder(); |
| 145 | + diskXml.append("<disk type='block'>"); |
| 146 | + diskXml.append("<driver name='qemu' type='raw' cache='none'/>"); |
| 147 | + diskXml.append("<source dev='").append(destDevicePath).append("'/>"); |
| 148 | + diskXml.append("<target dev='").append(sourceDiskDef.getDiskLabel()).append("' bus='") |
| 149 | + .append(sourceDiskDef.getBusType().toString()).append("'/>"); |
| 150 | + diskXml.append("</disk>"); |
| 151 | + return diskXml.toString(); |
| 152 | + } |
| 153 | + |
89 | 154 | protected MigrateVolumeAnswer migratePowerFlexVolume(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { |
90 | 155 |
|
91 | 156 | // Source Details |
@@ -183,6 +248,152 @@ protected MigrateVolumeAnswer migratePowerFlexVolume(final MigrateVolumeCommand |
183 | 248 | } |
184 | 249 | } |
185 | 250 |
|
| 251 | + protected boolean shouldAttemptFiberChannelLiveMigration(MigrateVolumeCommand command, PrimaryDataStoreTO srcPrimaryDataStore, PrimaryDataStoreTO destPrimaryDataStore) { |
| 252 | + if (srcPrimaryDataStore == null || destPrimaryDataStore == null) { |
| 253 | + return false; |
| 254 | + } |
| 255 | + |
| 256 | + if (!Storage.StoragePoolType.FiberChannel.equals(srcPrimaryDataStore.getPoolType()) || |
| 257 | + !Storage.StoragePoolType.FiberChannel.equals(destPrimaryDataStore.getPoolType())) { |
| 258 | + return false; |
| 259 | + } |
| 260 | + |
| 261 | + VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); |
| 262 | + |
| 263 | + return srcVolumeObjectTO != null && StringUtils.isNotBlank(srcVolumeObjectTO.getVmName()); |
| 264 | + } |
| 265 | + |
| 266 | + protected MigrateVolumeAnswer migrateFiberChannelVolume(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { |
| 267 | + VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); |
| 268 | + VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData(); |
| 269 | + if (srcVolumeObjectTO == null || destVolumeObjectTO == null || StringUtils.isBlank(srcVolumeObjectTO.getVmName())) { |
| 270 | + return migrateRegularVolume(command, libvirtComputingResource); |
| 271 | + } |
| 272 | + |
| 273 | + String vmName = srcVolumeObjectTO.getVmName(); |
| 274 | + Map<String, String> srcDetails = command.getSrcDetails(); |
| 275 | + Map<String, String> destDetails = command.getDestDetails(); |
| 276 | + |
| 277 | + PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore(); |
| 278 | + PrimaryDataStoreTO destPrimaryDataStore = (PrimaryDataStoreTO)destVolumeObjectTO.getDataStore(); |
| 279 | + |
| 280 | + KVMStoragePoolManager storagePoolManager = libvirtComputingResource.getStoragePoolMgr(); |
| 281 | + KVMStoragePool sourceStoragePool = null; |
| 282 | + KVMStoragePool destStoragePool = null; |
| 283 | + Domain dm = null; |
| 284 | + VolumeMigrationCancelHook cancelHook = null; |
| 285 | + boolean migrationSucceeded = false; |
| 286 | + |
| 287 | + String srcIdentifier = resolveVolumeIdentifier(srcVolumeObjectTO, srcDetails); |
| 288 | + String destIdentifier = resolveVolumeIdentifier(destVolumeObjectTO, destDetails); |
| 289 | + |
| 290 | + if (StringUtils.isBlank(srcIdentifier) || StringUtils.isBlank(destIdentifier)) { |
| 291 | + return migrateRegularVolume(command, libvirtComputingResource); |
| 292 | + } |
| 293 | + |
| 294 | + try { |
| 295 | + sourceStoragePool = storagePoolManager.getStoragePool(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid()); |
| 296 | + if (!sourceStoragePool.connectPhysicalDisk(srcIdentifier, srcDetails)) { |
| 297 | + return new MigrateVolumeAnswer(command, false, "Unable to connect source Fibre Channel volume on hypervisor", srcIdentifier); |
| 298 | + } |
| 299 | + KVMPhysicalDisk srcPhysicalDisk = storagePoolManager.getPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcIdentifier); |
| 300 | + if (srcPhysicalDisk == null) { |
| 301 | + return new MigrateVolumeAnswer(command, false, "Unable to obtain source Fibre Channel disk handle", srcIdentifier); |
| 302 | + } |
| 303 | + |
| 304 | + destStoragePool = storagePoolManager.getStoragePool(destPrimaryDataStore.getPoolType(), destPrimaryDataStore.getUuid()); |
| 305 | + if (!destStoragePool.connectPhysicalDisk(destIdentifier, destDetails)) { |
| 306 | + return new MigrateVolumeAnswer(command, false, "Unable to connect destination Fibre Channel volume on hypervisor", destIdentifier); |
| 307 | + } |
| 308 | + |
| 309 | + if (destVolumeObjectTO.getPath() == null) { |
| 310 | + destVolumeObjectTO.setPath(destIdentifier); |
| 311 | + } |
| 312 | + |
| 313 | + KVMPhysicalDisk destPhysicalDisk = storagePoolManager.getPhysicalDisk(destPrimaryDataStore.getPoolType(), destPrimaryDataStore.getUuid(), destIdentifier); |
| 314 | + if (destPhysicalDisk == null) { |
| 315 | + return new MigrateVolumeAnswer(command, false, "Unable to obtain destination Fibre Channel disk handle", destIdentifier); |
| 316 | + } |
| 317 | + String destDevicePath = destPhysicalDisk.getPath(); |
| 318 | + |
| 319 | + LibvirtUtilitiesHelper helper = libvirtComputingResource.getLibvirtUtilitiesHelper(); |
| 320 | + Connect conn = helper.getConnection(); |
| 321 | + dm = libvirtComputingResource.getDomain(conn, vmName); |
| 322 | + if (dm == null) { |
| 323 | + return new MigrateVolumeAnswer(command, false, "Unable to locate libvirt domain for VM " + vmName, null); |
| 324 | + } |
| 325 | + |
| 326 | + DomainInfo.DomainState domainState = dm.getInfo().state; |
| 327 | + if (domainState != DomainInfo.DomainState.VIR_DOMAIN_RUNNING) { |
| 328 | + return migrateRegularVolume(command, libvirtComputingResource); |
| 329 | + } |
| 330 | + |
| 331 | + List<DiskDef> disks = libvirtComputingResource.getDisks(conn, vmName); |
| 332 | + DiskDef srcDiskDef = locateSourceDiskDefinition(disks, srcPhysicalDisk, srcDetails); |
| 333 | + if (srcDiskDef == null) { |
| 334 | + return new MigrateVolumeAnswer(command, false, "Unable to match Fibre Channel disk within VM definition", null); |
| 335 | + } |
| 336 | + |
| 337 | + String diskLabel = srcDiskDef.getDiskLabel(); |
| 338 | + String destinationDiskXml = buildFiberChannelDestinationDiskXml(srcDiskDef, destDevicePath); |
| 339 | + |
| 340 | + TypedUlongParameter parameter = new TypedUlongParameter("bandwidth", 0); |
| 341 | + TypedParameter[] parameters = new TypedParameter[] { parameter }; |
| 342 | + |
| 343 | + cancelHook = new VolumeMigrationCancelHook(dm, diskLabel); |
| 344 | + libvirtComputingResource.addDisconnectHook(cancelHook); |
| 345 | + |
| 346 | + libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING_IN_BACKEND); |
| 347 | + |
| 348 | + dm.blockCopy(diskLabel, destinationDiskXml, parameters, Domain.BlockCopyFlags.REUSE_EXT); |
| 349 | + |
| 350 | + MigrateVolumeAnswer answer = checkBlockJobStatus(command, dm, diskLabel, srcPhysicalDisk.getPath(), destDevicePath, libvirtComputingResource, conn, null); |
| 351 | + migrationSucceeded = answer != null && answer.getResult(); |
| 352 | + |
| 353 | + if (answer != null) { |
| 354 | + libvirtComputingResource.createOrUpdateLogFileForCommand(command, migrationSucceeded ? Command.State.COMPLETED : Command.State.FAILED); |
| 355 | + return answer; |
| 356 | + } |
| 357 | + |
| 358 | + migrationSucceeded = true; |
| 359 | + libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.COMPLETED); |
| 360 | + return new MigrateVolumeAnswer(command, true, null, destIdentifier); |
| 361 | + } catch (LibvirtException e) { |
| 362 | + logger.warn("Fibre Channel live volume migration failed due to libvirt error", e); |
| 363 | + libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED); |
| 364 | + return new MigrateVolumeAnswer(command, false, "Libvirt error during Fibre Channel migration: " + e.getMessage(), null); |
| 365 | + } catch (Exception e) { |
| 366 | + logger.warn("Fibre Channel live volume migration failed", e); |
| 367 | + libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED); |
| 368 | + return new MigrateVolumeAnswer(command, false, "Fibre Channel migration failed: " + e.getMessage(), null); |
| 369 | + } finally { |
| 370 | + if (cancelHook != null) { |
| 371 | + libvirtComputingResource.removeDisconnectHook(cancelHook); |
| 372 | + } |
| 373 | + if (dm != null) { |
| 374 | + try { |
| 375 | + dm.free(); |
| 376 | + } catch (LibvirtException e) { |
| 377 | + logger.trace("Ignoring libvirt error while freeing domain", e); |
| 378 | + } |
| 379 | + } |
| 380 | + if (!migrationSucceeded && destStoragePool != null) { |
| 381 | + try { |
| 382 | + destStoragePool.disconnectPhysicalDisk(destIdentifier); |
| 383 | + } catch (Exception e) { |
| 384 | + logger.warn("Unable to disconnect destination Fibre Channel disk after failed migration", e); |
| 385 | + } |
| 386 | + } |
| 387 | + if (sourceStoragePool != null) { |
| 388 | + try { |
| 389 | + sourceStoragePool.disconnectPhysicalDisk(srcIdentifier); |
| 390 | + } catch (Exception e) { |
| 391 | + logger.warn("Unable to disconnect source Fibre Channel disk after migration", e); |
| 392 | + } |
| 393 | + } |
| 394 | + } |
| 395 | + } |
| 396 | + |
186 | 397 | protected MigrateVolumeAnswer checkBlockJobStatus(MigrateVolumeCommand command, Domain dm, String diskLabel, String srcPath, String destPath, LibvirtComputingResource libvirtComputingResource, Connect conn, String srcSecretUUID) throws LibvirtException { |
187 | 398 | int timeBetweenTries = 1000; // Try more frequently (every sec) and return early if disk is found |
188 | 399 | int waitTimeInSec = command.getWait(); |
|
0 commit comments