CLOUDSTACK-8862: Introduced new state attaching for volume. This will make sure that other attach operation on same volume will fail gracefully without calling access calls for managed storage like SolidFire

Also, skipping test_upload_attach_volume as there is no implementation
which supports this.
This commit is contained in:
Anshul Gangwar 2017-01-10 17:10:28 +05:30
parent b8dd08c362
commit b766bf7fc9
4 changed files with 150 additions and 95 deletions

View File

@ -51,7 +51,8 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
NotUploaded("The volume entry is just created in DB, not yet uploaded"),
UploadInProgress("Volume upload is in progress"),
UploadError("Volume upload encountered some error"),
UploadAbandoned("Volume upload is abandoned since the upload was never initiated within a specificed time");
UploadAbandoned("Volume upload is abandoned since the upload was never initiated within a specificed time"),
Attaching("The volume is attaching to a VM");
String _description;
@ -118,6 +119,9 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(UploadInProgress, Event.OperationTimeout, UploadError, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(UploadError, Event.DestroyRequested, Destroy, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(UploadAbandoned, Event.DestroyRequested, Destroy, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Ready, Event.AttachRequested, Attaching, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Attaching, Event.OperationSucceeded, Ready, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Attaching, Event.OperationFailed, Ready, null));
}
}
@ -139,6 +143,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
DestroyRequested,
ExpungingRequested,
ResizeRequested,
AttachRequested,
OperationTimeout;
}

View File

@ -1459,7 +1459,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
return true;
}
private void cleanupVolumeDuringAttachFailure(Long volumeId) {
private void cleanupVolumeDuringAttachFailure(Long volumeId, Long vmId) {
VolumeVO volume = _volsDao.findById(volumeId);
if (volume == null) {
return;
@ -1469,6 +1469,13 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
s_logger.debug("Remove volume: " + volume.getId() + ", as it's leftover from last mgt server stop");
_volsDao.remove(volume.getId());
}
if(volume.getState().equals(Volume.State.Attaching)) {
s_logger.warn("Vol: " + volume.getName() + " failed to attach to VM: " + _userVmDao.findById(vmId).getHostName() +
" on last mgt server stop, changing state back to Ready");
volume.setState(Volume.State.Ready);
_volsDao.update(volumeId, volume);
}
}
private void cleanupVolumeDuringMigrationFailure(Long volumeId, Long destPoolId) {
@ -1512,7 +1519,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
try {
if (job.getCmd().equalsIgnoreCase(VmWorkAttachVolume.class.getName())) {
VmWorkAttachVolume work = VmWorkSerializer.deserialize(VmWorkAttachVolume.class, job.getCmdInfo());
cleanupVolumeDuringAttachFailure(work.getVolumeId());
cleanupVolumeDuringAttachFailure(work.getVolumeId(), work.getVmId());
} else if (job.getCmd().equalsIgnoreCase(VmWorkMigrateVolume.class.getName())) {
VmWorkMigrateVolume work = VmWorkSerializer.deserialize(VmWorkMigrateVolume.class, job.getCmdInfo());
cleanupVolumeDuringMigrationFailure(work.getVolumeId(), work.getDestPoolId());

View File

@ -2474,6 +2474,26 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return !storeForExistingStoreScope.isSameScope(storeForNewStoreScope);
}
private synchronized void checkAndSetAttaching(Long volumeId, Long hostId) {
VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
if (volumeToAttach.isAttachedVM()) {
throw new CloudRuntimeException("volume: " + volumeToAttach.getName() + " is already attached to a VM: " + volumeToAttach.getAttachedVmName());
}
if (volumeToAttach.getState().equals(Volume.State.Ready)) {
volumeToAttach.stateTransit(Volume.Event.AttachRequested);
} else {
String error = null;
if (hostId == null) {
error = "Please try attach operation after starting VM once";
} else {
error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready state";
}
s_logger.error(error);
throw new CloudRuntimeException(error);
}
}
private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) {
String errorMsg = "Failed to attach volume " + volumeToAttach.getName() + " to VM " + vm.getHostName();
boolean sendCommand = vm.getState() == State.Running;
@ -2504,112 +2524,128 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
// volumeToAttachStoragePool should be null if the VM we are attaching the disk to has never been started before
DataStore dataStore = volumeToAttachStoragePool != null ? dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary) : null;
// if we don't have a host, the VM we are attaching the disk to has never been started before
if (host != null) {
try {
volService.grantAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
}
catch (Exception e) {
volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
checkAndSetAttaching(volumeToAttach.getId(), hostId);
throw new CloudRuntimeException(e.getMessage());
}
}
if (sendCommand) {
if (host != null && host.getHypervisorType() == HypervisorType.KVM &&
volumeToAttachStoragePool.isManaged() &&
volumeToAttach.getPath() == null) {
volumeToAttach.setPath(volumeToAttach.get_iScsiName());
_volsDao.update(volumeToAttach.getId(), volumeToAttach);
}
DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO();
deviceId = getDeviceId(vm, deviceId);
DiskTO disk = storageMgr.getDiskWithThrottling(volTO, volumeToAttach.getVolumeType(), deviceId, volumeToAttach.getPath(),
vm.getServiceOfferingId(), volumeToAttach.getDiskOfferingId());
AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName());
ChapInfo chapInfo = volService.getChapInfo(volFactory.getVolume(volumeToAttach.getId()), dataStore);
Map<String, String> details = new HashMap<String, String>();
disk.setDetails(details);
details.put(DiskTO.MANAGED, String.valueOf(volumeToAttachStoragePool.isManaged()));
details.put(DiskTO.STORAGE_HOST, volumeToAttachStoragePool.getHostAddress());
details.put(DiskTO.STORAGE_PORT, String.valueOf(volumeToAttachStoragePool.getPort()));
details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeToAttach.getSize()));
details.put(DiskTO.IQN, volumeToAttach.get_iScsiName());
details.put(DiskTO.MOUNT_POINT, volumeToAttach.get_iScsiName());
details.put(DiskTO.PROTOCOL_TYPE, (volumeToAttach.getPoolType() != null) ? volumeToAttach.getPoolType().toString() : null);
if (chapInfo != null) {
details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
}
_userVmDao.loadDetails(vm);
Map<String, String> controllerInfo = new HashMap<String, String>();
controllerInfo.put(VmDetailConstants.ROOT_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER));
controllerInfo.put(VmDetailConstants.DATA_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.DATA_DISK_CONTROLLER));
cmd.setControllerInfo(controllerInfo);
s_logger.debug("Attach volume id:" + volumeToAttach.getId() + " on VM id:" + vm.getId() + " has controller info:" + controllerInfo);
try {
answer = (AttachAnswer)_agentMgr.send(hostId, cmd);
} catch (Exception e) {
if(host!=null) {
volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
boolean attached = false;
try {
// if we don't have a host, the VM we are attaching the disk to has never been started before
if (host != null) {
try {
volService.grantAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
}
catch (Exception e) {
volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
throw new CloudRuntimeException(e.getMessage());
}
throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage());
}
}
if (!sendCommand || (answer != null && answer.getResult())) {
// Mark the volume as attached
if (sendCommand) {
DiskTO disk = answer.getDisk();
_volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), disk.getDiskSeq());
volumeToAttach = _volsDao.findById(volumeToAttach.getId());
if (volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
volumeToAttach.setPath(answer.getDisk().getPath());
if (host != null && host.getHypervisorType() == HypervisorType.KVM &&
volumeToAttachStoragePool.isManaged() &&
volumeToAttach.getPath() == null) {
volumeToAttach.setPath(volumeToAttach.get_iScsiName());
_volsDao.update(volumeToAttach.getId(), volumeToAttach);
}
} else {
DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO();
deviceId = getDeviceId(vm, deviceId);
_volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), deviceId);
}
DiskTO disk = storageMgr.getDiskWithThrottling(volTO, volumeToAttach.getVolumeType(), deviceId, volumeToAttach.getPath(),
vm.getServiceOfferingId(), volumeToAttach.getDiskOfferingId());
// insert record for disk I/O statistics
VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(), vm.getId(), volumeToAttach.getId());
if (diskstats == null) {
diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(), vm.getId(), volumeToAttach.getId());
_vmDiskStatsDao.persist(diskstats);
}
AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName());
return _volsDao.findById(volumeToAttach.getId());
} else {
if (answer != null) {
String details = answer.getDetails();
if (details != null && !details.isEmpty()) {
errorMsg += "; " + details;
ChapInfo chapInfo = volService.getChapInfo(volFactory.getVolume(volumeToAttach.getId()), dataStore);
Map<String, String> details = new HashMap<String, String>();
disk.setDetails(details);
details.put(DiskTO.MANAGED, String.valueOf(volumeToAttachStoragePool.isManaged()));
details.put(DiskTO.STORAGE_HOST, volumeToAttachStoragePool.getHostAddress());
details.put(DiskTO.STORAGE_PORT, String.valueOf(volumeToAttachStoragePool.getPort()));
details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeToAttach.getSize()));
details.put(DiskTO.IQN, volumeToAttach.get_iScsiName());
details.put(DiskTO.MOUNT_POINT, volumeToAttach.get_iScsiName());
details.put(DiskTO.PROTOCOL_TYPE, (volumeToAttach.getPoolType() != null) ? volumeToAttach.getPoolType().toString() : null);
if (chapInfo != null) {
details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
}
_userVmDao.loadDetails(vm);
Map<String, String> controllerInfo = new HashMap<String, String>();
controllerInfo.put(VmDetailConstants.ROOT_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER));
controllerInfo.put(VmDetailConstants.DATA_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.DATA_DISK_CONTROLLER));
cmd.setControllerInfo(controllerInfo);
s_logger.debug("Attach volume id:" + volumeToAttach.getId() + " on VM id:" + vm.getId() + " has controller info:" + controllerInfo);
try {
answer = (AttachAnswer)_agentMgr.send(hostId, cmd);
} catch (Exception e) {
if(host!=null) {
volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
}
throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage());
}
}
if(host!= null) {
volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
if (!sendCommand || (answer != null && answer.getResult())) {
// Mark the volume as attached
if (sendCommand) {
DiskTO disk = answer.getDisk();
_volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), disk.getDiskSeq());
volumeToAttach = _volsDao.findById(volumeToAttach.getId());
if (volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
volumeToAttach.setPath(answer.getDisk().getPath());
_volsDao.update(volumeToAttach.getId(), volumeToAttach);
}
} else {
deviceId = getDeviceId(vm, deviceId);
_volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), deviceId);
}
// insert record for disk I/O statistics
VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(), vm.getId(), volumeToAttach.getId());
if (diskstats == null) {
diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(), vm.getId(), volumeToAttach.getId());
_vmDiskStatsDao.persist(diskstats);
}
attached = true;
} else {
if (answer != null) {
String details = answer.getDetails();
if (details != null && !details.isEmpty()) {
errorMsg += "; " + details;
}
}
if (host != null) {
volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
}
throw new CloudRuntimeException(errorMsg);
}
throw new CloudRuntimeException(errorMsg);
} finally {
Volume.Event ev = Volume.Event.OperationFailed;
VolumeInfo volInfo = volFactory.getVolume(volumeToAttach.getId());
if(attached) {
ev = Volume.Event.OperationSucceeded;
s_logger.debug("Volume: " + volInfo.getName() + " successfully attached to VM: " + volInfo.getAttachedVmName());
} else {
s_logger.debug("Volume: " + volInfo.getName() + " failed to attach to VM: " + volInfo.getAttachedVmName());
}
volInfo.stateTransit(ev);
}
return _volsDao.findById(volumeToAttach.getId());
}
private int getMaxDataVolumesSupported(UserVmVO vm) {

View File

@ -249,6 +249,9 @@ class TestDeployVM(cloudstackTestCase):
# should be "Stopped".
# 3. Attach volume should be successful
# Skipping this test
self.skipTest("Skipping test as proper implementation seems to be missing")
self.debug("Deploying instance in the account: %s" %
self.account.name)
self.virtual_machine = VirtualMachine.create(
@ -369,6 +372,9 @@ class TestDeployVM(cloudstackTestCase):
# 3. Attach volume should be successful
# 4. Detach volume from instance. Detach should be successful
# Skipping this test
self.skipTest("Skipping test as proper implementation seems to be missing")
self.debug("Deploying instance in the account: %s" %
self.account.name)
self.virtual_machine = VirtualMachine.create(
@ -1474,7 +1480,7 @@ class TestUploadAttachVolume(cloudstackTestCase):
cls.testdata = cls.testClient.getParsedTestDataConfig()
cls.hypervisor = cls.testClient.getHypervisorInfo()
cls.skip = False
cls.skip = True
if cls.hypervisor.lower() == 'lxc':
if not find_storage_pool_type(cls.apiclient, storagetype='rbd'):
@ -1519,7 +1525,8 @@ class TestUploadAttachVolume(cloudstackTestCase):
def setUp(self):
if self.skip:
self.skipTest("RBD storage type is required for data volumes for LXC")
self.skipTest("Attach operation for uploaded volume to VM which is not started once is not supported")
# self.skipTest("RBD storage type is required for data volumes for LXC")
self.apiclient = self.testClient.getApiClient()
self.dbclient = self.testClient.getDbConnection()