Skip to content

Commit f9b61bc

Browse files
anuragawyadvr
authored andcommitted
orchestration: Allow VM that has never started to have volumes attached (#3276)
With this patch b766bf7 we started tracking disks in attaching state so that other attach request can fail gracefully. However this missed the case where disks were in allocated state but attach was requested. For the use case where users want to attach disk in allocated state but not ready, we need to have allocated-attaching transition as well. We must take care of returning to the original state - allocated or ready - when attach request has completed. For the use case of unstarted vm's the disk must proceed as follows - "Allocated" -> Attaching -> Allocated. When VM is started, the disk is "created" and pool is assigned. For the use case of started VMs it's more trivial and disk proceeds as follows - Ready -> Attaching -> Ready. Test this by creating a VM with "startvm=false", create a disk and try attaching it in allocated state. It would give an exception on latest 4.11 but will be fixed on this patch. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
1 parent b60daf7 commit f9b61bc

4 files changed

Lines changed: 107 additions & 41 deletions

File tree

api/src/com/cloud/storage/Volume.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ enum State {
5252
UploadInProgress("Volume upload is in progress"),
5353
UploadError("Volume upload encountered some error"),
5454
UploadAbandoned("Volume upload is abandoned since the upload was never initiated within a specificed time"),
55-
Attaching("The volume is attaching to a VM");
55+
Attaching("The volume is attaching to a VM from Ready state.");
5656

5757
String _description;
5858

@@ -72,6 +72,8 @@ public String getDescription() {
7272
static {
7373
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Allocated, Event.CreateRequested, Creating, null));
7474
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Allocated, Event.DestroyRequested, Destroy, null));
75+
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Allocated, Event.OperationFailed, Allocated, null));
76+
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Allocated, Event.OperationSucceeded, Allocated, null));
7577
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Creating, Event.OperationRetry, Creating, null));
7678
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Creating, Event.OperationFailed, Allocated, null));
7779
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Creating, Event.OperationSucceeded, Ready, null));

engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
217217
@Override
218218
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
219219
DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
220+
if (dataStoreDriver == null) {
221+
return;
222+
}
220223

221224
if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
222225
((PrimaryDataStoreDriver)dataStoreDriver).revokeAccess(dataObject, host, dataStore);

server/src/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,10 @@ private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) {
18881888
}
18891889
}
18901890

1891+
if (volumePool == null) {
1892+
sendCommand = false;
1893+
}
1894+
18911895
Answer answer = null;
18921896

18931897
if (sendCommand) {
@@ -1921,12 +1925,13 @@ private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) {
19211925
_volsDao.detachVolume(volume.getId());
19221926

19231927
// volume.getPoolId() should be null if the VM we are detaching the disk from has never been started before
1924-
DataStore dataStore = volume.getPoolId() != null ? dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary) : null;
1925-
1926-
volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore);
1927-
1928-
handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName());
1929-
1928+
if (volume.getPoolId() != null) {
1929+
DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary);
1930+
volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore);
1931+
}
1932+
if (volumePool != null && hostId != null) {
1933+
handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName());
1934+
}
19301935
return _volsDao.findById(volumeId);
19311936
} else {
19321937

@@ -2634,24 +2639,25 @@ private boolean needMoveVolume(VolumeVO existingVolume, VolumeInfo newVolume) {
26342639
return !storeForExistingStoreScope.isSameScope(storeForNewStoreScope);
26352640
}
26362641

2637-
private synchronized void checkAndSetAttaching(Long volumeId, Long hostId) {
2642+
private synchronized void checkAndSetAttaching(Long volumeId) {
26382643
VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
26392644

26402645
if (volumeToAttach.isAttachedVM()) {
26412646
throw new CloudRuntimeException("volume: " + volumeToAttach.getName() + " is already attached to a VM: " + volumeToAttach.getAttachedVmName());
26422647
}
2643-
if (volumeToAttach.getState().equals(Volume.State.Ready)) {
2648+
2649+
if (Volume.State.Allocated.equals(volumeToAttach.getState())) {
2650+
return;
2651+
}
2652+
2653+
if (Volume.State.Ready.equals(volumeToAttach.getState())) {
26442654
volumeToAttach.stateTransit(Volume.Event.AttachRequested);
2645-
} else {
2646-
String error = null;
2647-
if (hostId == null) {
2648-
error = "Please try attach operation after starting VM once";
2649-
} else {
2650-
error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready state";
2651-
}
2652-
s_logger.error(error);
2653-
throw new CloudRuntimeException(error);
2655+
return;
26542656
}
2657+
2658+
final String error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready or Allocated state";
2659+
s_logger.error(error);
2660+
throw new CloudRuntimeException(error);
26552661
}
26562662

26572663
private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) {
@@ -2684,7 +2690,7 @@ private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, L
26842690
// volumeToAttachStoragePool should be null if the VM we are attaching the disk to has never been started before
26852691
DataStore dataStore = volumeToAttachStoragePool != null ? dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary) : null;
26862692

2687-
checkAndSetAttaching(volumeToAttach.getId(), hostId);
2693+
checkAndSetAttaching(volumeToAttach.getId());
26882694

26892695
boolean attached = false;
26902696
try {
@@ -2773,9 +2779,10 @@ private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, L
27732779

27742780
volumeToAttach = _volsDao.findById(volumeToAttach.getId());
27752781

2776-
if (vm.getHypervisorType() == HypervisorType.KVM && volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
2782+
if (vm.getHypervisorType() == HypervisorType.KVM &&
2783+
volumeToAttachStoragePool != null && volumeToAttachStoragePool.isManaged() &&
2784+
volumeToAttach.getPath() == null && volumeToAttach.get_iScsiName() != null) {
27772785
volumeToAttach.setPath(volumeToAttach.get_iScsiName());
2778-
27792786
_volsDao.update(volumeToAttach.getId(), volumeToAttach);
27802787
}
27812788
}

test/integration/smoke/test_volumes.py

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -398,40 +398,40 @@ def test_02_attach_volume(self):
398398
# 3. disk should be attached to instance successfully
399399

400400
self.debug(
401-
"Attaching volume (ID: %s) to VM (ID: %s)" % (
402-
self.volume.id,
403-
self.virtual_machine.id
404-
))
401+
"Attaching volume (ID: %s) to VM (ID: %s)" % (
402+
self.volume.id,
403+
self.virtual_machine.id
404+
))
405405
self.virtual_machine.attach_volume(self.apiClient, self.volume)
406406
self.attached = True
407407
list_volume_response = Volume.list(
408-
self.apiClient,
409-
id=self.volume.id
410-
)
408+
self.apiClient,
409+
id=self.volume.id
410+
)
411411
self.assertEqual(
412-
isinstance(list_volume_response, list),
413-
True,
414-
"Check list response returns a valid list"
415-
)
412+
isinstance(list_volume_response, list),
413+
True,
414+
"Check list response returns a valid list"
415+
)
416416
self.assertNotEqual(
417-
list_volume_response,
418-
None,
419-
"Check if volume exists in ListVolumes"
420-
)
417+
list_volume_response,
418+
None,
419+
"Check if volume exists in ListVolumes"
420+
)
421421
volume = list_volume_response[0]
422422
self.assertNotEqual(
423-
volume.virtualmachineid,
424-
None,
425-
"Check if volume state (attached) is reflected"
426-
)
423+
volume.virtualmachineid,
424+
None,
425+
"Check if volume state (attached) is reflected"
426+
)
427427
try:
428428
#Format the attached volume to a known fs
429429
format_volume_to_ext3(self.virtual_machine.get_ssh_client())
430430

431431
except Exception as e:
432432

433433
self.fail("SSH failed for VM: %s - %s" %
434-
(self.virtual_machine.ipaddress, e))
434+
(self.virtual_machine.ipaddress, e))
435435
return
436436

437437
@attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
@@ -857,6 +857,60 @@ def test_10_list_volumes(self):
857857
self.assertTrue(hasattr(root_volume, "podname"))
858858
self.assertEqual(root_volume.podname, list_pods.name)
859859

860+
@attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="true")
861+
def test_11_attach_volume_with_unstarted_vm(self):
862+
"""Attach a created Volume to a unstarted VM
863+
"""
864+
# Validate the following
865+
# 1. Attach to a vm in startvm=false state works and vm can be started afterwards.
866+
# 2. shows list of volumes
867+
# 3. "Attach Disk" pop-up box will display with list of instances
868+
# 4. disk should be attached to instance successfully
869+
870+
test_vm = VirtualMachine.create(
871+
self.apiclient,
872+
self.services,
873+
accountid=self.account.name,
874+
domainid=self.account.domainid,
875+
serviceofferingid=self.service_offering.id,
876+
mode=self.services["mode"],
877+
startvm=False
878+
)
879+
880+
self.debug(
881+
"Attaching volume (ID: %s) to VM (ID: %s)" % (
882+
self.volume.id,
883+
test_vm.id
884+
))
885+
test_vm.attach_volume(self.apiClient, self.volume)
886+
test_vm.start(self.apiClient)
887+
888+
list_volume_response = Volume.list(
889+
self.apiClient,
890+
id=self.volume.id
891+
)
892+
self.assertEqual(
893+
isinstance(list_volume_response, list),
894+
True,
895+
"Check list response returns a valid list"
896+
)
897+
self.assertNotEqual(
898+
list_volume_response,
899+
None,
900+
"Check if volume exists in ListVolumes"
901+
)
902+
volume = list_volume_response[0]
903+
self.assertNotEqual(
904+
volume.virtualmachineid,
905+
None,
906+
"Check if volume state (attached) is reflected"
907+
)
908+
909+
test_vm.detach_volume(self.apiClient, self.volume)
910+
self.cleanup.append(test_vm)
911+
912+
return
913+
860914
def wait_for_attributes_and_return_root_vol(self):
861915
def checkVolumeResponse():
862916
list_volume_response = Volume.list(

0 commit comments

Comments
 (0)