diff --git a/core/src/main/java/com/cloud/agent/api/CleanForMigrationStorageCommand.java b/core/src/main/java/com/cloud/agent/api/CleanForMigrationStorageCommand.java new file mode 100644 index 000000000000..fc84ff7d1647 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/CleanForMigrationStorageCommand.java @@ -0,0 +1,42 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public class CleanForMigrationStorageCommand extends Command { + private VirtualMachineTO vmSpec; + + protected CleanForMigrationStorageCommand() { + } + + public CleanForMigrationStorageCommand(VirtualMachineTO vmSpec) { + this.vmSpec = vmSpec; + } + + public VirtualMachineTO getVmSpec() { + return vmSpec; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/CleanForMigrationStorageCommandAnswer.java b/core/src/main/java/com/cloud/agent/api/CleanForMigrationStorageCommandAnswer.java new file mode 100644 index 000000000000..8b121ffc172f --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/CleanForMigrationStorageCommandAnswer.java @@ -0,0 +1,36 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +public class CleanForMigrationStorageCommandAnswer extends Answer { + + protected CleanForMigrationStorageCommandAnswer() { + } + + public CleanForMigrationStorageCommandAnswer(CleanForMigrationStorageCommand cmd) { + super(cmd); + } + + + public CleanForMigrationStorageCommandAnswer(CleanForMigrationStorageCommand cmd, Exception ex) { + super(cmd, ex); + } + +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareForMigrationStorageAnswer.java b/core/src/main/java/com/cloud/agent/api/PrepareForMigrationStorageAnswer.java new file mode 100644 index 000000000000..28e16ff10226 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareForMigrationStorageAnswer.java @@ -0,0 +1,49 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; + +public class PrepareForMigrationStorageAnswer extends Answer { + VirtualMachineTO vmSpec; + VolumeTO[] volumes; + + protected PrepareForMigrationStorageAnswer() { + } + + public PrepareForMigrationStorageAnswer(PrepareForMigrationStorageCommand cmd, VirtualMachineTO vmSpec, VolumeTO[] volumes) { + super(cmd, false, null); + this.vmSpec = vmSpec; + this.volumes = volumes; + } + + public PrepareForMigrationStorageAnswer(PrepareForMigrationStorageCommand cmd, Exception ex) { + super(cmd, ex); + } + + public VirtualMachineTO getVmSpec() { + return vmSpec; + } + + public VolumeTO[] getVolumes() { + return volumes; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareForMigrationStorageCommand.java b/core/src/main/java/com/cloud/agent/api/PrepareForMigrationStorageCommand.java new file mode 100644 index 000000000000..de3ea7ab473e --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareForMigrationStorageCommand.java @@ -0,0 +1,46 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +public class PrepareForMigrationStorageCommand extends Command { + private VolumeObjectTO volumeTO; + + protected PrepareForMigrationStorageCommand() { + } + + public PrepareForMigrationStorageCommand(VolumeObjectTO volumeTO) { + this.volumeTO = volumeTO; + } + + public VolumeObjectTO getVolumeTO() { + return volumeTO; + } + + public void setVolumeTO(VolumeObjectTO volumeTO) { + this.volumeTO = volumeTO; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java index 803e24bb4392..7efe00cb402e 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java @@ -45,7 +45,7 @@ import com.xensource.xenapi.Network; import com.xensource.xenapi.SR; -@ResourceWrapper(handles = MigrateWithStorageReceiveCommand.class) +@ResourceWrapper(handles = MigrateWithStorageReceiveCommand.class) public final class XenServer610MigrateWithStorageReceiveCommandWrapper extends CommandWrapper { private static final Logger s_logger = Logger.getLogger(XenServer610MigrateWithStorageReceiveCommandWrapper.class); @@ -87,6 +87,11 @@ public Answer execute(final MigrateWithStorageReceiveCommand command, final XenS final String uuid = xsHost.getUuid(); final Map other = new HashMap(); + + if (vmSpec.getDetails().containsKey("forcemigrate")) { + other.put("force", vmSpec.getDetails().get("forcemigrate")); + } + other.put("live", "true"); final Host host = Host.getByUuid(connection, uuid); @@ -101,4 +106,4 @@ public Answer execute(final MigrateWithStorageReceiveCommand command, final XenS return new MigrateWithStorageReceiveAnswer(command, e); } } -} \ No newline at end of file +} diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageSendCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageSendCommandWrapper.java index c4ebb0065e9b..df39bd28808b 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageSendCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageSendCommandWrapper.java @@ -46,7 +46,7 @@ import com.xensource.xenapi.VIF; import com.xensource.xenapi.VM; -@ResourceWrapper(handles = MigrateWithStorageSendCommand.class) +@ResourceWrapper(handles = MigrateWithStorageSendCommand.class) public final class XenServer610MigrateWithStorageSendCommandWrapper extends CommandWrapper { private static final Logger s_logger = Logger.getLogger(XenServer610MigrateWithStorageSendCommandWrapper.class); @@ -73,6 +73,9 @@ public Answer execute(final MigrateWithStorageSendCommand command, final XenServ // the answer object. It'll be deserialzed and object created in migrate with // storage send command execution. final Map other = new HashMap(); + if (vmSpec.getDetails().containsKey("forcemigrate")) { + other.put("force", vmSpec.getDetails().get("forcemigrate")); + } other.put("live", "true"); // Create the vdi map which tells what volumes of the vm need to go @@ -80,7 +83,7 @@ public Answer execute(final MigrateWithStorageSendCommand command, final XenServ final Map vdiMap = new HashMap(); for (final Pair entry : volumeToSr) { if (entry.second() instanceof SR) { - final SR sr = (SR)entry.second(); + final SR sr = (SR) entry.second(); final VDI vdi = xenServer610Resource.getVDIbyUuid(connection, entry.first().getPath()); vdiMap.put(vdi, sr); } else { @@ -98,7 +101,7 @@ public Answer execute(final MigrateWithStorageSendCommand command, final XenServ final Map vifMap = new HashMap(); for (final Pair entry : nicToNetwork) { if (entry.second() instanceof Network) { - final Network network = (Network)entry.second(); + final Network network = (Network) entry.second(); final VIF vif = xenServer610Resource.getVifByMac(connection, vmToMigrate, entry.first().getMac()); vifMap.put(vif, network); } else { @@ -148,4 +151,4 @@ public Answer execute(final MigrateWithStorageSendCommand command, final XenServ } } } -} \ No newline at end of file +} diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanForMigrationStorageCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanForMigrationStorageCommandWrapper.java new file mode 100644 index 000000000000..5048b061752a --- /dev/null +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanForMigrationStorageCommandWrapper.java @@ -0,0 +1,55 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CleanForMigrationStorageCommand; +import com.cloud.agent.api.CleanForMigrationStorageCommandAnswer; +import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.VM; +import org.apache.log4j.Logger; +import org.apache.xmlrpc.XmlRpcException; + +@ResourceWrapper(handles = CleanForMigrationStorageCommand.class) +public final class CitrixCleanForMigrationStorageCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(CitrixCleanForMigrationStorageCommandWrapper.class); + + @Override + public Answer execute(final CleanForMigrationStorageCommand command, final CitrixResourceBase citrixResourceBase) { + final Connection conn = citrixResourceBase.getConnection(); + try { + VM vm = citrixResourceBase.getVM(conn, command.getVmSpec().getName()); + vm.destroy(conn); + } catch (Types.XenAPIException e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + " clean for migration storage failed due to " + e.toString(), e); + return new CleanForMigrationStorageCommandAnswer(command, e); + } catch (XmlRpcException e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + " clean for migration storage failed due to " + e.toString(), e); + return new CleanForMigrationStorageCommandAnswer(command, e); + } + + return new CleanForMigrationStorageCommandAnswer(command); + } +} \ No newline at end of file diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPrepareForMigrationStorageCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPrepareForMigrationStorageCommandWrapper.java new file mode 100644 index 000000000000..06f49d321a7b --- /dev/null +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPrepareForMigrationStorageCommandWrapper.java @@ -0,0 +1,128 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PrepareForMigrationStorageAnswer; +import com.cloud.agent.api.PrepareForMigrationStorageCommand; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.storage.Storage; +import com.cloud.storage.Volume; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.vm.VirtualMachine; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.SR; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.VDI; +import com.xensource.xenapi.VM; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.log4j.Logger; +import org.apache.xmlrpc.XmlRpcException; + +import java.util.HashMap; +import java.util.Map; + +@ResourceWrapper(handles = PrepareForMigrationStorageCommand.class) +public final class CitrixPrepareForMigrationStorageCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(CitrixPrepareForMigrationStorageCommandWrapper.class); + + @Override + public Answer execute(final PrepareForMigrationStorageCommand command, final CitrixResourceBase citrixResourceBase) { + final Connection conn = citrixResourceBase.getConnection(); + String vmNameNVdiRootId; + try { + vmNameNVdiRootId = citrixResourceBase.callHostPlugin(conn, + "migrate-unattached-disk", "create_n_attach", + "local_vdi_uuid", command.getVolumeTO().getPath()).trim(); + } catch (final Exception e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + " prepare for storage migration failed due to " + e.toString(), e); + return new PrepareForMigrationStorageAnswer(command, e); + } + + String[] splitInfo = vmNameNVdiRootId.split(" "); + String vmName = splitInfo[0]; + String rootVdiId = splitInfo[1]; + + VM.Record vmRecord; + VDI.Record vdiRecord; + String poolUuid; + VolumeObjectTO volumeToMove = command.getVolumeTO(); + try { + VM vm = citrixResourceBase.getVM(conn, vmName); + vmRecord = vm.getRecord(conn); + VDI vdi = citrixResourceBase.getVDIbyUuid(conn, rootVdiId); + vdiRecord = vdi.getRecord(conn); + SR sr = vdi.getSR(conn); + poolUuid = sr.getNameLabel(conn); + } catch (Types.XenAPIException e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + " prepare for migration storage failed due to " + e.toString(), e); + return new PrepareForMigrationStorageAnswer(command, e); + } catch (XmlRpcException e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + " prepare for migration storage failed due to " + e.toString(), e); + return new PrepareForMigrationStorageAnswer(command, e); + } + + + VirtualMachineTO vmSpec = new VirtualMachineTO(1L, + vmName, + VirtualMachine.Type.Instance, + vmRecord.VCPUsMax.intValue(), + 2000, + vmRecord.memoryStaticMin, + vmRecord.memoryStaticMax, + VirtualMachineTemplate.BootloaderType.External, + "busybox", + false, + false, + "none"); + vmSpec.setDisks(new DiskTO[]{ + new DiskTO(volumeToMove, 2L, volumeToMove.getPath(), volumeToMove.getVolumeType()) + }); + Map details = new HashMap(); + details.put("forcemigrate", "true"); + vmSpec.setDetails(details); + vmSpec.setNics(new NicTO[]{}); + + + VolumeTO[] volumes = new VolumeTO[]{ + new VolumeTO(1, + Volume.Type.ROOT, + Storage.StoragePoolType.PreSetup, + poolUuid, "root", "/", + rootVdiId, vdiRecord.virtualSize, + ""), + new VolumeTO( + volumeToMove.getId(), + volumeToMove.getVolumeType(), + Storage.StoragePoolType.VMFS, + volumeToMove.getDataStore().getUuid(), volumeToMove.getName(), "/", + volumeToMove.getPath(), volumeToMove.getSize(), + volumeToMove.getChainInfo()), + }; + return new PrepareForMigrationStorageAnswer(command, vmSpec, volumes); + } +} diff --git a/plugins/hypervisors/xenserver/src/main/java/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java b/plugins/hypervisors/xenserver/src/main/java/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java index dbfcfe987fc5..96b86e02a3c5 100644 --- a/plugins/hypervisors/xenserver/src/main/java/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java +++ b/plugins/hypervisors/xenserver/src/main/java/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java @@ -33,6 +33,10 @@ import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.to.VolumeObjectTO; @@ -41,6 +45,7 @@ import org.springframework.stereotype.Component; import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Command; import com.cloud.agent.api.Answer; import com.cloud.agent.api.CreateStoragePoolCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; @@ -52,12 +57,17 @@ import com.cloud.agent.api.MigrateWithStorageReceiveCommand; import com.cloud.agent.api.MigrateWithStorageSendAnswer; import com.cloud.agent.api.MigrateWithStorageSendCommand; +import com.cloud.agent.api.PrepareForMigrationStorageCommand; +import com.cloud.agent.api.PrepareForMigrationStorageAnswer; +import com.cloud.agent.api.CleanForMigrationStorageCommand; +import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.xenserver.resource.CitrixHelper; import com.cloud.storage.Snapshot; @@ -68,13 +78,23 @@ import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.storage.DataStoreRole; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; @Component -public class XenServerStorageMotionStrategy implements DataMotionStrategy { +public class XenServerStorageMotionStrategy implements DataMotionStrategy, Configurable { + static final ConfigKey XenCopyAsyncAcrossCluster = + new ConfigKey( + "Advanced", + Boolean.class, + "xen.live.migrate.unattached.volumes", + "false", + "Indicates whether to live migrate unattached volumes across clusters rather than via secondary storage.", + true, + ConfigKey.Scope.Global); private static final Logger s_logger = Logger.getLogger(XenServerStorageMotionStrategy.class); @Inject AgentManager agentMgr; @@ -85,14 +105,28 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { @Inject PrimaryDataStoreDao storagePoolDao; @Inject - private VolumeDetailsDao volumeDetailsDao; - @Inject VMInstanceDao instanceDao; @Inject SnapshotDao snapshotDao; + @Inject + HostDao hostDao; + @Inject + EndPointSelector selector; + @Inject + private VolumeDetailsDao volumeDetailsDao; @Override public StrategyPriority canHandle(DataObject srcData, DataObject destData) { + if (!XenCopyAsyncAcrossCluster.value()) { + return StrategyPriority.CANT_HANDLE; + } + // handle case move between pool a volume and become first to use before ancient motion strategy + if (destData.getType() == DataObjectType.VOLUME && srcData.getType() == DataObjectType.VOLUME && + srcData.getDataStore().getRole() == DataStoreRole.Primary && destData.getDataStore().getRole() == DataStoreRole.Primary && + srcData.getId() != destData.getId()) { + return StrategyPriority.HYPERVISOR; + } + return StrategyPriority.CANT_HANDLE; } @@ -107,7 +141,93 @@ public StrategyPriority canHandle(Map volumeMap, Host src @Override public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback callback) { - throw new UnsupportedOperationException(); + if (XenCopyAsyncAcrossCluster.value()) { + s_logger.info("XenServerStorageMotionStrategy: xen.live.migrate.unattached.volumes is enabled"); + } else { + s_logger.info("XenServerStorageMotionStrategy: xen.live.migrate.unattached.volumes is disabled"); + } + if (!XenCopyAsyncAcrossCluster.value() || this.canHandle(srcData, destData).equals(StrategyPriority.CANT_HANDLE)) { + throw new UnsupportedOperationException(); + } + + EndPoint epSrc = selector.select(srcData); + if (epSrc == null) { + this.sendErrorMessage("No remote endpoint to send command as source, check if host or ssvm is down?", callback); + return; + } + Host srcHost = hostDao.findById(epSrc.getId()); + + + if (destHost == null) { + EndPoint epDest = selector.select(destData); + if (epDest == null) { + this.sendErrorMessage("No remote endpoint to send command as dest, check if host or ssvm is down?", callback); + return; + } + destHost = hostDao.findById(epDest.getId()); + } + + VolumeObjectTO volumeSrcTo = (VolumeObjectTO) srcData.getTO(); + StoragePool storagePoolDest = (StoragePool) destData.getDataStore(); + PrepareForMigrationStorageAnswer prepAnswer; + try { + // ask for creating a vm in stop mode on source host and attach non attached vdi to it + prepAnswer = (PrepareForMigrationStorageAnswer) agentMgr.send(srcHost.getId(), new PrepareForMigrationStorageCommand(volumeSrcTo)); + } catch (Exception e) { + this.sendErrorMessage("copy failed " + e.getMessage(), callback); + return; + } + + + List> volumeToStorageUuid = new ArrayList<>(); + + for (VolumeTO volumeTo : prepAnswer.getVolumes()) { + volumeToStorageUuid.add(new Pair<>(volumeTo, storagePoolDest.getUuid())); + } + + MigrateWithStorageCompleteAnswer answer; + try { + answer = this.migrateVolumeAcrossCluster(prepAnswer.getVmSpec(), srcHost, destHost, volumeToStorageUuid); + } catch (Exception e) { + this.sendErrorMessage("copy failed " + e.getMessage(), callback); + return; + } finally { + // cleaning up vm for migration as we don't need it anymore + try { + agentMgr.send(destHost.getId(), new CleanForMigrationStorageCommand(prepAnswer.getVmSpec())); + } catch (Exception e) { + this.sendErrorMessage("copy failed during cleanup migration vm " + e.getMessage(), callback); + return; + } + } + + // set new vdi uuid (which is store in path) in the volume database + Long idVolumeDest = destData.getTO().getId(); + // set new vdi uuid (which is store in path) in the new volume database + for (VolumeObjectTO volumeTo : answer.getVolumeTos()) { + if (volumeSrcTo.getId() != volumeTo.getId()) { + continue; + } + VolumeVO volumeDest = volDao.findById(idVolumeDest); + + volumeDest.setPath(volumeTo.getPath()); + volumeDest.setFolder(storagePoolDest.getPath()); + volumeDest.setPodId(storagePoolDest.getPodId()); + volumeDest.setPoolId(storagePoolDest.getId()); + + volDao.update(idVolumeDest, volumeDest); + + break; + } + + // mark pool id as null to notify that volume has been removed and need just to be cleared + Long idVolumeSrc = srcData.getTO().getId(); + VolumeVO volumeSrc = volDao.findById(idVolumeSrc); + volumeSrc.setPoolId(null); + volDao.update(idVolumeSrc, volumeSrc); + + CopyCommandResult result = new CopyCommandResult(null, answer); + callback.complete(result); } @Override @@ -135,6 +255,19 @@ public void copyAsync(Map volumeMap, VirtualMachineTO vmT callback.complete(result); } + private void sendErrorMessage(String message, AsyncCompletionCallback callback) { + s_logger.error(message); + Answer answer = new Answer(new Command() { + @Override + public boolean executeInSequence() { + return false; + } + }, false, message); + CopyCommandResult result = new CopyCommandResult(null, answer); + result.setResult(message); + callback.complete(result); + } + private String getBasicIqn(long volumeId) { VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, PrimaryDataStoreDriver.BASIC_IQN); @@ -161,7 +294,7 @@ private void verifyNoSnapshotsOnManagedStorageVolumes(Map * send a command to the destination cluster to create an SR and to attach to the SR from all hosts in the cluster. */ private String handleManagedVolumePreMigration(VolumeInfo volumeInfo, StoragePool storagePool, Host destHost) { - final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver(); + final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver) volumeInfo.getDataStore().getDriver(); VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_CREATE, Boolean.TRUE.toString(), false); @@ -245,7 +378,7 @@ private void handleManagedVolumePostMigration(VolumeInfo volumeInfo, Host srcHos throw new CloudRuntimeException(errMsg); } - final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver(); + final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver) volumeInfo.getDataStore().getDriver(); pdsd.revokeAccess(volumeInfo, srcHost, volumeInfo.getDataStore()); @@ -290,7 +423,7 @@ private void handleManagedVolumesAfterFailedMigration(Map return; } - final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver(); + final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver) volumeInfo.getDataStore().getDriver(); VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_REVOKE_ACCESS, Boolean.TRUE.toString(), false); @@ -310,80 +443,80 @@ private void handleManagedVolumesAfterFailedMigration(Map private Answer migrateVmWithVolumesAcrossCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, Host destHost, Map volumeToPool) throws AgentUnavailableException { // Initiate migration of a virtual machine with its volumes. + verifyNoSnapshotsOnManagedStorageVolumes(volumeToPool); - try { - verifyNoSnapshotsOnManagedStorageVolumes(volumeToPool); - - List> volumeToStorageUuid = new ArrayList<>(); + List> volumeToStorageUuid = new ArrayList<>(); - for (Map.Entry entry : volumeToPool.entrySet()) { - VolumeInfo volumeInfo = entry.getKey(); - StoragePool storagePool = storagePoolDao.findById(volumeInfo.getPoolId()); - VolumeTO volumeTo = new VolumeTO(volumeInfo, storagePool); + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volumeInfo = entry.getKey(); + StoragePool storagePool = storagePoolDao.findById(volumeInfo.getPoolId()); + VolumeTO volumeTo = new VolumeTO(volumeInfo, storagePool); - if (storagePool.isManaged()) { - String iqn = handleManagedVolumePreMigration(volumeInfo, storagePool, destHost); + if (storagePool.isManaged()) { + String iqn = handleManagedVolumePreMigration(volumeInfo, storagePool, destHost); - volumeToStorageUuid.add(new Pair<>(volumeTo, iqn)); - } - else { - StoragePool pool = (StoragePool)entry.getValue(); - String srNameLabel = CitrixHelper.getSRNameLabel(pool.getUuid(), pool.getPoolType(), pool.getPath()); - volumeToStorageUuid.add(new Pair<>(volumeTo, srNameLabel)); - } + volumeToStorageUuid.add(new Pair<>(volumeTo, iqn)); } + else { + StoragePool pool = (StoragePool) entry.getValue(); + String srNameLabel = CitrixHelper.getSRNameLabel(pool.getUuid(), pool.getPoolType(), pool.getPath()); + volumeToStorageUuid.add(new Pair<>(volumeTo, srNameLabel)); + } + } - // Migration across cluster needs to be done in three phases. - // 1. Send a migrate receive command to the destination host so that it is ready to receive a vm. - // 2. Send a migrate send command to the source host. This actually migrates the vm to the destination. - // 3. Complete the process. Update the volume details. + // Migration across cluster needs to be done in three phases. + // 1. Send a migrate receive command to the destination host so that it is ready to receive a vm. + // 2. Send a migrate send command to the source host. This actually migrates the vm to the destination. + // 3. Complete the process. Update the volume details. + MigrateWithStorageCompleteAnswer answer; + try { + answer = migrateVolumeAcrossCluster(to, srcHost, destHost, volumeToStorageUuid); + } catch (OperationTimedoutException e) { + s_logger.error("Error while migrating vm " + vm + " to host " + destHost, e); + throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId()); + } catch (CloudRuntimeException e) { + s_logger.error("Error on vm " + vm + " " + e.getMessage()); + handleManagedVolumesAfterFailedMigration(volumeToPool, destHost); + throw e; + } - MigrateWithStorageReceiveCommand receiveCmd = new MigrateWithStorageReceiveCommand(to, volumeToStorageUuid); - MigrateWithStorageReceiveAnswer receiveAnswer = (MigrateWithStorageReceiveAnswer)agentMgr.send(destHost.getId(), receiveCmd); + // s_logger.error("Migration with storage of vm " + vm + " to host " + destHost + " failed."); + updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos(), srcHost); + return answer; + } - if (receiveAnswer == null) { - s_logger.error("Migration with storage of vm " + vm + " to host " + destHost + " failed."); - throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); - } else if (!receiveAnswer.getResult()) { - s_logger.error("Migration with storage of vm " + vm + " failed. Details: " + receiveAnswer.getDetails()); - throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); - } + private MigrateWithStorageCompleteAnswer migrateVolumeAcrossCluster(VirtualMachineTO to, Host srcHost, Host destHost, List> volumeToStorageUuid) throws AgentUnavailableException, OperationTimedoutException { - MigrateWithStorageSendCommand sendCmd = - new MigrateWithStorageSendCommand(to, receiveAnswer.getVolumeToSr(), receiveAnswer.getNicToNetwork(), receiveAnswer.getToken()); - MigrateWithStorageSendAnswer sendAnswer = (MigrateWithStorageSendAnswer)agentMgr.send(srcHost.getId(), sendCmd); - if (sendAnswer == null) { - handleManagedVolumesAfterFailedMigration(volumeToPool, destHost); + MigrateWithStorageReceiveCommand receiveCmd = new MigrateWithStorageReceiveCommand(to, volumeToStorageUuid); + MigrateWithStorageReceiveAnswer receiveAnswer = (MigrateWithStorageReceiveAnswer) agentMgr.send(destHost.getId(), receiveCmd); - s_logger.error("Migration with storage of vm " + vm + " to host " + destHost + " failed."); - throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); - } else if (!sendAnswer.getResult()) { - handleManagedVolumesAfterFailedMigration(volumeToPool, destHost); + if (receiveAnswer == null) { + throw new CloudRuntimeException("Error while migrating the vm to host " + destHost); + } else if (!receiveAnswer.getResult()) { + throw new CloudRuntimeException("Error while migrating the vm to host " + destHost + ". Details: " + receiveAnswer.getDetails()); + } - s_logger.error("Migration with storage of vm " + vm + " failed. Details: " + sendAnswer.getDetails()); - throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); - } + MigrateWithStorageSendCommand sendCmd = + new MigrateWithStorageSendCommand(to, receiveAnswer.getVolumeToSr(), receiveAnswer.getNicToNetwork(), receiveAnswer.getToken()); + MigrateWithStorageSendAnswer sendAnswer = (MigrateWithStorageSendAnswer) agentMgr.send(srcHost.getId(), sendCmd); - MigrateWithStorageCompleteCommand command = new MigrateWithStorageCompleteCommand(to); - MigrateWithStorageCompleteAnswer answer = (MigrateWithStorageCompleteAnswer)agentMgr.send(destHost.getId(), command); + if (sendAnswer == null) { + throw new CloudRuntimeException("Error while migrating the vm to host " + destHost); + } else if (!sendAnswer.getResult()) { + throw new CloudRuntimeException("Error while migrating the vm to host " + destHost + ". Details: " + receiveAnswer.getDetails()); + } - if (answer == null) { - s_logger.error("Migration with storage of vm " + vm + " failed."); - throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); - } else if (!answer.getResult()) { - s_logger.error("Migration with storage of vm " + vm + " failed. Details: " + answer.getDetails()); - throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); - } else { - // Update the volume details after migration. - updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos(), srcHost); - } + MigrateWithStorageCompleteCommand command = new MigrateWithStorageCompleteCommand(to); + MigrateWithStorageCompleteAnswer answer = (MigrateWithStorageCompleteAnswer) agentMgr.send(destHost.getId(), command); - return answer; - } catch (OperationTimedoutException e) { - s_logger.error("Error while migrating vm " + vm + " to host " + destHost, e); - throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId()); + if (answer == null) { + throw new CloudRuntimeException("Error while migrating the vm to host " + destHost); + } else if (!answer.getResult()) { + throw new CloudRuntimeException("Error while migrating the vm to host " + destHost + ". Details: " + receiveAnswer.getDetails()); } + + return answer; } private Answer migrateVmWithVolumesWithinCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, Host destHost, Map volumeToPool) @@ -395,12 +528,12 @@ private Answer migrateVmWithVolumesWithinCluster(VMInstanceVO vm, VirtualMachine for (Map.Entry entry : volumeToPool.entrySet()) { VolumeInfo volume = entry.getKey(); VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId())); - StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue()); + StorageFilerTO filerTo = new StorageFilerTO((StoragePool) entry.getValue()); volumeToFilerto.add(new Pair(volumeTo, filerTo)); } MigrateWithStorageCommand command = new MigrateWithStorageCommand(to, volumeToFilerto); - MigrateWithStorageAnswer answer = (MigrateWithStorageAnswer)agentMgr.send(destHost.getId(), command); + MigrateWithStorageAnswer answer = (MigrateWithStorageAnswer) agentMgr.send(destHost.getId(), command); if (answer == null) { s_logger.error("Migration with storage of vm " + vm + " failed."); throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); @@ -422,7 +555,7 @@ private Answer migrateVmWithVolumesWithinCluster(VMInstanceVO vm, VirtualMachine private void updateVolumePathsAfterMigration(Map volumeToPool, List volumeTos, Host srcHost) { for (Map.Entry entry : volumeToPool.entrySet()) { VolumeInfo volumeInfo = entry.getKey(); - StoragePool storagePool = (StoragePool)entry.getValue(); + StoragePool storagePool = (StoragePool) entry.getValue(); boolean updated = false; @@ -455,4 +588,14 @@ private void updateVolumePathsAfterMigration(Map volumeTo } } } + + @Override + public String getConfigComponentName() { + return XenServerStorageMotionStrategy.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{XenCopyAsyncAcrossCluster}; + } } diff --git a/scripts/vm/hypervisor/xenserver/cloudlog b/scripts/vm/hypervisor/xenserver/cloudlog index ed7e6908ad77..f6944b28acfa 100644 --- a/scripts/vm/hypervisor/xenserver/cloudlog +++ b/scripts/vm/hypervisor/xenserver/cloudlog @@ -29,7 +29,7 @@ rotate 20 } -/var/log/cloud/ovstunnel.log /var/log/cloud/ovs-pvlan.log /var/log/cloud/swiftxenserver.log /var/log/cloud/s3xenserver.log /var/log/cloud/storageplugin { +/var/log/cloud/ovstunnel.log /var/log/cloud/ovs-pvlan.log /var/log/cloud/swiftxenserver.log /var/log/cloud/s3xenserver.log /var/log/cloud/storageplugin /var/log/cloud/migrate-unattached-disk.log { daily size 1M rotate 2 diff --git a/scripts/vm/hypervisor/xenserver/migrate-unattached-disk b/scripts/vm/hypervisor/xenserver/migrate-unattached-disk new file mode 100644 index 000000000000..b7b63fdfe2e8 --- /dev/null +++ b/scripts/vm/hypervisor/xenserver/migrate-unattached-disk @@ -0,0 +1,295 @@ +#!/usr/bin/env python + +# A plugin for migrating unattached disks across clusters + +import socket +import logging +from random import randint + +import XenAPIPlugin +import XenAPI + +import cloudstack_pluginlib as lib + +lib.setup_logging('/var/log/cloud/migrate-unattached-disk.log') + + +def log_info(func_name, info_type, args): + logging.info( + '[%s] %s %s: %s', + LOGID, + func_name, + info_type, + args if isinstance(args, str) + else 'args: %s' % ['%s: %s' % (k, v) for k, v in args.items() + if k not in ['remote_username', 'remote_password']] + ) + + +def log_errors(func): + def _wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except XenAPI.Failure, exc: + logging.error( + '[%s] %s: XenAPI.Failure: %s', + LOGID, func.__name__, str(exc) + ) + raise + except lib.PluginError, exc: + logging.error( + '[%s] %s: %s: %s', + LOGID, func.__name__, exc.__class__.__name__, str(exc) + ) + raise + except Exception, exc: + logging.error( + '[%s] %s: %s: %s', + LOGID, func.__name__, exc.__class__.__name__, str(exc) + ) + raise + return _wrapper + + +def ignore_failure(func, *args, **kwargs): + try: + return func(*args, **kwargs) + except XenAPI.Failure: + return None + + +def get_remote_connection(args): + protocol = get_protocol(args) + remote_host = args['remote_host'] + remote_port = get_port(protocol) + remote_username = args['remote_username'] + remote_password = args['remote_password'] + return protocol, remote_host, remote_port, remote_username, remote_password + + +def get_protocol(args): + protocol = args['protocol'] + if protocol not in ['http', 'https']: + raise ValueError('Invalid protocol %s' % protocol) + return protocol + + +def get_port(protocol): + return 443 if protocol == 'https' else 80 + + +def login(protocol, remote_host, remote_port, remote_username, remote_password): + try: + remote_session = \ + XenAPI.Session('%s://%s:%s/' % (protocol, remote_host, remote_port)) + remote_session.login_with_password(remote_username, remote_password) + return remote_session + except socket.gaierror, exn: + logging.debug('gaierror logging in: %s', exn) + + +@log_errors +def migrate_vdi(session, args): + """ + Live migrate a VDI to a remote SR by attaching it to a temporary transport VM. + """ + log_info('migrate_vdi', '## RECEIVED ##', args) + local_vdi_uuid = args['local_vdi_uuid'] + remote_sr_uuid = args['remote_sr_uuid'] + network_uuid = args['network_uuid'] + dest_host_uuid = args['dest_host_uuid'] + vdi_ref = session.xenapi.VDI.get_by_uuid(local_vdi_uuid) + vdi_records = session.xenapi.VDI.get_record(vdi_ref) + log_info('migrate_vdi', + '', + 'VDI: "%s" will be migrated' % vdi_records['name_label']) + new_vm = None + migrated_vm = None + try: + new_vm = session.xenapi.VM.get_by_uuid(create_vm(session)) + create_vbd(session, new_vm, vdi_ref, 'autodetect') + protocol, remote_host, remote_port, remote_username, remote_password = \ + get_remote_connection(args) + + # recreate session each time cause of 400 sessions limits in xenserver + remote_session = login(protocol, remote_host, remote_port, + remote_username, remote_password) + migrated_vm = migrate(session, remote_session, dest_host_uuid, + network_uuid, new_vm, remote_sr_uuid) + + remote_session = login(protocol, remote_host, remote_port, + remote_username, remote_password) + new_vdi_uuid = find_migrated_vdi_uuid(remote_session, migrated_vm) + finally: + if new_vm is not None: + ignore_failure(cleanup, session, new_vm) + if migrated_vm is not None: + remote_session = ignore_failure(login, protocol, remote_host, remote_port, + remote_username, remote_password) + ignore_failure(cleanup, remote_session, migrated_vm) + log_info('migrate_vdi', '## SUCCESS ##', args['local_vdi_uuid']) + return new_vdi_uuid + + +def migrate_rcv(remote_session, dest_host_uuid, network_uuid, opts): + host_ref = remote_session.xenapi.host.get_by_uuid(dest_host_uuid) + network_ref = remote_session.xenapi.network.get_by_uuid(network_uuid) + return remote_session.xenapi.host.migrate_receive(host_ref, network_ref, opts) + + +def migrate(local_session, remote_session, dest_host_uuid, network_uuid, vm_ref, remote_sr_uuid): + opts = { + 'live': 'true', + 'force': 'true', + 'copy': 'false', + } + sr_ref = remote_session.xenapi.SR.get_by_uuid(remote_sr_uuid) + recv = migrate_rcv(remote_session, dest_host_uuid, network_uuid, opts) + vdi_map = map_all_vdis(local_session, vm_ref, sr_ref) + + local_session.xenapi.VM.assert_can_migrate(vm_ref, recv, True, vdi_map, {}, opts) + return local_session.xenapi.VM.migrate_send(vm_ref, recv, True, vdi_map, {}, opts) + + +def cleanup(session, vm_ref): + session.xenapi.VM.destroy(vm_ref) + + +@log_errors +def create_n_attach(session, args): + """ + Create a transport VM and attach a VDI to it. + """ + log_info('create_n_attach', '## RECEIVED ##', args) + local_vdi_uuid = args['local_vdi_uuid'] + vdi_ref = session.xenapi.VDI.get_by_uuid(local_vdi_uuid) + vm_uuid = create_vm(session) + vm_ref = session.xenapi.VM.get_by_uuid(vm_uuid) + vm_name = 'migration-vm-%s' % local_vdi_uuid + session.xenapi.VM.set_name_label(vm_ref, vm_name) + log_info('create_n_attach', + '', + 'VM %s has been created' % vm_name) + + # Create a fake root disk for the migration vm + sr_ref = session.xenapi.VDI.get_SR(vdi_ref) + sr_uuid = session.xenapi.SR.get_uuid(sr_ref) + root_vdi_ref = session.xenapi.VDI.create({'name_label': 'root', + 'name_description': 'migration-vm-root', + 'SR': sr_ref, + 'virtual_size': 1024, + 'type': 'User', + 'sharable': False, + 'read_only': False, + 'other_config': {}}) + + root_vdi_uuid = session.xenapi.VDI.get_uuid(root_vdi_ref) + create_vbd(session, vm_ref, root_vdi_ref, '0') + + # Create a second VBD to attach the local VDI + log_info('create_n_attach', + '', + 'Attaching VDI %s to %s' % (local_vdi_uuid, vm_name)) + create_vbd(session, vm_ref, vdi_ref, '2') + + log_info('create_n_attach', '## SUCCESS ##', vm_name + ' ' + root_vdi_uuid) + return vm_name + ' ' + root_vdi_uuid + + +@log_errors +def create_vbd(session, vm_ref, vdi_ref, userdevice): + try: + session.xenapi.VBD.create({'VM': vm_ref, + 'VDI': vdi_ref, + 'userdevice': userdevice, + 'bootable': True if userdevice == '0' else False, + 'mode': 'RW', + 'type': 'Disk', + 'unpluggable': False, + 'empty': False, + 'other_config': {}, + 'qos_algorithm_type': '', + 'qos_algorithm_params': {}}) + except Exception, exc: + cleanup(session, vm_ref) + raise exc + + +@log_errors +def create_vm(session): + vm_ref = session.xenapi.VM.create({'actions_after_crash': 'destroy', + 'actions_after_reboot': 'restart', + 'actions_after_shutdown': 'destroy', + 'affinity': '', + 'HVM_boot_params': {}, + 'HVM_boot_policy': '', + 'is_a_template': False, + 'memory_dynamic_min': '0', + 'memory_dynamic_max': '0', + 'memory_static_min': '0', + 'memory_static_max': '0', + 'memory_target': '0', + 'name_description': (u'Temporary VM used to migrate' + 'unattached volumes across clusters'), + 'name_label': 'migration-vm', + 'other_config': {}, + 'PCI_bus': '', + 'platform': {'acpi': 'true', 'apic': 'true', 'pae': 'true', + 'viridian': 'true', 'timeoffset': '0'}, + 'PV_args': '', + 'PV_bootloader': '', + 'PV_bootloader_args': '', + 'PV_kernel': '', + 'PV_legacy_args': '', + 'PV_ramdisk': '', + 'recommendations': '', + 'user_version': '1', + 'VCPUs_at_startup': 1, + 'VCPUs_max': 1, + 'VCPUs_params': {}}) + + vm_records = session.xenapi.VM.get_record(vm_ref) + return vm_records['uuid'] + + +def map_all_vdis(session, vm_ref, sr_ref_dest): + vdi_map = {} + for vbd in session.xenapi.VM.get_VBDs(vm_ref): + if session.xenapi.VBD.get_type(vbd) == 'CD': + continue + vdi_ref = ignore_failure(session.xenapi.VBD.get_VDI, vbd) + if not vdi_ref: + continue + vdi_map[vdi_ref] = sr_ref_dest + return vdi_map + + +def find_migrated_vdi_uuid(session, vm_ref): + for vbd in session.xenapi.VM.get_VBDs(vm_ref): + if session.xenapi.VBD.get_type(vbd) == 'CD': + continue + if session.xenapi.VBD.get_userdevice(vbd) == 'xvda': + continue + vdi_ref = ignore_failure(session.xenapi.VBD.get_VDI, vbd) + if not vdi_ref: + continue + return session.xenapi.VDI.get_record(vdi_ref)['uuid'] + return None + + +@log_errors +def migrated_vdi_uuid(session, args): + vm_uuid = args['vm_uuid'] + vm_ref = session.xenapi.VM.get_by_uuid(vm_uuid) + return find_migrated_vdi_uuid(session, vm_ref) + + +LOGID = randint(000000, 999999) + + +if __name__ == '__main__': + XenAPIPlugin.dispatch({ + 'migrate_vdi': migrate_vdi, + 'create_n_attach': create_n_attach, + 'migrated_vdi_uuid': migrated_vdi_uuid, + }) diff --git a/scripts/vm/hypervisor/xenserver/xenserver65/patch b/scripts/vm/hypervisor/xenserver/xenserver65/patch index f18a325f05bf..7b5321922409 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver65/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver65/patch @@ -67,4 +67,5 @@ ovs-get-dhcp-iface.sh=..,0755,/opt/cloud/bin ovs-get-bridge.sh=..,0755,/opt/cloud/bin cloudlog=..,0644,/etc/logrotate.d update_host_passwd.sh=../..,0755,/opt/cloud/bin -logrotate=..,0755,/etc/cron.hourly \ No newline at end of file +logrotate=..,0755,/etc/cron.hourly +migrate-unattached-disk=..,0755,/etc/xapi.d/plugins