diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index 77296365810..bf0ea6cc66d 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -2004,6 +2004,10 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return _host; } + public int getMigrateWait() { + return _migratewait; + } + protected boolean getHostInfo(final Connection conn) throws IllegalArgumentException { try { final Host myself = Host.getByUuid(conn, _host.getUuid()); diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServer610Resource.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServer610Resource.java index b372ef788ed..7f5dfdbb5cf 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServer610Resource.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServer610Resource.java @@ -68,9 +68,7 @@ public class XenServer610Resource extends XenServer600Resource { @Override public Answer executeRequest(final Command cmd) { - if (cmd instanceof MigrateWithStorageCommand) { - return execute((MigrateWithStorageCommand)cmd); - } else if (cmd instanceof MigrateWithStorageReceiveCommand) { + if (cmd instanceof MigrateWithStorageReceiveCommand) { return execute((MigrateWithStorageReceiveCommand)cmd); } else if (cmd instanceof MigrateWithStorageSendCommand) { return execute((MigrateWithStorageSendCommand)cmd); @@ -83,7 +81,7 @@ public class XenServer610Resource extends XenServer600Resource { } } - private List getUpdatedVolumePathsOfMigratedVm(final Connection connection, final VM migratedVm, final DiskTO[] volumes) throws CloudRuntimeException { + public List getUpdatedVolumePathsOfMigratedVm(final Connection connection, final VM migratedVm, final DiskTO[] volumes) throws CloudRuntimeException { final List volumeToList = new ArrayList(); try { diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/CitrixRequestWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/CitrixRequestWrapper.java index e7e238f9597..59d1a5f6361 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/CitrixRequestWrapper.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/CitrixRequestWrapper.java @@ -44,6 +44,7 @@ import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortCommand; import com.cloud.agent.api.MaintainCommand; import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.MigrateWithStorageCommand; import com.cloud.agent.api.ModifySshKeysCommand; import com.cloud.agent.api.ModifyStoragePoolCommand; import com.cloud.agent.api.NetworkRulesSystemVmCommand; @@ -87,6 +88,8 @@ import com.cloud.agent.api.storage.ResizeVolumeCommand; import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; import com.cloud.hypervisor.xenserver.resource.XenServer56FP1Resource; import com.cloud.hypervisor.xenserver.resource.XenServer56Resource; +import com.cloud.hypervisor.xenserver.resource.XenServer610Resource; +import com.cloud.hypervisor.xenserver.resource.XenServer620SP1Resource; import com.cloud.resource.CommandWrapper; import com.cloud.resource.RequestWrapper; import com.cloud.resource.ServerResource; @@ -187,7 +190,12 @@ public class CitrixRequestWrapper extends RequestWrapper { // XenServer620SP1Resource commands final Hashtable, CommandWrapper> xenServer620SP1Commands = new Hashtable, CommandWrapper>(); xenServer620SP1Commands.put(GetGPUStatsCommand.class, new XenServer620SP1GetGPUStatsCommandWrapper()); - resources.put(XenServer56FP1Resource.class, xenServer620SP1Commands); + resources.put(XenServer620SP1Resource.class, xenServer620SP1Commands); + + // XenServer610Resource commands + final Hashtable, CommandWrapper> xenServer610Commands = new Hashtable, CommandWrapper>(); + xenServer610Commands.put(MigrateWithStorageCommand.class, new XenServer610MigrateWithStorageCommandWrapper()); + resources.put(XenServer610Resource.class, xenServer610Commands); } public static CitrixRequestWrapper getInstance() { diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610MigrateWithStorageCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610MigrateWithStorageCommandWrapper.java new file mode 100644 index 00000000000..0d1cac47cc8 --- /dev/null +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610MigrateWithStorageCommandWrapper.java @@ -0,0 +1,135 @@ +// +// 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; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.MigrateWithStorageAnswer; +import com.cloud.agent.api.MigrateWithStorageCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.hypervisor.xenserver.resource.XenServer610Resource; +import com.cloud.hypervisor.xenserver.resource.XsHost; +import com.cloud.hypervisor.xenserver.resource.XsLocalNetwork; +import com.cloud.network.Networks.TrafficType; +import com.cloud.resource.CommandWrapper; +import com.cloud.utils.exception.CloudRuntimeException; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Host; +import com.xensource.xenapi.Network; +import com.xensource.xenapi.SR; +import com.xensource.xenapi.Task; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.VDI; +import com.xensource.xenapi.VIF; +import com.xensource.xenapi.VM; + +public final class XenServer610MigrateWithStorageCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(XenServer610MigrateWithStorageCommandWrapper.class); + + @Override + public Answer execute(final MigrateWithStorageCommand command, final XenServer610Resource xenServer610Resource) { + final Connection connection = xenServer610Resource.getConnection(); + final VirtualMachineTO vmSpec = command.getVirtualMachine(); + final Map volumeToFiler = command.getVolumeToFiler(); + final String vmName = vmSpec.getName(); + Task task = null; + + final XsHost xsHost = xenServer610Resource.getHost(); + final String uuid = xsHost.getUuid(); + try { + xenServer610Resource.prepareISO(connection, vmName); + + // Get the list of networks and recreate VLAN, if required. + for (final NicTO nicTo : vmSpec.getNics()) { + xenServer610Resource.getNetwork(connection, nicTo); + } + + final Map other = new HashMap(); + other.put("live", "true"); + + final XsLocalNetwork nativeNetworkForTraffic = xenServer610Resource.getNativeNetworkForTraffic(connection, TrafficType.Storage, null); + final Network networkForSm = nativeNetworkForTraffic.getNetwork(); + + // Create the vif map. The vm stays in the same cluster so we have to pass an empty vif map. + final Map vifMap = new HashMap(); + final Map vdiMap = new HashMap(); + for (final Map.Entry entry : volumeToFiler.entrySet()) { + vdiMap.put(xenServer610Resource.getVDIbyUuid(connection, entry.getKey().getPath()), xenServer610Resource.getStorageRepository(connection, entry.getValue().getUuid())); + } + + // Get the vm to migrate. + final Set vms = VM.getByNameLabel(connection, vmSpec.getName()); + final VM vmToMigrate = vms.iterator().next(); + + // Check migration with storage is possible. + final Host host = Host.getByUuid(connection, uuid); + final Map token = host.migrateReceive(connection, networkForSm, other); + task = vmToMigrate.assertCanMigrateAsync(connection, token, true, vdiMap, vifMap, other); + try { + // poll every 1 seconds + final long timeout = xenServer610Resource.getMigrateWait() * 1000L; + xenServer610Resource.waitForTask(connection, task, 1000, timeout); + xenServer610Resource.checkForSuccess(connection, task); + } catch (final Types.HandleInvalid e) { + s_logger.error("Error while checking if vm " + vmName + " can be migrated to the destination host " + host, e); + throw new CloudRuntimeException("Error while checking if vm " + vmName + " can be migrated to the " + "destination host " + host, e); + } + + // Migrate now. + task = vmToMigrate.migrateSendAsync(connection, token, true, vdiMap, vifMap, other); + try { + // poll every 1 seconds. + final long timeout = xenServer610Resource.getMigrateWait() * 1000L; + xenServer610Resource.waitForTask(connection, task, 1000, timeout); + xenServer610Resource.checkForSuccess(connection, task); + } catch (final Types.HandleInvalid e) { + s_logger.error("Error while migrating vm " + vmName + " to the destination host " + host, e); + throw new CloudRuntimeException("Error while migrating vm " + vmName + " to the destination host " + host, e); + } + + // Volume paths would have changed. Return that information. + final List volumeToList = xenServer610Resource.getUpdatedVolumePathsOfMigratedVm(connection, vmToMigrate, vmSpec.getDisks()); + vmToMigrate.setAffinity(connection, host); + return new MigrateWithStorageAnswer(command, volumeToList); + } catch (final Exception e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + ". Storage motion failed due to " + e.toString(), e); + return new MigrateWithStorageAnswer(command, e); + } finally { + if (task != null) { + try { + task.destroy(connection); + } catch (final Exception e) { + s_logger.debug("Unable to destroy task " + task.toString() + " on host " + uuid + " due to " + e.toString()); + } + } + } + } +} \ No newline at end of file diff --git a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610WrapperTest.java b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610WrapperTest.java index e8a64de6f58..a708684e4d6 100644 --- a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610WrapperTest.java +++ b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/XenServer610WrapperTest.java @@ -17,25 +17,47 @@ package com.cloud.hypervisor.xenserver.resource.wrapper; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.apache.xmlrpc.XmlRpcException; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.cloud.agent.api.Answer; import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.MigrateWithStorageCommand; import com.cloud.agent.api.SetupCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; import com.cloud.host.HostEnvironment; import com.cloud.hypervisor.xenserver.resource.XenServer610Resource; +import com.cloud.hypervisor.xenserver.resource.XsHost; +import com.cloud.hypervisor.xenserver.resource.XsLocalNetwork; +import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetworkSetupInfo; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Host; +import com.xensource.xenapi.Network; +import com.xensource.xenapi.Types.XenAPIException; @RunWith(PowerMockRunner.class) +@PrepareForTest({Host.class}) public class XenServer610WrapperTest { @Mock @@ -69,4 +91,66 @@ public class XenServer610WrapperTest { assertFalse(answer.getResult()); } + + @Test + public void testMigrateWithStorageCommand() { + final String vmName = "small"; + final String uuid = "206b21a7-c6ec-40e2-b5e2-f861b9612f04"; + + final Connection conn = Mockito.mock(Connection.class); + final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class); + final Map volumeToFiler = new HashMap(); + + final NicTO nicTO1 = Mockito.mock(NicTO.class); + final NicTO nicTO2 = Mockito.mock(NicTO.class); + final NicTO nicTO3 = Mockito.mock(NicTO.class); + final NicTO [] nicTOs = {nicTO1, nicTO2, nicTO3}; + + final XsLocalNetwork nativeNetworkForTraffic = Mockito.mock(XsLocalNetwork.class); + final Network networkForSm = Mockito.mock(Network.class); + + final XsHost xsHost = Mockito.mock(XsHost.class); + final Host host = Mockito.mock(Host.class); + + final MigrateWithStorageCommand gpuStats = new MigrateWithStorageCommand(vmSpec, volumeToFiler); + + final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance(); + assertNotNull(wrapper); + + when(xenServer610Resource.getConnection()).thenReturn(conn); + when(vmSpec.getName()).thenReturn(vmName); + when(vmSpec.getNics()).thenReturn(nicTOs); + + try { + when(xenServer610Resource.getHost()).thenReturn(xsHost); + when(xsHost.getUuid()).thenReturn(uuid); + + when(xenServer610Resource.getNativeNetworkForTraffic(conn, TrafficType.Storage, null)).thenReturn(nativeNetworkForTraffic); + when(nativeNetworkForTraffic.getNetwork()).thenReturn(networkForSm); + } catch (final XenAPIException e) { + fail(e.getMessage()); + } catch (final XmlRpcException e) { + fail(e.getMessage()); + } + + final Answer answer = wrapper.execute(gpuStats, xenServer610Resource); + + verify(xenServer610Resource, times(1)).getConnection(); + + try { + verify(xenServer610Resource, times(1)).prepareISO(conn, vmName); + verify(xenServer610Resource, times(1)).getNetwork(conn, nicTO1); + verify(xenServer610Resource, times(1)).getNetwork(conn, nicTO2); + verify(xenServer610Resource, times(1)).getNetwork(conn, nicTO3); + + verify(xenServer610Resource, times(1)).getNativeNetworkForTraffic(conn, TrafficType.Storage, null); + verify(nativeNetworkForTraffic, times(1)).getNetwork(); + } catch (final XenAPIException e) { + fail(e.getMessage()); + } catch (final XmlRpcException e) { + fail(e.getMessage()); + } + + assertFalse(answer.getResult()); + } } \ No newline at end of file