cloudstack/server/test/com/cloud/storage/VolumeApiServiceImplTest.java
Syed f46651e672 Support Backup of Snapshots for Managed Storage
This PR adds an ability to Pass a new parameter, locationType,
    to the “createSnapshot” API command. Depending on the locationType,
    we decide where the snapshot should go in case of managed storage.

    There are two possible values for the locationType param

    1) `Standard`: The standard operation for managed storage is to
    keep the snapshot on the device. For non-managed storage, this will
    be to upload it to secondary storage. This option will be the
    default.

    2) `Archive`: Applicable only to managed storage. This will
    keep the snapshot on the secondary storage. For non-managed
    storage, this will result in an error.

    The reason for implementing this feature is to avoid a single
    point of failure for primary storage. Right now in case of managed
    storage, if the primary storage goes down, there is no easy way
    to recover data as all snapshots are also stored on the primary.
    This features allows us to mitigate that risk.
2016-10-30 23:19:58 -06:00

447 lines
20 KiB
Java

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.storage;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.org.Grouping;
import com.cloud.serializer.GsonHelper;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import junit.framework.Assert;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobJoinMapDao;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import javax.inject.Inject;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class VolumeApiServiceImplTest {
@Inject
VolumeApiServiceImpl _svc = new VolumeApiServiceImpl();
@Mock
VolumeDao _volumeDao;
@Mock
AccountManager _accountMgr;
@Mock
UserVmDao _userVmDao;
@Mock
PrimaryDataStoreDao _storagePoolDao;
@Mock
VMSnapshotDao _vmSnapshotDao;
@Mock
AsyncJobManager _jobMgr;
@Mock
AsyncJobJoinMapDao _joinMapDao;
@Mock
VolumeDataFactory _volFactory;
@Mock
VMInstanceDao _vmInstanceDao;
@Mock
VolumeInfo volumeInfoMock;
@Mock
SnapshotInfo snapshotInfoMock;
@Mock
VolumeService volService;
@Mock
CreateVolumeCmd createVol;
@Mock
UserVmManager _userVmMgr;
@Mock
DataCenterDao _dcDao;
DetachVolumeCmd detachCmd = new DetachVolumeCmd();
Class<?> _detachCmdClass = detachCmd.getClass();
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
_svc._volsDao = _volumeDao;
_svc._accountMgr = _accountMgr;
_svc._userVmDao = _userVmDao;
_svc._storagePoolDao = _storagePoolDao;
_svc._vmSnapshotDao = _vmSnapshotDao;
_svc._vmInstanceDao = _vmInstanceDao;
_svc._jobMgr = _jobMgr;
_svc.volFactory = _volFactory;
_svc.volService = volService;
_svc._userVmMgr = _userVmMgr;
_svc._dcDao = _dcDao;
_svc._gson = GsonHelper.getGsonLogger();
// mock caller context
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.ACCOUNT_TYPE_NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
// mock async context
AsyncJobExecutionContext context = new AsyncJobExecutionContext();
AsyncJobExecutionContext.init(_svc._jobMgr, _joinMapDao);
AsyncJobVO job = new AsyncJobVO();
context.setJob(job);
AsyncJobExecutionContext.setCurrentExecutionContext(context);
TransactionLegacy txn = TransactionLegacy.open("runVolumeDaoImplTest");
try {
// volume of running vm id=1
VolumeVO volumeOfRunningVm = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
when(_svc._volsDao.findById(1L)).thenReturn(volumeOfRunningVm);
UserVmVO runningVm = new UserVmVO(1L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false,
false, 1L, 1L, 1, 1L, null, "vm", null);
runningVm.setState(State.Running);
runningVm.setDataCenterId(1L);
when(_svc._userVmDao.findById(1L)).thenReturn(runningVm);
// volume of stopped vm id=2
VolumeVO volumeOfStoppedVm = new VolumeVO("root", 1L, 1L, 1L, 1L, 2L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
volumeOfStoppedVm.setPoolId(1L);
when(_svc._volsDao.findById(2L)).thenReturn(volumeOfStoppedVm);
UserVmVO stoppedVm = new UserVmVO(2L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false,
false, 1L, 1L, 1, 1L, null, "vm", null);
stoppedVm.setState(State.Stopped);
stoppedVm.setDataCenterId(1L);
when(_svc._userVmDao.findById(2L)).thenReturn(stoppedVm);
// volume of hyperV vm id=3
UserVmVO hyperVVm = new UserVmVO(3L, "vm", "vm", 1, HypervisorType.Hyperv, 1L, false,
false, 1L, 1L, 1, 1L, null, "vm", null);
hyperVVm.setState(State.Stopped);
hyperVVm.setDataCenterId(1L);
when(_svc._userVmDao.findById(3L)).thenReturn(hyperVVm);
VolumeVO volumeOfStoppeHyperVVm = new VolumeVO("root", 1L, 1L, 1L, 1L, 3L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
volumeOfStoppeHyperVVm.setPoolId(1L);
when(_svc._volsDao.findById(3L)).thenReturn(volumeOfStoppeHyperVVm);
StoragePoolVO unmanagedPool = new StoragePoolVO();
when(_svc._storagePoolDao.findById(1L)).thenReturn(unmanagedPool);
// volume of managed pool id=4
StoragePoolVO managedPool = new StoragePoolVO();
managedPool.setManaged(true);
when(_svc._storagePoolDao.findById(2L)).thenReturn(managedPool);
VolumeVO managedPoolVolume = new VolumeVO("root", 1L, 1L, 1L, 1L, 2L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
managedPoolVolume.setPoolId(2L);
when(_svc._volsDao.findById(4L)).thenReturn(managedPoolVolume);
// non-root non-datadisk volume
VolumeInfo volumeWithIncorrectVolumeType = Mockito.mock(VolumeInfo.class);
when(volumeWithIncorrectVolumeType.getId()).thenReturn(5L);
when(volumeWithIncorrectVolumeType.getVolumeType()).thenReturn(Volume.Type.ISO);
when(_svc.volFactory.getVolume(5L)).thenReturn(volumeWithIncorrectVolumeType);
// correct root volume
VolumeInfo correctRootVolume = Mockito.mock(VolumeInfo.class);
when(correctRootVolume.getId()).thenReturn(6L);
when(correctRootVolume.getDataCenterId()).thenReturn(1L);
when(correctRootVolume.getVolumeType()).thenReturn(Volume.Type.ROOT);
when(correctRootVolume.getInstanceId()).thenReturn(null);
when(correctRootVolume.getState()).thenReturn(Volume.State.Ready);
when(correctRootVolume.getTemplateId()).thenReturn(null);
when(correctRootVolume.getPoolId()).thenReturn(1L);
when(_svc.volFactory.getVolume(6L)).thenReturn(correctRootVolume);
VolumeVO correctRootVolumeVO = new VolumeVO("root", 1L, 1L, 1L, 1L, 2L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
when(_svc._volsDao.findById(6L)).thenReturn(correctRootVolumeVO);
// managed root volume
VolumeInfo managedVolume = Mockito.mock(VolumeInfo.class);
when(managedVolume.getId()).thenReturn(7L);
when(managedVolume.getDataCenterId()).thenReturn(1L);
when(managedVolume.getVolumeType()).thenReturn(Volume.Type.ROOT);
when(managedVolume.getInstanceId()).thenReturn(null);
when(managedVolume.getPoolId()).thenReturn(2L);
when(_svc.volFactory.getVolume(7L)).thenReturn(managedVolume);
VolumeVO managedVolume1 = new VolumeVO("root", 1L, 1L, 1L, 1L, 2L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
managedVolume1.setPoolId(2L);
managedVolume1.setDataCenterId(1L);
when(_svc._volsDao.findById(7L)).thenReturn(managedVolume1);
// vm having root volume
UserVmVO vmHavingRootVolume = new UserVmVO(4L, "vm", "vm", 1, HypervisorType.XenServer, 1L, false,
false, 1L, 1L, 1, 1L, null, "vm", null);
vmHavingRootVolume.setState(State.Stopped);
vmHavingRootVolume.setDataCenterId(1L);
when(_svc._userVmDao.findById(4L)).thenReturn(vmHavingRootVolume);
List<VolumeVO> vols = new ArrayList<VolumeVO>();
vols.add(new VolumeVO());
when(_svc._volsDao.findByInstanceAndDeviceId(4L, 0L)).thenReturn(vols);
// volume in uploaded state
VolumeInfo uploadedVolume = Mockito.mock(VolumeInfo.class);
when(uploadedVolume.getId()).thenReturn(8L);
when(uploadedVolume.getDataCenterId()).thenReturn(1L);
when(uploadedVolume.getVolumeType()).thenReturn(Volume.Type.ROOT);
when(uploadedVolume.getInstanceId()).thenReturn(null);
when(uploadedVolume.getPoolId()).thenReturn(1L);
when(uploadedVolume.getState()).thenReturn(Volume.State.Uploaded);
when(_svc.volFactory.getVolume(8L)).thenReturn(uploadedVolume);
VolumeVO upVolume = new VolumeVO("root", 1L, 1L, 1L, 1L, 2L, "root", "root", Storage.ProvisioningType.THIN, 1, null,
null, "root", Volume.Type.ROOT);
upVolume.setPoolId(1L);
upVolume.setDataCenterId(1L);
upVolume.setState(Volume.State.Uploaded);
when(_svc._volsDao.findById(8L)).thenReturn(upVolume);
// helper dao methods mock
when(_svc._vmSnapshotDao.findByVm(any(Long.class))).thenReturn(new ArrayList<VMSnapshotVO>());
when(_svc._vmInstanceDao.findById(any(Long.class))).thenReturn(stoppedVm);
DataCenterVO enabledZone = Mockito.mock(DataCenterVO.class);
when(enabledZone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
when(_svc._dcDao.findById(anyLong())).thenReturn(enabledZone);
} finally {
txn.close("runVolumeDaoImplTest");
}
// helper methods mock
doNothing().when(_svc._accountMgr).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
doNothing().when(_svc._jobMgr).updateAsyncJobAttachment(any(Long.class), any(String.class), any(Long.class));
when(_svc._jobMgr.submitAsyncJob(any(AsyncJobVO.class), any(String.class), any(Long.class))).thenReturn(1L);
}
/**
* TESTS FOR DETACH ROOT VOLUME, COUNT=4
* @throws Exception
*/
@Test(expected = InvalidParameterValueException.class)
public void testDetachVolumeFromRunningVm() throws NoSuchFieldException, IllegalAccessException {
Field dedicateIdField = _detachCmdClass.getDeclaredField("id");
dedicateIdField.setAccessible(true);
dedicateIdField.set(detachCmd, 1L);
_svc.detachVolumeFromVM(detachCmd);
}
@Test(expected = InvalidParameterValueException.class)
public void testDetachVolumeFromStoppedHyperVVm() throws NoSuchFieldException, IllegalAccessException {
Field dedicateIdField = _detachCmdClass.getDeclaredField("id");
dedicateIdField.setAccessible(true);
dedicateIdField.set(detachCmd, 3L);
_svc.detachVolumeFromVM(detachCmd);
}
@Test(expected = InvalidParameterValueException.class)
public void testDetachVolumeOfManagedDataStore() throws NoSuchFieldException, IllegalAccessException {
Field dedicateIdField = _detachCmdClass.getDeclaredField("id");
dedicateIdField.setAccessible(true);
dedicateIdField.set(detachCmd, 4L);
_svc.detachVolumeFromVM(detachCmd);
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testDetachVolumeFromStoppedXenVm() throws NoSuchFieldException, IllegalAccessException {
thrown.expect(NullPointerException.class);
Field dedicateIdField = _detachCmdClass.getDeclaredField("id");
dedicateIdField.setAccessible(true);
dedicateIdField.set(detachCmd, 2L);
_svc.detachVolumeFromVM(detachCmd);
}
/**
* TESTS FOR ATTACH ROOT VOLUME, COUNT=7
*/
// Negative test - try to attach non-root non-datadisk volume
@Test(expected = InvalidParameterValueException.class)
public void attachIncorrectDiskType() throws NoSuchFieldException, IllegalAccessException {
_svc.attachVolumeToVM(1L, 5L, 0L);
}
// Negative test - attach root volume to running vm
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskToRunningVm() throws NoSuchFieldException, IllegalAccessException {
_svc.attachVolumeToVM(1L, 6L, 0L);
}
// Negative test - attach root volume to non-xen vm
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskToHyperVm() throws NoSuchFieldException, IllegalAccessException {
_svc.attachVolumeToVM(3L, 6L, 0L);
}
// Negative test - attach root volume from the managed data store
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskOfManagedDataStore() throws NoSuchFieldException, IllegalAccessException {
_svc.attachVolumeToVM(2L, 7L, 0L);
}
// Negative test - root volume can't be attached to the vm already having a root volume attached
@Test(expected = InvalidParameterValueException.class)
public void attachRootDiskToVmHavingRootDisk() throws NoSuchFieldException, IllegalAccessException {
_svc.attachVolumeToVM(4L, 6L, 0L);
}
// Negative test - root volume in uploaded state can't be attached
@Test(expected = InvalidParameterValueException.class)
public void attachRootInUploadedState() throws NoSuchFieldException, IllegalAccessException {
_svc.attachVolumeToVM(2L, 8L, 0L);
}
// Positive test - attach ROOT volume in correct state, to the vm not having root volume attached
@Test
public void attachRootVolumePositive() throws NoSuchFieldException, IllegalAccessException {
thrown.expect(NullPointerException.class);
_svc.attachVolumeToVM(2L, 6L, 0L);
}
// volume not Ready
@Test(expected = InvalidParameterValueException.class)
public void testTakeSnapshotF1() throws ResourceAllocationException {
when(_volFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
when(volumeInfoMock.getState()).thenReturn(Volume.State.Allocated);
when(volumeInfoMock.getPoolId()).thenReturn(1L);
_svc.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null);
}
@Test
public void testTakeSnapshotF2() throws ResourceAllocationException {
when(_volFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
when(volumeInfoMock.getState()).thenReturn(Volume.State.Ready);
when(volumeInfoMock.getInstanceId()).thenReturn(null);
when(volumeInfoMock.getPoolId()).thenReturn(1L);
when (volService.takeSnapshot(Mockito.any(VolumeInfo.class))).thenReturn(snapshotInfoMock);
_svc.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null);
}
@Test
public void testNullGetVolumeNameFromCmd() {
when(createVol.getVolumeName()).thenReturn(null);
Assert.assertNotNull(_svc.getVolumeNameFromCommand(createVol));
}
@Test
public void testEmptyGetVolumeNameFromCmd() {
when(createVol.getVolumeName()).thenReturn("");
Assert.assertNotNull(_svc.getVolumeNameFromCommand(createVol));
}
@Test
public void testBlankGetVolumeNameFromCmd() {
when(createVol.getVolumeName()).thenReturn(" ");
Assert.assertNotNull(_svc.getVolumeNameFromCommand(createVol));
}
@Test
public void testNonEmptyGetVolumeNameFromCmd() {
when(createVol.getVolumeName()).thenReturn("abc");
Assert.assertSame(_svc.getVolumeNameFromCommand(createVol), "abc");
}
@Test
public void testUpdateMissingRootDiskControllerWithNullChainInfo() {
_svc.updateMissingRootDiskController(null, null);
verify(_svc._userVmMgr, times(0)).persistDeviceBusInfo(any(UserVmVO.class), anyString());
}
@Test
public void testUpdateMissingRootDiskControllerWithValidChainInfo() {
UserVmVO vm = _svc._userVmDao.findById(1L);
assert vm.getType() == VirtualMachine.Type.User;
_svc.updateMissingRootDiskController(vm, "{\"diskDeviceBusName\":\"scsi0:0\",\"diskChain\":[\"[somedatastore] i-3-VM-somePath/ROOT-1.vmdk\"]}");
verify(_svc._userVmMgr, times(1)).persistDeviceBusInfo(any(UserVmVO.class), eq("scsi"));
}
@Test
/**
* Setting locationType for a non-managed storage should give an error
*/
public void testAllocSnapshotNonManagedStorageArchive() {
try {
_svc.allocSnapshot(6L, 1L, "test", Snapshot.LocationType.SECONDARY);
} catch (InvalidParameterValueException e) {
Assert.assertEquals(e.getMessage(), "VolumeId: 6 LocationType is supported only for managed storage");
return;
} catch (ResourceAllocationException e) {
Assert.fail("Unexpected excepiton " + e.getMessage());
}
Assert.fail("Expected Exception for archive in non-managed storage");
}
@After
public void tearDown() {
CallContext.unregister();
}
}