From 92eaf49f2932e8ed07461fb23f57575ce7a51c17 Mon Sep 17 00:00:00 2001 From: Edison Su Date: Mon, 24 Oct 2011 15:56:23 -0700 Subject: [PATCH] Add storage migration --- .../agent/manager/MockStorageManager.java | 6 +- .../agent/manager/MockStorageManagerImpl.java | 46 ++ .../cloud/agent/manager/MockVmManager.java | 3 + .../agent/manager/MockVmManagerImpl.java | 7 + .../agent/manager/SimulatorManagerImpl.java | 9 + agent/src/com/cloud/agent/mockvm/MockVm.java | 7 + .../com/cloud/api/commands/MigrateVMCmd.java | 47 +- api/src/com/cloud/event/EventTypes.java | 3 +- api/src/com/cloud/storage/StorageService.java | 2 + api/src/com/cloud/storage/Volume.java | 64 +-- api/src/com/cloud/vm/UserVmService.java | 6 +- api/src/com/cloud/vm/VirtualMachine.java | 2 + core/src/com/cloud/storage/VolumeVO.java | 36 +- .../src/com/cloud/api/ApiResponseHelper.java | 8 +- .../baremetal/BareMetalVmManagerImpl.java | 4 +- .../cloud/capacity/CapacityManagerImpl.java | 8 +- .../network/ovs/OvsNetworkManagerImpl.java | 4 +- .../security/SecurityGroupManagerImpl.java | 4 +- .../src/com/cloud/storage/StorageManager.java | 19 +- .../com/cloud/storage/StorageManagerImpl.java | 447 ++++++++++++------ .../src/com/cloud/storage/dao/VolumeDao.java | 11 +- .../com/cloud/storage/dao/VolumeDaoImpl.java | 62 ++- .../storage/snapshot/SnapshotManagerImpl.java | 71 ++- .../src/com/cloud/vm/UserVmManagerImpl.java | 40 +- .../src/com/cloud/vm/UserVmStateListener.java | 4 +- .../com/cloud/vm/VirtualMachineManager.java | 3 + .../cloud/vm/VirtualMachineManagerImpl.java | 51 +- .../com/cloud/vm/dao/VMInstanceDaoImpl.java | 9 +- setup/db/create-schema.sql | 6 +- tools/testClient/asyncJobMgr.py | 4 +- tools/testClient/cloudstackConnection.py | 1 + utils/src/com/cloud/utils/fsm/StateDao.java | 2 +- .../com/cloud/utils/fsm/StateListener.java | 6 +- .../com/cloud/utils/fsm/StateMachine2.java | 9 +- .../src/com/cloud/utils/fsm/StateObject.java | 1 - 35 files changed, 735 insertions(+), 277 deletions(-) diff --git a/agent-simulator/src/com/cloud/agent/manager/MockStorageManager.java b/agent-simulator/src/com/cloud/agent/manager/MockStorageManager.java index 0fa9f7b74d7..fb6cb74d61f 100644 --- a/agent-simulator/src/com/cloud/agent/manager/MockStorageManager.java +++ b/agent-simulator/src/com/cloud/agent/manager/MockStorageManager.java @@ -27,6 +27,8 @@ import com.cloud.agent.api.ModifyStoragePoolCommand; import com.cloud.agent.api.SecStorageSetupCommand; import com.cloud.agent.api.SecStorageVMSetupCommand; import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.agent.api.storage.CopyVolumeAnswer; +import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.CreateAnswer; import com.cloud.agent.api.storage.CreateCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; @@ -77,5 +79,7 @@ public interface MockStorageManager extends Manager { public Answer CreatePrivateTemplateFromVolume(CreatePrivateTemplateFromVolumeCommand cmd); - StoragePoolInfo getLocalStorage(String hostGuid, Long storageSize); + StoragePoolInfo getLocalStorage(String hostGuid, Long storageSize); + + CopyVolumeAnswer CopyVolume(CopyVolumeCommand cmd); } diff --git a/agent-simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java b/agent-simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java index a443406a137..94e1f067248 100644 --- a/agent-simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java +++ b/agent-simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java @@ -44,6 +44,8 @@ import com.cloud.agent.api.SecStorageSetupAnswer; import com.cloud.agent.api.SecStorageSetupCommand; import com.cloud.agent.api.SecStorageVMSetupCommand; import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.agent.api.storage.CopyVolumeAnswer; +import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.CreateAnswer; import com.cloud.agent.api.storage.CreateCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; @@ -669,6 +671,50 @@ public class MockStorageManagerImpl implements MockStorageManager { return new CreatePrivateTemplateAnswer(cmd, true, "", template.getName(), template.getSize(), template.getSize(), template.getName(), ImageFormat.QCOW2); } + + @Override + public CopyVolumeAnswer CopyVolume(CopyVolumeCommand cmd) { + boolean toSecondaryStorage = cmd.toSecondaryStorage(); + MockSecStorageVO sec = _mockSecStorageDao.findByUrl(cmd.getSecondaryStorageURL()); + if (sec == null) { + return new CopyVolumeAnswer(cmd, false, "can't find secondary storage", null, null); + } + MockStoragePoolVO primaryStorage = _mockStoragePoolDao.findByUuid(cmd.getPool().getUuid()); + if (primaryStorage == null) { + return new CopyVolumeAnswer(cmd, false, "Can't find primary storage", null, null); + } + + MockVolumeVO volume = _mockVolumeDao.findByStoragePathAndType(cmd.getVolumePath()); + if (volume == null) { + return new CopyVolumeAnswer(cmd, false, "cant' find volume" + cmd.getVolumePath(), null, null); + } + + String name = UUID.randomUUID().toString(); + if (toSecondaryStorage) { + + MockVolumeVO vol = new MockVolumeVO(); + + vol.setName(name); + vol.setPath(sec.getMountPoint() + name); + vol.setPoolId(sec.getId()); + vol.setSize(volume.getSize()); + vol.setStatus(Status.DOWNLOADED); + vol.setType(MockVolumeType.VOLUME); + vol = _mockVolumeDao.persist(vol); + return new CopyVolumeAnswer(cmd, true, null, sec.getMountPoint(), vol.getPath()); + } + else { + MockVolumeVO vol = new MockVolumeVO(); + vol.setName(name); + vol.setPath(primaryStorage.getMountPoint() + name); + vol.setPoolId(primaryStorage.getId()); + vol.setSize(volume.getSize()); + vol.setStatus(Status.DOWNLOADED); + vol.setType(MockVolumeType.VOLUME); + vol = _mockVolumeDao.persist(vol); + return new CopyVolumeAnswer(cmd, true, null, primaryStorage.getMountPoint(), vol.getPath()); + } + } } diff --git a/agent-simulator/src/com/cloud/agent/manager/MockVmManager.java b/agent-simulator/src/com/cloud/agent/manager/MockVmManager.java index 474a0a97b23..b075b59d846 100644 --- a/agent-simulator/src/com/cloud/agent/manager/MockVmManager.java +++ b/agent-simulator/src/com/cloud/agent/manager/MockVmManager.java @@ -13,6 +13,8 @@ import java.util.Set; import com.cloud.agent.api.Answer; import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.CleanupNetworkRulesCmd; +import com.cloud.agent.api.GetDomRVersionAnswer; +import com.cloud.agent.api.GetDomRVersionCmd; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortCommand; import com.cloud.agent.api.MigrateAnswer; @@ -80,5 +82,6 @@ public interface MockVmManager extends Manager { HashMap> syncNetworkGroups(SimulatorInfo info); SecurityIngressRuleAnswer AddSecurityIngressRules(SecurityIngressRulesCmd cmd, SimulatorInfo info); MigrateAnswer Migrate(MigrateCommand cmd, SimulatorInfo info); + GetDomRVersionAnswer getDomRVersion(GetDomRVersionCmd cmd); } diff --git a/agent-simulator/src/com/cloud/agent/manager/MockVmManagerImpl.java b/agent-simulator/src/com/cloud/agent/manager/MockVmManagerImpl.java index fa46b1536d3..c60f83ee719 100644 --- a/agent-simulator/src/com/cloud/agent/manager/MockVmManagerImpl.java +++ b/agent-simulator/src/com/cloud/agent/manager/MockVmManagerImpl.java @@ -21,6 +21,8 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.CheckVirtualMachineAnswer; import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.CleanupNetworkRulesCmd; +import com.cloud.agent.api.GetDomRVersionAnswer; +import com.cloud.agent.api.GetDomRVersionCmd; import com.cloud.agent.api.GetVmStatsAnswer; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortAnswer; @@ -351,6 +353,11 @@ public class MockVmManagerImpl implements MockVmManager { public Answer WatchConsoleProxyLoad(WatchConsoleProxyLoadCommand cmd) { return Answer.createUnsupportedCommandAnswer(cmd); } + + @Override + public GetDomRVersionAnswer getDomRVersion(GetDomRVersionCmd cmd) { + return new GetDomRVersionAnswer(cmd, null, null, null); + } @Override public SecurityIngressRuleAnswer AddSecurityIngressRules(SecurityIngressRulesCmd cmd, SimulatorInfo info) { diff --git a/agent-simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java b/agent-simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java index 5e45031df39..d4e4209b007 100644 --- a/agent-simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java +++ b/agent-simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java @@ -17,6 +17,7 @@ import com.cloud.agent.api.AttachVolumeCommand; import com.cloud.agent.api.BackupSnapshotCommand; import com.cloud.agent.api.CheckHealthCommand; import com.cloud.agent.api.CleanupNetworkRulesCmd; +import com.cloud.agent.api.ClusterSyncCommand; import com.cloud.agent.api.Command; import com.cloud.agent.api.ComputeChecksumCommand; import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; @@ -25,6 +26,7 @@ import com.cloud.agent.api.CreateStoragePoolCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteSnapshotBackupCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.agent.api.GetDomRVersionCmd; import com.cloud.agent.api.GetHostStatsCommand; import com.cloud.agent.api.GetStorageStatsCommand; import com.cloud.agent.api.GetVmStatsCommand; @@ -52,6 +54,7 @@ import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; import com.cloud.agent.api.routing.SetStaticNatRulesCommand; import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.CreateCommand; import com.cloud.agent.api.storage.DeleteTemplateCommand; import com.cloud.agent.api.storage.DestroyCommand; @@ -260,6 +263,12 @@ public class SimulatorManagerImpl implements SimulatorManager { return _mockAgentMgr.MaintainCommand((MaintainCommand)cmd); } else if (cmd instanceof GetVmStatsCommand) { return _mockVmMgr.getVmStats((GetVmStatsCommand)cmd); + } else if (cmd instanceof GetDomRVersionCmd) { + return _mockVmMgr.getDomRVersion((GetDomRVersionCmd)cmd); + } else if (cmd instanceof ClusterSyncCommand) { + return new Answer(cmd); + } else if (cmd instanceof CopyVolumeCommand) { + return _mockStorageMgr.CopyVolume((CopyVolumeCommand)cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } diff --git a/agent/src/com/cloud/agent/mockvm/MockVm.java b/agent/src/com/cloud/agent/mockvm/MockVm.java index ba43d9066ac..56a39d76c7c 100644 --- a/agent/src/com/cloud/agent/mockvm/MockVm.java +++ b/agent/src/com/cloud/agent/mockvm/MockVm.java @@ -70,6 +70,13 @@ public class MockVm { public int getVncPort() { return vncPort; + } + public static void main(String[] args) { + long i = 10; + Long l = null; + if (i == l) { + System.out.print("fdfd"); + } } } diff --git a/api/src/com/cloud/api/commands/MigrateVMCmd.java b/api/src/com/cloud/api/commands/MigrateVMCmd.java index d697e6d76d2..f502e169644 100644 --- a/api/src/com/cloud/api/commands/MigrateVMCmd.java +++ b/api/src/com/cloud/api/commands/MigrateVMCmd.java @@ -33,6 +33,7 @@ import com.cloud.exception.ManagementServerException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; import com.cloud.host.Host; +import com.cloud.storage.StoragePool; import com.cloud.user.Account; import com.cloud.user.UserContext; import com.cloud.uservm.UserVm; @@ -48,12 +49,14 @@ public class MigrateVMCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name=ApiConstants.HOST_ID, type=CommandType.LONG, required=true, description="destination Host ID to migrate VM to") + @Parameter(name=ApiConstants.HOST_ID, type=CommandType.LONG, required=false, description="destination Host ID to migrate VM to") private Long hostId; @Parameter(name=ApiConstants.VIRTUAL_MACHINE_ID, type=CommandType.LONG, required=true, description="the ID of the virtual machine") private Long virtualMachineId; + @Parameter(name=ApiConstants.STORAGE_ID, type=CommandType.LONG, required=false, description="destination storage pool ID to migrate VM to") + private Long storageId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -66,6 +69,10 @@ public class MigrateVMCmd extends BaseAsyncCmd { public Long getVirtualMachineId() { return virtualMachineId; } + + public Long getStoragePoolId() { + return storageId; + } ///////////////////////////////////////////////////// @@ -99,18 +106,44 @@ public class MigrateVMCmd extends BaseAsyncCmd { @Override public void execute(){ + if (getHostId() == null && getStoragePoolId() == null) { + throw new InvalidParameterValueException("either hostId or storageId must be specified"); + } + + if (getHostId() != null && getStoragePoolId() != null) { + throw new InvalidParameterValueException("only one of hostId and storageId can be specified"); + } + UserVm userVm = _userVmService.getUserVm(getVirtualMachineId()); if (userVm == null) { throw new InvalidParameterValueException("Unable to find the VM by id=" + getVirtualMachineId()); } - Host destinationHost = _resourceService.getHost(getHostId()); - if (destinationHost == null) { - throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId()); - } - try{ + Host destinationHost = null; + if (getHostId() != null) { + destinationHost = _resourceService.getHost(getHostId()); + if (destinationHost == null) { + throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId()); + } UserContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: "+ getHostId()); - VirtualMachine migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), destinationHost); + } + + StoragePool destStoragePool = null; + if (getStoragePoolId() != null) { + destStoragePool = _storageService.getStoragePool(getStoragePoolId()); + if (destStoragePool == null) { + throw new InvalidParameterValueException("Unable to find the storage pool to migrate the VM"); + } + UserContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to storage pool Id: "+ getStoragePoolId()); + } + + try{ + VirtualMachine migratedVm = null; + if (getHostId() != null) { + migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), destinationHost); + } else if (getStoragePoolId() != null) { + migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), destStoragePool); + } if (migratedVm != null) { UserVmResponse response = _responseGenerator.createUserVmResponse("virtualmachine", (UserVm)migratedVm).get(0); response.setResponseName(getCommandName()); diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 20d95d05bbe..502bf798da6 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -104,7 +104,8 @@ public class EventTypes { public static final String EVENT_VOLUME_ATTACH = "VOLUME.ATTACH"; public static final String EVENT_VOLUME_DETACH = "VOLUME.DETACH"; public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT"; - public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD"; + public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD"; + public static final String EVENT_VOLUME_MIGRATE = "VOLUME.MIGRATE"; // Domains public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE"; diff --git a/api/src/com/cloud/storage/StorageService.java b/api/src/com/cloud/storage/StorageService.java index 95658af1238..0c2754e25d4 100644 --- a/api/src/com/cloud/storage/StorageService.java +++ b/api/src/com/cloud/storage/StorageService.java @@ -18,6 +18,7 @@ package com.cloud.storage; import java.net.UnknownHostException; +import java.util.List; import com.cloud.api.commands.CancelPrimaryStorageMaintenanceCmd; import com.cloud.api.commands.CreateStoragePoolCmd; @@ -105,4 +106,5 @@ public interface StorageService { public StoragePool getStoragePool(long id); + Volume migrateVolume(Long volumeId, Long storagePoolId) throws ConcurrentOperationException; } diff --git a/api/src/com/cloud/storage/Volume.java b/api/src/com/cloud/storage/Volume.java index 37c48aeeb14..1bea520e148 100755 --- a/api/src/com/cloud/storage/Volume.java +++ b/api/src/com/cloud/storage/Volume.java @@ -25,16 +25,22 @@ import com.cloud.acl.ControlledEntity; import com.cloud.template.BasedOn; import com.cloud.utils.fsm.FiniteState; import com.cloud.utils.fsm.StateMachine; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.utils.fsm.StateObject; +import com.cloud.vm.VirtualMachine; -public interface Volume extends ControlledEntity, BasedOn { +public interface Volume extends ControlledEntity, BasedOn, StateObject { enum Type { UNKNOWN, ROOT, SWAP, DATADISK, ISO }; - enum State implements FiniteState { + enum State { Allocated("The volume is allocated but has not been created yet."), Creating("The volume is being created. getPoolId() should reflect the pool where it is being created."), Ready("The volume is ready to be used."), + Migrating("The volume is migrating to other storage pool"), + Snapshotting("There is a snapshot created on this volume, not backed up to secondary storage yet"), + Expunging("The volume is being expunging"), Destroy("The volume is destroyed, and can't be recovered."); String _description; @@ -43,46 +49,44 @@ public interface Volume extends ControlledEntity, BasedOn { _description = description; } - @Override - public StateMachine getStateMachine() { + public static StateMachine2 getStateMachine() { return s_fsm; } - @Override - public State getNextState(Event event) { - return s_fsm.getNextState(this, event); - } - - @Override - public List getFromStates(Event event) { - return s_fsm.getFromStates(this, event); - } - - @Override - public Set getPossibleEvents() { - return s_fsm.getPossibleEvents(this); - } - - @Override public String getDescription() { return _description; } - private final static StateMachine s_fsm = new StateMachine(); + private final static StateMachine2 s_fsm = new StateMachine2(); static { - s_fsm.addTransition(Allocated, Event.Create, Creating); - s_fsm.addTransition(Allocated, Event.Destroy, Destroy); + s_fsm.addTransition(Allocated, Event.CreateRequested, Creating); + s_fsm.addTransition(Allocated, Event.DestroyRequested, Destroy); s_fsm.addTransition(Creating, Event.OperationRetry, Creating); s_fsm.addTransition(Creating, Event.OperationFailed, Allocated); s_fsm.addTransition(Creating, Event.OperationSucceeded, Ready); - s_fsm.addTransition(Creating, Event.Destroy, Destroy); - s_fsm.addTransition(Creating, Event.Create, Creating); - s_fsm.addTransition(Ready, Event.Destroy, Destroy); + s_fsm.addTransition(Creating, Event.DestroyRequested, Destroy); + s_fsm.addTransition(Creating, Event.CreateRequested, Creating); + s_fsm.addTransition(Ready, Event.DestroyRequested, Destroy); + s_fsm.addTransition(Destroy, Event.ExpungingRequested, Expunging); + s_fsm.addTransition(Ready, Event.SnapshotRequested, Snapshotting); + s_fsm.addTransition(Snapshotting, Event.OperationSucceeded, Ready); + s_fsm.addTransition(Snapshotting, Event.OperationFailed, Ready); + s_fsm.addTransition(Ready, Event.MigrationRequested, Migrating); + s_fsm.addTransition(Migrating, Event.OperationSucceeded, Ready); + s_fsm.addTransition(Migrating, Event.OperationFailed, Ready); + s_fsm.addTransition(Destroy, Event.OperationSucceeded, Destroy); } } enum Event { - Create, OperationFailed, OperationSucceeded, OperationRetry, Destroy; + CreateRequested, + OperationFailed, + OperationSucceeded, + OperationRetry, + MigrationRequested, + SnapshotRequested, + DestroyRequested, + ExpungingRequested; } long getId(); @@ -133,4 +137,10 @@ public interface Volume extends ControlledEntity, BasedOn { String getChainInfo(); boolean isRecreatable(); + + public long getUpdatedCount(); + + public void incrUpdatedCount(); + + public Date getUpdated(); } diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index 655b972315b..79826f64e64 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -48,6 +48,7 @@ import com.cloud.exception.VirtualMachineMigrationException; import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.offering.ServiceOffering; +import com.cloud.storage.StoragePool; import com.cloud.storage.Volume; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; @@ -358,11 +359,12 @@ public interface UserVmService { /** * Migrate the given VM to the destination host provided. The API returns the migrated VM if migration succeeds. Only Root * Admin can migrate a VM. - * + * @param destinationStorage TODO * @param Long vmId * vmId of The VM to migrate * @param Host * destinationHost to migrate the VM + * * @return VirtualMachine migrated VM * @throws ManagementServerException * in case we get error finding the VM or host or access errors or other internal errors. @@ -376,4 +378,6 @@ public interface UserVmService { VirtualMachine migrateVirtualMachine(Long vmId, Host destinationHost) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; UserVm moveVMToUser(MoveUserVMCmd moveUserVMCmd) throws ResourceAllocationException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException ; + + VirtualMachine vmStorageMigration(Long vmId, StoragePool destPool); } diff --git a/api/src/com/cloud/vm/VirtualMachine.java b/api/src/com/cloud/vm/VirtualMachine.java index 7568c984325..c416c07f1fb 100755 --- a/api/src/com/cloud/vm/VirtualMachine.java +++ b/api/src/com/cloud/vm/VirtualMachine.java @@ -79,6 +79,7 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, StateObject s_fsm.addTransition(State.Stopped, VirtualMachine.Event.OperationFailed, State.Stopped); s_fsm.addTransition(State.Stopped, VirtualMachine.Event.ExpungeOperation, State.Expunging); s_fsm.addTransition(State.Stopped, VirtualMachine.Event.AgentReportShutdowned, State.Stopped); + s_fsm.addTransition(State.Stopped, VirtualMachine.Event.StorageMigrationRequested, State.Migrating); s_fsm.addTransition(State.Starting, VirtualMachine.Event.OperationRetry, State.Starting); s_fsm.addTransition(State.Starting, VirtualMachine.Event.OperationSucceeded, State.Running); s_fsm.addTransition(State.Starting, VirtualMachine.Event.OperationFailed, State.Stopped); @@ -160,6 +161,7 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, StateObject AgentReportStopped, AgentReportRunning, MigrationRequested, + StorageMigrationRequested, ExpungeOperation, OperationSucceeded, OperationFailed, diff --git a/core/src/com/cloud/storage/VolumeVO.java b/core/src/com/cloud/storage/VolumeVO.java index ea61cd2ab42..523feb31323 100755 --- a/core/src/com/cloud/storage/VolumeVO.java +++ b/core/src/com/cloud/storage/VolumeVO.java @@ -49,7 +49,10 @@ public class VolumeVO implements Volume { @Column(name = "pool_id") Long poolId; - + + @Column(name = "last_pool_id") + Long lastPoolId; + @Column(name = "account_id") long accountId; @@ -110,6 +113,9 @@ public class VolumeVO implements Volume { @Column(name = "updated") @Temporal(value = TemporalType.TIMESTAMP) Date updated; + + @Column(name="update_count", updatable = true, nullable=false) + protected long updatedCount; // This field should be updated everytime the state is updated. There's no set method in the vo object because it is done with in the dao code. @Column(name = "recreatable") boolean recreatable; @@ -144,6 +150,7 @@ public class VolumeVO implements Volume { this.podId = podId; this.dataCenterId = dcId; this.volumeType = vType; + this.state = Volume.State.Allocated; this.recreatable = false; } @@ -162,6 +169,20 @@ public class VolumeVO implements Volume { this.deviceId = that.getDeviceId(); } + @Override + public long getUpdatedCount() { + return this.updatedCount; + } + + @Override + public void incrUpdatedCount() { + this.updatedCount++; + } + + public void decrUpdatedCount() { + this.updatedCount--; + } + @Override public boolean isRecreatable() { return recreatable; @@ -342,6 +363,7 @@ public class VolumeVO implements Volume { this.poolId = poolId; } + @Override public Date getUpdated() { return updated; } @@ -351,10 +373,6 @@ public class VolumeVO implements Volume { return state; } - public void setState(State state) { - this.state = state; - } - public void setUpdated(Date updated) { this.updated = updated; } @@ -381,6 +399,14 @@ public class VolumeVO implements Volume { public void setChainInfo(String chainInfo) { this.chainInfo = chainInfo; } + + public Long getLastPoolId() { + return this.lastPoolId; + } + + public void setLastPoolId(Long poolId) { + this.lastPoolId = poolId; + } @Override public int hashCode() { diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index c449232e910..4aa0a0c0c7c 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -2376,9 +2376,11 @@ public class ApiResponseHelper implements ResponseGenerator { sgr.setDescription(sgd.getDescription()); Account account = ApiDBUtils.findAccountByNameDomain(sgd.getAccountName(), sgd.getDomainId()); - populateAccount(sgr, account.getId()); - populateDomain(sgr, sgd.getDomainId()); - + if (account != null) { + populateAccount(sgr, account.getId()); + populateDomain(sgr, sgd.getDomainId()); + } + sgr.setObjectName(sgd.getObjectName()); securityGroupResponse.add(sgr); } diff --git a/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java b/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java index ef1e7684e31..95678965dff 100755 --- a/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java +++ b/server/src/com/cloud/baremetal/BareMetalVmManagerImpl.java @@ -510,12 +510,12 @@ public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMet } @Override - public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Long id) { + public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Object opaque) { return true; } @Override - public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Long id) { + public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Object opaque) { if (newState != State.Starting && newState != State.Error && newState != State.Expunging) { return true; } diff --git a/server/src/com/cloud/capacity/CapacityManagerImpl.java b/server/src/com/cloud/capacity/CapacityManagerImpl.java index a06c79b7092..b0587db7520 100755 --- a/server/src/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/com/cloud/capacity/CapacityManagerImpl.java @@ -50,6 +50,7 @@ import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; import com.cloud.utils.component.Inject; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; @@ -530,15 +531,18 @@ public class CapacityManagerImpl implements CapacityManager, StateListener hosts = (Pair)opaque; + Long oldHostId = hosts.first(); s_logger.debug("VM state transitted from :" + oldState + " to " + newState + " with event: " + event + "vm's original host id: " + vm.getLastHostId() + " new host id: " + vm.getHostId() + " host id before state transition: " + oldHostId); diff --git a/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java b/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java index cd27ce9a100..08b6294ede5 100644 --- a/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java +++ b/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java @@ -110,7 +110,7 @@ public class OvsNetworkManagerImpl implements OvsNetworkManager { } @Override - public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vm, boolean status, Long oldHostId) { + public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vm, boolean status, Object opaque) { if (!_isEnabled || !status || (vm.getType() != VirtualMachine.Type.User && vm.getType() != VirtualMachine.Type.DomainRouter)) { return false; } @@ -123,7 +123,7 @@ public class OvsNetworkManagerImpl implements OvsNetworkManager { } @Override - public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vm, boolean status, Long id) { + public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vm, boolean status, Object opaque) { if (!_isEnabled || !status || (vm.getType() != VirtualMachine.Type.User && vm.getType() != VirtualMachine.Type.DomainRouter)) { return false; } diff --git a/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java b/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java index 63516f0af63..b9570e4bcd7 100755 --- a/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java +++ b/server/src/com/cloud/network/security/SecurityGroupManagerImpl.java @@ -1243,12 +1243,12 @@ public class SecurityGroupManagerImpl implements SecurityGroupManager, SecurityG } @Override - public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Long id) { + public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Object opaque) { return true; } @Override - public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vm, boolean status, Long oldHostId) { + public boolean postStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vm, boolean status, Object opaque) { if (!status) { return false; } diff --git a/server/src/com/cloud/storage/StorageManager.java b/server/src/com/cloud/storage/StorageManager.java index 125e45653a4..8d7f7b26748 100755 --- a/server/src/com/cloud/storage/StorageManager.java +++ b/server/src/com/cloud/storage/StorageManager.java @@ -33,10 +33,12 @@ import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.service.ServiceOfferingVO; +import com.cloud.storage.Volume.Event; import com.cloud.storage.Volume.Type; import com.cloud.user.Account; import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; +import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.DiskProfile; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -86,9 +88,10 @@ public interface StorageManager extends Manager { * @param destPoolDcId * @param destPoolPodId * @param destPoolClusterId - * @return VolumeVO + * @return VolumeVO + * @throws ConcurrentOperationException */ - VolumeVO moveVolume(VolumeVO volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType); + VolumeVO moveVolume(VolumeVO volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) throws ConcurrentOperationException; /** * Create a volume based on the given criteria @@ -110,9 +113,10 @@ public interface StorageManager extends Manager { /** * Marks the specified volume as destroyed in the management server database. The expunge thread will delete the volume from its storage pool. - * @param volume + * @param volume + * @return */ - void destroyVolume(VolumeVO volume) throws ConcurrentOperationException; + boolean destroyVolume(VolumeVO volume) throws ConcurrentOperationException; /** Create capacity entries in the op capacity table * @param storagePool @@ -207,4 +211,11 @@ public interface StorageManager extends Manager { VMTemplateHostVO getTemplateHostRef(long zoneId, long tmpltId, boolean readyOnly); + boolean StorageMigration( + VirtualMachineProfile vm, + StoragePool destPool) throws ConcurrentOperationException; + + boolean stateTransitTo(Volume vol, Event event) + throws NoTransitionException; + } diff --git a/server/src/com/cloud/storage/StorageManagerImpl.java b/server/src/com/cloud/storage/StorageManagerImpl.java index 34bfce94c10..5834b4befa8 100755 --- a/server/src/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/com/cloud/storage/StorageManagerImpl.java @@ -123,6 +123,7 @@ import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.Volume.Event; import com.cloud.storage.Volume.Type; import com.cloud.storage.allocator.StoragePoolAllocator; import com.cloud.storage.dao.DiskOfferingDao; @@ -166,6 +167,8 @@ import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExecutionException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.utils.fsm.StateMachine2; import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.DiskProfile; import com.cloud.vm.DomainRouterVO; @@ -310,6 +313,7 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag protected float _overProvisioningFactor = 1; private long _maxVolumeSizeInGb; private long _serverId; + private StateMachine2 _volStateMachine; public boolean share(VMInstanceVO vm, List vols, HostVO host, boolean cancelPreviousShare) throws StorageUnavailableException { @@ -495,6 +499,12 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag String volumeFolder = null; + try { + stateTransitTo(volume, Volume.Event.CreateRequested); + } catch (NoTransitionException e) { + s_logger.debug(e.toString()); + return null; + } // Create the Volume object and save it so that we can return it to the user Account account = _accountDao.findById(volume.getAccountId()); @@ -560,27 +570,31 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag throw new CloudRuntimeException(msg); } - // Update the volume in the database - Transaction txn = Transaction.currentTxn(); - txn.start(); + createdVolume = _volsDao.findById(volumeId); - if (success) { - createdVolume.setPodId(pod.first().getId()); - createdVolume.setPoolId(pool.getId()); - createdVolume.setPoolType(pool.getPoolType()); - createdVolume.setFolder(volumeFolder); - createdVolume.setPath(volumeUUID); - createdVolume.setDomainId(account.getDomainId()); - createdVolume.setState(Volume.State.Ready); - } else { - createdVolume.setState(Volume.State.Destroy); + try { + if (success) { + createdVolume.setPodId(pod.first().getId()); + createdVolume.setPoolId(pool.getId()); + createdVolume.setPoolType(pool.getPoolType()); + createdVolume.setFolder(volumeFolder); + createdVolume.setPath(volumeUUID); + createdVolume.setDomainId(account.getDomainId()); + stateTransitTo(createdVolume, Volume.Event.OperationSucceeded); + } + } catch (NoTransitionException e) { + s_logger.debug("Failed to update volume state: " + e.toString()); + return null; } - - _volsDao.update(volumeId, createdVolume); - txn.commit(); + return new Pair(createdVolume, details); } + + @Override + public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException { + return _volStateMachine.transitTo(vol, event, null, _volsDao); + } protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId) { @@ -589,7 +603,9 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag SnapshotVO snapshot = _snapshotDao.findById(snapshotId); // Precondition: snapshot is not null and not removed. Pair volumeDetails = createVolumeFromSnapshot(volume, snapshot); - createdVolume = volumeDetails.first(); + if (volumeDetails != null) { + createdVolume = volumeDetails.first(); + } return createdVolume; } @@ -670,6 +686,13 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag StoragePoolVO pool = null; final HashSet avoidPools = new HashSet(avoids); + try { + stateTransitTo(volume, Volume.Event.CreateRequested); + } catch (NoTransitionException e) { + s_logger.debug("Unable to update volume state: " + e.toString()); + return null; + } + if (diskOffering != null && diskOffering.isCustomized()) { diskOffering.setDiskSize(size); } @@ -755,8 +778,13 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag volume.setPoolType(pool.getPoolType()); volume.setPoolId(pool.getId()); volume.setPodId(pod.getId()); - volume.setState(Volume.State.Ready); - return _volsDao.persist(volume); + try { + stateTransitTo(volume, Volume.Event.OperationSucceeded); + } catch (NoTransitionException e) { + s_logger.debug("Unable to update volume state: " + e.toString()); + return null; + } + return volume; } } @@ -1062,6 +1090,7 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag } protected StorageManagerImpl() { + _volStateMachine = Volume.State.getStateMachine(); } @Override @@ -1511,7 +1540,7 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag } @Override - public VolumeVO moveVolume(VolumeVO volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) { + public VolumeVO moveVolume(VolumeVO volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) throws ConcurrentOperationException { List snapshots = _snapshotDao.listByVolumeId(volume.getId()); if (snapshots != null && snapshots.size() > 0) { @@ -1532,7 +1561,6 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag HostPodVO destPoolPod = _podDao.findById(destPoolPodId); StoragePoolVO destPool = findStoragePool(dskCh, destPoolDataCenter, destPoolPod, destPoolClusterId, null, new HashSet()); String secondaryStorageURL = getSecondaryStorageURL(volume.getDataCenterId()); - String secondaryStorageVolumePath = null; if (destPool == null) { throw new CloudRuntimeException("Failed to find a storage pool with enough capacity to move the volume to."); @@ -1541,56 +1569,9 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag throw new CloudRuntimeException("Failed to find secondary storage."); } - StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId()); - - // Copy the volume from the source storage pool to secondary storage - CopyVolumeCommand cvCmd = new CopyVolumeCommand(volume.getId(), volume.getPath(), srcPool, secondaryStorageURL, true, _copyvolumewait); - CopyVolumeAnswer cvAnswer; - try { - cvAnswer = (CopyVolumeAnswer) sendToPool(srcPool, cvCmd); - } catch (StorageUnavailableException e1) { - throw new CloudRuntimeException("Failed to copy the volume from the source primary storage pool to secondary storage.", e1); - } - - if (cvAnswer == null || !cvAnswer.getResult()) { - throw new CloudRuntimeException("Failed to copy the volume from the source primary storage pool to secondary storage."); - } - - secondaryStorageVolumePath = cvAnswer.getVolumePath(); - - // Copy the volume from secondary storage to the destination storage - // pool - cvCmd = new CopyVolumeCommand(volume.getId(), secondaryStorageVolumePath, destPool, secondaryStorageURL, false, _copyvolumewait); - try { - cvAnswer = (CopyVolumeAnswer) sendToPool(destPool, cvCmd); - } catch (StorageUnavailableException e1) { - throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool."); - } - - if (cvAnswer == null || !cvAnswer.getResult()) { - throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool."); - } - - String destPrimaryStorageVolumePath = cvAnswer.getVolumePath(); - String destPrimaryStorageVolumeFolder = cvAnswer.getVolumeFolder(); - // Delete the volume on the source storage pool - final DestroyCommand cmd = new DestroyCommand(srcPool, volume, null); - - volume.setPath(destPrimaryStorageVolumePath); - volume.setFolder(destPrimaryStorageVolumeFolder); - volume.setPodId(destPool.getPodId()); - volume.setPoolId(destPool.getId()); - _volsDao.update(volume.getId(), volume); - - Answer destroyAnswer = null; - try { - destroyAnswer = sendToPool(srcPool, cmd); - } catch (StorageUnavailableException e1) { - throw new CloudRuntimeException("Failed to destroy the volume from the source primary storage pool to secondary storage."); - } - if (destroyAnswer == null || !destroyAnswer.getResult()) { - throw new CloudRuntimeException("Failed to destroy the volume from the source primary storage pool to secondary storage."); - } + List vols = new ArrayList(); + vols.add(volume); + migrateVolumes(vols, destPool); return _volsDao.findById(volume.getId()); } @@ -1730,11 +1711,7 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag volume.setInstanceId(null); volume.setUpdated(new Date()); volume.setDomainId((caller == null) ? Domain.ROOT_DOMAIN : caller.getDomainId()); - if (cmd.getSnapshotId() == null) { - volume.setState(Volume.State.Allocated); - } else { - volume.setState(Volume.State.Creating); - } + volume = _volsDao.persist(volume); UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), diskOfferingId, null, size); _usageEventDao.persist(usageEvent); @@ -1779,12 +1756,16 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag @Override @DB - public void destroyVolume(VolumeVO volume) throws ConcurrentOperationException { - assert (volume.getState() != Volume.State.Destroy) : "Why destroy method is called for the volume that is already destroyed?"; - Transaction txn = Transaction.currentTxn(); - txn.start(); - - _volsDao.update(volume, Volume.Event.Destroy); + public boolean destroyVolume(VolumeVO volume) throws ConcurrentOperationException { + try { + if (!stateTransitTo(volume, Volume.Event.DestroyRequested)) { + throw new ConcurrentOperationException("Failed to transit to destroyed state"); + } + } catch (NoTransitionException e) { + s_logger.debug("Unable to destoy the volume: " + e.toString()); + return false; + } + long volumeId = volume.getId(); // Delete the recurring snapshot policies for this volume. @@ -1803,8 +1784,19 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName()); _usageEventDao.persist(usageEvent); } - - txn.commit(); + + try { + if (!stateTransitTo(volume, Volume.Event.OperationSucceeded)) { + throw new ConcurrentOperationException("Failed to transit state"); + + } + } catch (NoTransitionException e) { + s_logger.debug("Unable to change volume state: " + e.toString()); + return false; + } + + return true; + } @Override @@ -2406,7 +2398,7 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag Account caller = UserContext.current().getCaller(); // Check that the volume ID is valid - VolumeVO volume = _volsDao.acquireInLockTable(volumeId, 10); + VolumeVO volume = _volsDao.findById(volumeId); if (volume == null) { throw new InvalidParameterValueException("Unable to aquire volume with ID: " + volumeId); } @@ -2414,36 +2406,23 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag //permission check _accountMgr.checkAccess(caller, null, volume); - try { + // Check that the volume is not currently attached to any VM + if (volume.getInstanceId() != null) { + throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM."); + } - // Check that the volume is stored on shared storage - // NOTE: We used to ensure the volume is on shared storage before deleting. However, this seems like an unnecessary - // check since all we allow - // is deleting a detached volume. Is there a technical reason why the volume has to be on shared storage? If so, - // uncomment this...otherwise, - // just delete the detached volume regardless of storage pool. - // if (!volumeOnSharedStoragePool(volume)) { - // throw new InvalidParameterValueException("Please specify a volume that has been created on a shared storage pool."); - // } - - // Check that the volume is not currently attached to any VM - if (volume.getInstanceId() != null) { - throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM."); - } - - // Check that the volume is not already destroyed - if (volume.getState() != Volume.State.Destroy) { - destroyVolume(volume); - } - - try { - expungeVolume(volume); - } catch (Exception e) { - s_logger.warn("Failed to expunge volume:", e); + // Check that the volume is not already destroyed + if (volume.getState() != Volume.State.Destroy) { + if (!destroyVolume(volume)) { return false; } - } finally { - _volumeDao.releaseFromLockTable(volumeId); + } + + try { + expungeVolume(volume); + } catch (Exception e) { + s_logger.warn("Failed to expunge volume:", e); + return false; } return true; @@ -2569,6 +2548,189 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag } } } + + @DB + @Override + public Volume migrateVolume(Long volumeId, Long storagePoolId) throws ConcurrentOperationException { + VolumeVO vol = _volsDao.findById(volumeId); + if (vol == null) { + throw new InvalidParameterValueException("Failed to find the volume id: " + volumeId); + } + + if (vol.getState() != Volume.State.Ready) { + throw new InvalidParameterValueException("Volume must be in ready state"); + } + + if (vol.getInstanceId() != null) { + throw new InvalidParameterValueException("Volume needs to be dettached from VM"); + } + + StoragePool destPool = _storagePoolDao.findById(storagePoolId); + if (destPool == null) { + throw new InvalidParameterValueException("Faild to find the destination storage pool: " + storagePoolId); + } + + List vols = new ArrayList(); + vols.add(vol); + + migrateVolumes(vols, destPool); + return vol; + } + + @DB + public boolean migrateVolumes(List volumes, StoragePool destPool) throws ConcurrentOperationException { + Transaction txn = Transaction.currentTxn(); + txn.start(); + + boolean transitResult = false; + try { + for (Volume volume : volumes) { + try { + if (!stateTransitTo(volume, Volume.Event.MigrationRequested)) { + throw new ConcurrentOperationException("Failed to transit volume state"); + } + } catch (NoTransitionException e) { + s_logger.debug("Failed to set state into migrate: " + e.toString()); + throw new CloudRuntimeException("Failed to set state into migrate: " + e.toString()); + } + } + + transitResult = true; + } finally { + if (!transitResult) { + txn.rollback(); + } else { + txn.commit(); + } + } + + //At this stage, nobody can modify volumes. Send the copyvolume command + List> destroyCmds = new ArrayList>(); + List answers = new ArrayList(); + try { + for (Volume volume: volumes) { + String secondaryStorageURL = getSecondaryStorageURL(volume.getDataCenterId()); + StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId()); + CopyVolumeCommand cvCmd = new CopyVolumeCommand(volume.getId(), volume.getPath(), srcPool, secondaryStorageURL, true, _copyvolumewait); + CopyVolumeAnswer cvAnswer; + try { + cvAnswer = (CopyVolumeAnswer) sendToPool(srcPool, cvCmd); + } catch (StorageUnavailableException e1) { + throw new CloudRuntimeException("Failed to copy the volume from the source primary storage pool to secondary storage.", e1); + } + + if (cvAnswer == null || !cvAnswer.getResult()) { + throw new CloudRuntimeException("Failed to copy the volume from the source primary storage pool to secondary storage."); + } + + String secondaryStorageVolumePath = cvAnswer.getVolumePath(); + + // Copy the volume from secondary storage to the destination storage + // pool + cvCmd = new CopyVolumeCommand(volume.getId(), secondaryStorageVolumePath, destPool, secondaryStorageURL, false, _copyvolumewait); + try { + cvAnswer = (CopyVolumeAnswer) sendToPool(destPool, cvCmd); + } catch (StorageUnavailableException e1) { + throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool."); + } + + if (cvAnswer == null || !cvAnswer.getResult()) { + throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool."); + } + + answers.add(cvAnswer); + destroyCmds.add(new Pair(srcPool, new DestroyCommand(srcPool, volume, null))); + } + } finally { + if (answers.size() != volumes.size()) { + //this means one of copying volume failed + for (Volume volume : volumes) { + try { + stateTransitTo(volume, Volume.Event.OperationFailed); + } catch (NoTransitionException e) { + s_logger.debug("Failed to change volume state: " + e.toString()); + } + } + } else { + //Need a transaction, make sure all the volumes get migrated to new storage pool + txn = Transaction.currentTxn(); + txn.start(); + + transitResult = false; + try { + for (int i = 0; i < volumes.size(); i++) { + CopyVolumeAnswer answer = answers.get(i); + VolumeVO volume = (VolumeVO)volumes.get(i); + Long oldPoolId = volume.getPoolId(); + volume.setPath(answer.getVolumePath()); + volume.setFolder(answer.getVolumeFolder()); + volume.setPodId(destPool.getPodId()); + volume.setPoolId(destPool.getId()); + volume.setLastPoolId(oldPoolId); + try { + stateTransitTo(volume, Volume.Event.OperationSucceeded); + } catch (NoTransitionException e) { + s_logger.debug("Failed to change volume state: " + e.toString()); + throw new CloudRuntimeException("Failed to change volume state: " + e.toString()); + } + } + + transitResult = true; + } finally { + if (!transitResult) { + txn.rollback(); + } else { + txn.commit(); + } + } + + + } + } + + //all the volumes get migrated to new storage pool, need to delete the copy on old storage pool + for (Pair cmd : destroyCmds) { + try { + Answer cvAnswer = sendToPool(cmd.first(), cmd.second()); + } catch (StorageUnavailableException e) { + s_logger.debug("Unable to delete the old copy on storage pool: " + e.toString()); + } + } + return true; + } + + @Override + public boolean StorageMigration(VirtualMachineProfile vm, StoragePool destPool) throws ConcurrentOperationException { + List vols = _volsDao.findUsableVolumesForInstance(vm.getId()); + List volumesNeedToMigrate = new ArrayList(); + + for (VolumeVO volume : vols) { + if (volume.getState() != Volume.State.Ready) { + s_logger.debug("volume: " + volume.getId() + " is in " + volume.getState() + " state"); + throw new CloudRuntimeException("volume: " + volume.getId() + " is in " + volume.getState() + " state"); + } + + if (volume.getPoolId() == destPool.getId()) { + s_logger.debug("volume: " + volume.getId() + " is on the same storage pool: " + destPool.getId()); + continue; + } + + //Only support migrate in the same pod at first. + if (volume.getPodId() != destPool.getPodId()) { + throw new InvalidParameterValueException("Can't migrate vm between different pods: volume: " + volume.getId() + " at pod: " + + volume.getPodId() + ", while dest pool: " + destPool.getId() + " at pod: " + destPool.getPodId()); + } + + volumesNeedToMigrate.add(volume); + } + + if (volumesNeedToMigrate.isEmpty()) { + s_logger.debug("No volume need to be migrated"); + return true; + } + + return migrateVolumes(volumesNeedToMigrate, destPool); + } @Override public void prepare(VirtualMachineProfile vm, DeployDestination dest) throws StorageUnavailableException, InsufficientStorageCapacityException { @@ -2639,19 +2801,19 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag } try { - _volsDao.update(newVol, Volume.Event.Create); - } catch (ConcurrentOperationException e) { - throw new StorageUnavailableException("Unable to create " + newVol, newVol.getPoolId()); - } + stateTransitTo(newVol, Volume.Event.CreateRequested); + } catch (NoTransitionException e) { + throw new CloudRuntimeException("Unable to create " + e.toString()); + } Pair created = createVolume(newVol, _diskOfferingDao.findById(newVol.getDiskOfferingId()), vm, vols, dest); if (created == null) { Long poolId = newVol.getPoolId(); newVol.setPoolId(null); try { - _volsDao.update(newVol, Volume.Event.OperationFailed); - } catch (ConcurrentOperationException e) { - throw new CloudRuntimeException("Unable to update the failure on a volume: " + newVol, e); - } + stateTransitTo(newVol, Volume.Event.OperationFailed); + } catch (NoTransitionException e) { + throw new CloudRuntimeException("Unable to update the failure on a volume: " + newVol, e); + } throw new StorageUnavailableException("Unable to create " + newVol, poolId == null ? -1L : poolId); } created.first().setDeviceId(newVol.getDeviceId().intValue()); @@ -2661,8 +2823,8 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag newVol.setPoolType(created.second().getPoolType()); newVol.setPodId(created.second().getPodId()); try { - _volsDao.update(newVol, Volume.Event.OperationSucceeded); - } catch (ConcurrentOperationException e) { + stateTransitTo(newVol, Volume.Event.OperationSucceeded); + } catch (NoTransitionException e) { throw new CloudRuntimeException("Unable to update an CREATE operation succeeded on volume " + newVol, e); } if (s_logger.isDebugEnabled()) { @@ -2676,25 +2838,26 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag @DB protected VolumeVO switchVolume(VolumeVO existingVolume, VirtualMachineProfile vm) throws StorageUnavailableException { Transaction txn = Transaction.currentTxn(); + txn.start(); try { - txn.start(); - _volsDao.update(existingVolume, Volume.Event.Destroy); - - Long templateIdToUse = null; - Long volTemplateId = existingVolume.getTemplateId(); - long vmTemplateId = vm.getTemplateId(); - if (volTemplateId != null && volTemplateId.longValue() != vmTemplateId) { - if (s_logger.isDebugEnabled()) { - s_logger.debug("switchVolume: Old Volume's templateId: "+volTemplateId + " does not match the VM's templateId: "+vmTemplateId+", updating templateId in the new Volume"); - } - templateIdToUse = vmTemplateId; - } - VolumeVO newVolume = allocateDuplicateVolume(existingVolume, templateIdToUse); - txn.commit(); - return newVolume; - } catch (ConcurrentOperationException e) { - throw new StorageUnavailableException("Unable to duplicate the volume " + existingVolume, existingVolume.getPoolId(), e); + stateTransitTo(existingVolume, Volume.Event.DestroyRequested); + } catch (NoTransitionException e) { + s_logger.debug("Unable to destroy existing volume: " + e.toString()); } + + Long templateIdToUse = null; + Long volTemplateId = existingVolume.getTemplateId(); + long vmTemplateId = vm.getTemplateId(); + if (volTemplateId != null && volTemplateId.longValue() != vmTemplateId) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("switchVolume: Old Volume's templateId: "+volTemplateId + " does not match the VM's templateId: "+vmTemplateId+", updating templateId in the new Volume"); + } + templateIdToUse = vmTemplateId; + } + VolumeVO newVolume = allocateDuplicateVolume(existingVolume, templateIdToUse); + txn.commit(); + return newVolume; + } public Pair createVolume(VolumeVO toBeCreated, DiskOfferingVO offering, VirtualMachineProfile vm, List alreadyCreated, @@ -2716,8 +2879,8 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag } toBeCreated.setPoolId(pool.getId()); try { - _volsDao.update(toBeCreated, Volume.Event.OperationRetry); - } catch (ConcurrentOperationException e) { + stateTransitTo(toBeCreated, Volume.Event.OperationRetry); + } catch (NoTransitionException e) { throw new CloudRuntimeException("Unable to retry a create operation on volume " + toBeCreated); } @@ -2957,6 +3120,8 @@ public class StorageManagerImpl implements StorageManager, StorageService, Manag } return capacities; } + + @Override diff --git a/server/src/com/cloud/storage/dao/VolumeDao.java b/server/src/com/cloud/storage/dao/VolumeDao.java index e7f4bca9481..1078cfb424e 100755 --- a/server/src/com/cloud/storage/dao/VolumeDao.java +++ b/server/src/com/cloud/storage/dao/VolumeDao.java @@ -26,8 +26,9 @@ import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; +import com.cloud.utils.fsm.StateDao; -public interface VolumeDao extends GenericDao { +public interface VolumeDao extends GenericDao, StateDao { List findDetachedByAccount(long accountId); List findByAccount(long accountId); Pair getCountAndTotalByPool(long poolId); @@ -45,13 +46,7 @@ public interface VolumeDao extends GenericDao { List findByInstanceAndDeviceId(long instanceId, long deviceId); List findUsableVolumesForInstance(long instanceId); Long countAllocatedVolumesForAccount(long accountId); - /** - * Updates the volume only if the state in memory matches the state in the database. - * @param vol Volume to be updated. - * @param event event that causes the database change. - * @return true if update happened, false if not. - */ - boolean update(VolumeVO vol, Volume.Event event) throws ConcurrentOperationException; + HypervisorType getHypervisorType(long volumeId); List listVolumesToBeDestroyed(); diff --git a/server/src/com/cloud/storage/dao/VolumeDaoImpl.java b/server/src/com/cloud/storage/dao/VolumeDaoImpl.java index 4018a2634cb..519ba9c92dd 100755 --- a/server/src/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/server/src/com/cloud/storage/dao/VolumeDaoImpl.java @@ -31,6 +31,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Volume; +import com.cloud.storage.Volume.Event; import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeVO; import com.cloud.utils.Pair; @@ -58,7 +59,6 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol protected final SearchBuilder InstanceStatesSearch; protected final SearchBuilder AllFieldsSearch; protected GenericSearchBuilder CountByAccount; - protected final Attribute _stateAttr; protected static final String SELECT_VM_SQL = "SELECT DISTINCT instance_id from volumes v where v.host_id = ? and v.mirror_state = ?"; protected static final String SELECT_HYPERTYPE_FROM_VOLUME = "SELECT c.hypervisor_type from volumes v, storage_pool s, cluster c where v.pool_id = s.id and s.cluster_id = c.id and v.id = ?"; @@ -203,28 +203,6 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol update(volumeId, volume); } - @Override - public boolean update(VolumeVO vol, Volume.Event event) throws ConcurrentOperationException { - Volume.State oldState = vol.getState(); - Volume.State newState = oldState.getNextState(event); - - assert newState != null : "Event "+ event + " cannot happen from " + oldState; - - UpdateBuilder builder = getUpdateBuilder(vol); - builder.set(vol, _stateAttr, newState); - - SearchCriteria sc = AllFieldsSearch.create(); - sc.setParameters("id", vol.getId()); - sc.setParameters("state", oldState); - - int rows = update(builder, sc, null); - if (rows != 1) { - VolumeVO dbVol = findById(vol.getId()); - throw new ConcurrentOperationException("Unable to update " + vol + ": Old State=" + oldState + "; New State = " + newState + "; DB State=" + dbVol.getState()); - } - return rows == 1; - } - @Override @DB public HypervisorType getHypervisorType(long volumeId) { @@ -275,6 +253,7 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol AllFieldsSearch.and("id", AllFieldsSearch.entity().getId(), Op.EQ); AllFieldsSearch.and("destroyed", AllFieldsSearch.entity().getState(), Op.EQ); AllFieldsSearch.and("notDestroyed", AllFieldsSearch.entity().getState(), Op.NEQ); + AllFieldsSearch.and("updatedCount", AllFieldsSearch.entity().getUpdatedCount(), Op.EQ); AllFieldsSearch.done(); DetachedAccountIdSearch = createSearchBuilder(); @@ -312,9 +291,6 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol CountByAccount.and("account", CountByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); CountByAccount.and("state", CountByAccount.entity().getState(), SearchCriteria.Op.NIN); CountByAccount.done(); - - _stateAttr = _allAttributes.get("state"); - assert _stateAttr != null : "Couldn't get the state attribute"; } @Override @DB(txn=false) @@ -348,4 +324,38 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol return listBy(sc); } + + @Override + public boolean updateState(com.cloud.storage.Volume.State currentState, + Event event, com.cloud.storage.Volume.State nextState, Volume vo, + Object data) { + + Long oldUpdated = vo.getUpdatedCount(); + Date oldUpdatedTime = vo.getUpdated(); + + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("id", vo.getId()); + sc.setParameters("state", currentState); + sc.setParameters("updatedCount", vo.getUpdatedCount()); + + vo.incrUpdatedCount(); + + UpdateBuilder builder = getUpdateBuilder(vo); + builder.set(vo, "state", nextState); + builder.set(vo, "updated", new Date()); + + int rows = update((VolumeVO)vo, sc); + if (rows == 0 && s_logger.isDebugEnabled()) { + VolumeVO dbVol = findByIdIncludingRemoved(vo.getId()); + if (dbVol != null) { + StringBuilder str = new StringBuilder("Unable to update ").append(vo.toString()); + str.append(": DB Data={id=").append(dbVol.getId()).append("; state=").append(dbVol.getState()).append("; updatecount=").append(dbVol.getUpdatedCount()).append(";updatedTime=").append(dbVol.getUpdated()); + str.append(": New Data={id=").append(vo.getId()).append("; state=").append(nextState).append("; event=").append(event).append("; updatecount=").append(vo.getUpdatedCount()).append("; updatedTime=").append(vo.getUpdated()); + str.append(": stale Data={id=").append(vo.getId()).append("; state=").append(currentState).append("; event=").append(event).append("; updatecount=").append(oldUpdated).append("; updatedTime=").append(oldUpdatedTime); + } else { + s_logger.debug("Unable to update volume: id=" + vo.getId() + ", as there is no such volume exists in the database anymore"); + } + } + return rows > 0; + } } diff --git a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 92bf43c2b4e..d18db0418af 100755 --- a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -112,6 +112,7 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine.State; @@ -292,6 +293,7 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma _snapshotDao.update(snapshotId, snapshot); } else { long preSnapshotId = 0; + if (preSnapshotVO != null && preSnapshotVO.getBackupSnapshotId() != null) { preSnapshotId = preId; // default delta snap number is 16 @@ -315,6 +317,12 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma preSnapshotId = 0; } } + + //If the volume is moved around, backup a full snapshot to secondary storage + if (volume.getLastPoolId() != null && volume.getPoolId() != volume.getLastPoolId()) { + preSnapshotId = 0; + volume.setLastPoolId(volume.getPoolId()); + } snapshot = updateDBOnCreate(snapshotId, answer.getSnapshotPath(), preSnapshotId); } // Get the snapshot_schedule table entry for this snapshot and @@ -348,17 +356,24 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma @DB @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "creating snapshot", async = true) public SnapshotVO createSnapshot(Long volumeId, Long policyId, Long snapshotId) { - VolumeVO v = _volsDao.findById(volumeId); - Account owner = _accountMgr.getAccount(v.getAccountId()); + VolumeVO volume = _volsDao.findById(volumeId); + + if (volume == null) { + throw new InvalidParameterValueException("No such volume exist"); + } + + Account owner = _accountMgr.getAccount(volume.getAccountId()); SnapshotVO snapshot = null; - VolumeVO volume = null; + boolean backedUp = false; + // does the caller have the authority to act on this volume - _accountMgr.checkAccess(UserContext.current().getCaller(), null, v); + _accountMgr.checkAccess(UserContext.current().getCaller(), null, volume); + try { - if (v != null && _volsDao.getHypervisorType(v.getId()).equals(HypervisorType.KVM)) { + if (volume != null && _volsDao.getHypervisorType(volume.getId()).equals(HypervisorType.KVM)) { /* KVM needs to lock on the vm of volume, because it takes snapshot on behalf of vm, not volume */ - UserVmVO uservm = _vmDao.findById(v.getInstanceId()); + UserVmVO uservm = _vmDao.findById(volume.getInstanceId()); if (uservm != null) { UserVmVO vm = _vmDao.acquireInLockTable(uservm.getId(), 10); if (vm == null) { @@ -366,13 +381,13 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma } } } - Long poolId = v.getPoolId(); + Long poolId = volume.getPoolId(); if (poolId == null) { throw new CloudRuntimeException("You cannot take a snapshot of a volume until it has been attached to an instance"); } - if (_volsDao.getHypervisorType(v.getId()).equals(HypervisorType.KVM)) { - StoragePoolVO storagePool = _storagePoolDao.findById(v.getPoolId()); + if (_volsDao.getHypervisorType(volume.getId()).equals(HypervisorType.KVM)) { + StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); ClusterVO cluster = _clusterDao.findById(storagePool.getClusterId()); List hosts = _hostDao.listByCluster(cluster.getId()); if (hosts != null && !hosts.isEmpty()) { @@ -385,8 +400,8 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma } // if volume is attached to a vm in destroyed or expunging state; disallow - if (v.getInstanceId() != null) { - UserVmVO userVm = _vmDao.findById(v.getInstanceId()); + if (volume.getInstanceId() != null) { + UserVmVO userVm = _vmDao.findById(volume.getInstanceId()); if (userVm != null) { if (userVm.getState().equals(State.Destroyed) || userVm.getState().equals(State.Expunging)) { _snapshotDao.expunge(snapshotId); @@ -395,23 +410,24 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma } if(userVm.getHypervisorType() == HypervisorType.VMware) { - List activeSnapshots = _snapshotDao.listByInstanceId(v.getInstanceId(), Snapshot.Status.Creating, Snapshot.Status.CreatedOnPrimary, Snapshot.Status.BackingUp); + List activeSnapshots = _snapshotDao.listByInstanceId(volume.getInstanceId(), Snapshot.Status.Creating, Snapshot.Status.CreatedOnPrimary, Snapshot.Status.BackingUp); if(activeSnapshots.size() > 1) throw new CloudRuntimeException("There is other active snapshot tasks on the instance to which the volume is attached, please try again later"); } } } - volume = _volsDao.acquireInLockTable(volumeId, 10); - if (volume == null) { - _snapshotDao.expunge(snapshotId); - volume = _volsDao.findById(volumeId); - if (volume == null) { - throw new CloudRuntimeException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist"); - } else { - volume = null; - throw new CloudRuntimeException("Creating snapshot failed due to volume:" + volumeId + " is being used, try it later "); - } + //when taking snapshot, make sure nobody can delete/move the volume + boolean stateTransit = false; + try { + stateTransit = _storageMgr.stateTransitTo(volume, Volume.Event.SnapshotRequested); + } catch (NoTransitionException e) { + s_logger.debug("Failed transit volume state: " + e.toString()); + } finally { + if (!stateTransit) { + _snapshotDao.expunge(snapshotId); + throw new CloudRuntimeException("Creating snapshot failed due to volume:" + volumeId + " is being used, try it later "); + } } snapshot = createSnapshotOnPrimary(volume, policyId, snapshotId); @@ -442,7 +458,7 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma SnapshotVO freshSnapshot = _snapshotDao.findById(snapshot.getId()); if ((freshSnapshot != null) && backedUp) { UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_SNAPSHOT_CREATE, snapshot.getAccountId(), snapshot.getDataCenterId(), snapshotId, snapshot.getName(), null, null, - v.getSize()); + volume.getSize()); _usageEventDao.persist(usageEvent); } if( !backedUp ) { @@ -459,10 +475,13 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma _snapshotDao.update(snapshotId, snapshot); } } - - if ( volume != null ) { - _volsDao.releaseFromLockTable(volumeId); + + try { + _storageMgr.stateTransitTo(volume, Volume.Event.OperationSucceeded); + } catch (NoTransitionException e) { + s_logger.debug("Failed to transit volume state: " + e.toString()); } + } return snapshot; diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 1b898a8d1d8..270d8b265ee 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -156,6 +156,7 @@ import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.StoragePoolVO; import com.cloud.storage.VMTemplateHostVO; @@ -650,7 +651,11 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager if (moveVolumeNeeded) { // Move the volume to a storage pool in the VM's zone, pod, or cluster - volume = _storageMgr.moveVolume(volume, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), dataDiskHyperType); + try { + volume = _storageMgr.moveVolume(volume, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), dataDiskHyperType); + } catch (ConcurrentOperationException e) { + throw new CloudRuntimeException(e.toString()); + } } AsyncJobExecutor asyncExecutor = BaseAsyncJobExecutor.getCurrentExecutor(); @@ -3186,6 +3191,39 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager public UserVm getUserVm(long vmId) { return _vmDao.findById(vmId); } + + @Override + public VirtualMachine vmStorageMigration(Long vmId, StoragePool destPool) { + // access check - only root admin can migrate VM + Account caller = UserContext.current().getCaller(); + if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Caller is not a root admin, permission denied to migrate the VM"); + } + throw new PermissionDeniedException("No permission to migrate VM, Only Root Admin can migrate a VM!"); + } + + VMInstanceVO vm = _vmInstanceDao.findById(vmId); + if (vm == null) { + throw new InvalidParameterValueException("Unable to find the VM by id=" + vmId); + } + + if (vm.getState() != State.Stopped) { + throw new InvalidParameterValueException("VM is not Stopped, unable to migrate the vm " + vm); + } + + if (vm.getType() != VirtualMachine.Type.User) { + throw new InvalidParameterValueException("can only do storage migration on user vm"); + } + + HypervisorType destHypervisorType = _clusterDao.findById(destPool.getClusterId()).getHypervisorType(); + if (vm.getHypervisorType() != destHypervisorType) { + throw new InvalidParameterValueException("hypervisor is not compatible: dest: " + destHypervisorType.toString() + ", vm: " + vm.getHypervisorType().toString()); + } + VMInstanceVO migratedVm = _itMgr.storageMigration(vm, destPool); + return migratedVm; + + } @Override @ActionEvent(eventType = EventTypes.EVENT_VM_MIGRATE, eventDescription = "migrating VM", async = true) diff --git a/server/src/com/cloud/vm/UserVmStateListener.java b/server/src/com/cloud/vm/UserVmStateListener.java index 44fcef7fe3c..9b3077330ba 100644 --- a/server/src/com/cloud/vm/UserVmStateListener.java +++ b/server/src/com/cloud/vm/UserVmStateListener.java @@ -42,12 +42,12 @@ public class UserVmStateListener implements StateListener T storageMigration(T vm, StoragePool storagePoolId); + } diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index 4c87def3b0b..c322ed12b69 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -109,6 +109,7 @@ import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; @@ -1104,7 +1105,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene protected boolean stateTransitTo(VMInstanceVO vm, VirtualMachine.Event e, Long hostId, String reservationId) throws NoTransitionException { vm.setReservationId(reservationId); - return _stateMachine.transitTo(vm, e, hostId, _vmDao); + return _stateMachine.transitTo(vm, e, new Pair(vm.getHostId(), hostId), _vmDao); } @Override @@ -1119,7 +1120,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene vm.setLastHostId(vm.getHostId()); } } - return _stateMachine.transitTo(vm, e, hostId, _vmDao); + return _stateMachine.transitTo(vm, e, new Pair(vm.getHostId(), hostId), _vmDao); } @Override @@ -1169,6 +1170,52 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene return true; } + + @Override + public T storageMigration(T vm, StoragePool destPool) { + VirtualMachineGuru vmGuru = getVmGuru(vm); + + long vmId = vm.getId(); + vm = vmGuru.findById(vmId); + + try { + stateTransitTo(vm, VirtualMachine.Event.StorageMigrationRequested, null); + } catch (NoTransitionException e) { + s_logger.debug("Unable to migrate vm: " + e.toString()); + throw new CloudRuntimeException("Unable to migrate vm: " + e.toString()); + } + + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + boolean migrationResult = false; + try { + try { + migrationResult = _storageMgr.StorageMigration(profile, destPool); + } catch (ConcurrentOperationException e) { + + } + } finally { + if (migrationResult) { + try { + //when start the vm next time, don;'t look at last_host_id, only choose the host based on volume/storage pool + vm.setLastHostId(null); + stateTransitTo(vm, VirtualMachine.Event.AgentReportStopped, null); + } catch (NoTransitionException e) { + s_logger.debug("Failed to migrate vm: " + e.toString()); + throw new CloudRuntimeException("Failed to migrate vm: " + e.toString()); + } + } else { + s_logger.debug("storage migration failed: "); + try { + stateTransitTo(vm, VirtualMachine.Event.AgentReportStopped, null); + } catch (NoTransitionException e) { + s_logger.debug("Failed to change vm state: " + e.toString()); + throw new CloudRuntimeException("Failed to change vm state: " + e.toString()); + } + } + } + + return vm; + } @Override public T migrate(T vm, long srcHostId, DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, diff --git a/server/src/com/cloud/vm/dao/VMInstanceDaoImpl.java b/server/src/com/cloud/vm/dao/VMInstanceDaoImpl.java index 37ad4d6eb4c..098fa33c569 100644 --- a/server/src/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/server/src/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -28,6 +28,7 @@ import org.apache.log4j.Logger; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDaoImpl; +import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.db.Attribute; import com.cloud.utils.db.GenericDaoBase; @@ -279,13 +280,17 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem } @Override - public boolean updateState(State oldState, Event event, State newState, VirtualMachine vm, Long hostId) { + public boolean updateState(State oldState, Event event, State newState, VirtualMachine vm, Object opaque) { if (newState == null) { if (s_logger.isDebugEnabled()) { s_logger.debug("There's no way to transition from old state: " + oldState.toString() + " event: " + event.toString()); } return false; } + + @SuppressWarnings("unchecked") + Pair hosts = (Pair)opaque; + Long newHostId = hosts.second(); VMInstanceVO vmi = (VMInstanceVO)vm; Long oldHostId = vmi.getHostId(); @@ -302,7 +307,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem UpdateBuilder ub = getUpdateBuilder(vmi); ub.set(vmi, "state", newState); - ub.set(vmi, "hostId", hostId); + ub.set(vmi, "hostId", newHostId); ub.set(vmi, "podIdToDeployIn", vmi.getPodIdToDeployIn()); ub.set(vmi, _updateTimeAttr, new Date()); diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 1ae24aa8a68..0d22a14de0e 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -382,6 +382,7 @@ CREATE TABLE `cloud`.`volumes` ( `account_id` bigint unsigned NOT NULL COMMENT 'owner. foreign key to account table', `domain_id` bigint unsigned NOT NULL COMMENT 'the domain that the owner belongs to', `pool_id` bigint unsigned COMMENT 'pool it belongs to. foreign key to storage_pool table', + `last_pool_id` bigint unsigned COMMENT 'last pool it belongs to.', `instance_id` bigint unsigned NULL COMMENT 'vm instance it belongs to. foreign key to vm_instance table', `device_id` bigint unsigned NULL COMMENT 'which device inside vm instance it is ', `name` varchar(255) COMMENT 'A user specified name for the volume', @@ -404,6 +405,7 @@ CREATE TABLE `cloud`.`volumes` ( `removed` datetime COMMENT 'Date removed. not null if removed', `state` varchar(32) COMMENT 'State machine', `chain_info` text COMMENT 'save possible disk chain info in primary storage', + `update_count` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'date state was updated', PRIMARY KEY (`id`), INDEX `i_volumes__removed`(`removed`), INDEX `i_volumes__pod_id`(`pod_id`), @@ -412,9 +414,11 @@ CREATE TABLE `cloud`.`volumes` ( INDEX `i_volumes__account_id`(`account_id`), CONSTRAINT `fk_volumes__pool_id` FOREIGN KEY `fk_volumes__pool_id` (`pool_id`) REFERENCES `storage_pool` (`id`), INDEX `i_volumes__pool_id`(`pool_id`), + INDEX `i_volumes__last_pool_id`(`last_pool_id`), CONSTRAINT `fk_volumes__instance_id` FOREIGN KEY `fk_volumes__instance_id` (`instance_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE, INDEX `i_volumes__instance_id`(`instance_id`), - INDEX `i_volumes__state`(`state`) + INDEX `i_volumes__state`(`state`), + INDEX `i_volumes__update_count`(`update_count`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `cloud`.`snapshots` ( diff --git a/tools/testClient/asyncJobMgr.py b/tools/testClient/asyncJobMgr.py index 39ee80b4643..25e5f9416a9 100644 --- a/tools/testClient/asyncJobMgr.py +++ b/tools/testClient/asyncJobMgr.py @@ -20,12 +20,14 @@ class jobStatus(object): self.duration = None self.jobId = None self.responsecls = None + def __str__(self): + return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for (k, v) in self.__dict__.iteritems())) class workThread(threading.Thread): def __init__(self, in_queue, outqueue, apiClient, db=None, lock=None): threading.Thread.__init__(self) self.inqueue = in_queue self.output = outqueue - self.connection = apiClient.connection + self.connection = apiClient.connection.__copy__() self.db = None self.lock = lock diff --git a/tools/testClient/cloudstackConnection.py b/tools/testClient/cloudstackConnection.py index 3d341418665..9b2aa155658 100644 --- a/tools/testClient/cloudstackConnection.py +++ b/tools/testClient/cloudstackConnection.py @@ -51,6 +51,7 @@ class cloudConnection(object): requestUrl += "&signature=%s"%sig self.connection.request("GET", "/client/api?%s"%requestUrl) + return self.connection.getresponse().read() def make_request_without_auth(self, command, requests={}): diff --git a/utils/src/com/cloud/utils/fsm/StateDao.java b/utils/src/com/cloud/utils/fsm/StateDao.java index 8b407c30cab..2f91a2d8a2c 100644 --- a/utils/src/com/cloud/utils/fsm/StateDao.java +++ b/utils/src/com/cloud/utils/fsm/StateDao.java @@ -20,6 +20,6 @@ package com.cloud.utils.fsm; public interface StateDao { - boolean updateState(S currentState, E event, S nextState, V vo, Long id); + boolean updateState(S currentState, E event, S nextState, V vo, Object data); } diff --git a/utils/src/com/cloud/utils/fsm/StateListener.java b/utils/src/com/cloud/utils/fsm/StateListener.java index 49ff31e0f25..df9aeaf7274 100644 --- a/utils/src/com/cloud/utils/fsm/StateListener.java +++ b/utils/src/com/cloud/utils/fsm/StateListener.java @@ -27,11 +27,11 @@ public interface StateListener { * @param newState VM's new state * @param vo the VM instance * @param status the state transition is allowed or not - * @param id host id + * @param opaque host id * @param vmDao VM dao * @return */ - public boolean preStateTransitionEvent(S oldState, E event, S newState, V vo, boolean status, Long id); + public boolean preStateTransitionEvent(S oldState, E event, S newState, V vo, boolean status, Object opaque); /** * Event is triggered after state machine transition finished @@ -42,5 +42,5 @@ public interface StateListener { * @param status the state transition is allowed or not * @return */ - public boolean postStateTransitionEvent(S oldState, E event, S newState, V vo, boolean status, Long id); + public boolean postStateTransitionEvent(S oldState, E event, S newState, V vo, boolean status, Object opaque); } \ No newline at end of file diff --git a/utils/src/com/cloud/utils/fsm/StateMachine2.java b/utils/src/com/cloud/utils/fsm/StateMachine2.java index d0ab1835486..3529e3d8c72 100644 --- a/utils/src/com/cloud/utils/fsm/StateMachine2.java +++ b/utils/src/com/cloud/utils/fsm/StateMachine2.java @@ -99,7 +99,7 @@ public class StateMachine2> { } - public boolean transitTo(V vo, E e, Long id, StateDao dao) throws NoTransitionException { + public boolean transitTo(V vo, E e, Object opaque, StateDao dao) throws NoTransitionException { S currentState = vo.getState(); S nextState = getNextState(currentState, e); @@ -109,17 +109,16 @@ public class StateMachine2> { } for (StateListener listener : _listeners) { - listener.preStateTransitionEvent(currentState, e, nextState, vo, transitionStatus, id); + listener.preStateTransitionEvent(currentState, e, nextState, vo, transitionStatus, opaque); } - Long oldHostId = vo.getHostId(); - transitionStatus = dao.updateState(currentState, e, nextState, vo, id); + transitionStatus = dao.updateState(currentState, e, nextState, vo, opaque); if (!transitionStatus) { return false; } for (StateListener listener : _listeners) { - listener.postStateTransitionEvent(currentState, e, nextState, vo, transitionStatus, oldHostId); + listener.postStateTransitionEvent(currentState, e, nextState, vo, transitionStatus, opaque); } return true; diff --git a/utils/src/com/cloud/utils/fsm/StateObject.java b/utils/src/com/cloud/utils/fsm/StateObject.java index 74db84f258d..92c4cebdadc 100644 --- a/utils/src/com/cloud/utils/fsm/StateObject.java +++ b/utils/src/com/cloud/utils/fsm/StateObject.java @@ -23,5 +23,4 @@ public interface StateObject { * @return finite state. */ S getState(); - Long getHostId(); }