package org.ovirt.engine.core.bll.storage.ovfstore;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.ovirt.engine.core.utils.MockConfigRule.mockConfig;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.ovirt.engine.core.bll.BaseCommandTest;
import org.ovirt.engine.core.bll.utils.VmDeviceUtils;
import org.ovirt.engine.core.common.action.ProcessOvfUpdateForStoragePoolParameters;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainOvfInfo;
import org.ovirt.engine.core.common.businessentities.StorageDomainOvfInfoStatus;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.StoragePool;
import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMap;
import org.ovirt.engine.core.common.businessentities.StoragePoolStatus;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.VmTemplateStatus;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.constants.StorageConstants;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.KeyValuePairCompat;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.StorageDomainOvfInfoDao;
import org.ovirt.engine.core.dao.StoragePoolDao;
import org.ovirt.engine.core.dao.VmAndTemplatesGenerationsDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.dao.VmTemplateDao;
import org.ovirt.engine.core.utils.MockConfigRule;
public class ProcessOvfUpdateForStoragePoolCommandTest extends BaseCommandTest {
private static final int ITEMS_COUNT_PER_UPDATE = 100;
@Spy
@InjectMocks
private ProcessOvfUpdateForStoragePoolCommand<ProcessOvfUpdateForStoragePoolParameters> command =
new ProcessOvfUpdateForStoragePoolCommand<>(new ProcessOvfUpdateForStoragePoolParameters(), null);
@Mock
private StoragePoolDao storagePoolDao;
@Mock
private VmAndTemplatesGenerationsDao vmAndTemplatesGenerationsDao;
@Mock
private VmDao vmDao;
@Mock
private VmStaticDao vmStaticDao;
@Mock
private SnapshotDao snapshotDao;
@Mock
private VmTemplateDao vmTemplateDao;
@Mock
private StorageDomainDao storageDomainDao;
@Mock
private StorageDomainOvfInfoDao storageDomainOvfInfoDao;
@InjectMocks
private VmDeviceUtils vmDeviceUtils;
@Mock
private ClusterDao clusterDao;
@Spy
private OvfUpdateProcessHelper ovfUpdateProcessHelper;
private StoragePool pool1;
private Map<Guid, VM> vms;
private Map<Guid, VmTemplate> templates;
private Map<Guid, Long> executedUpdatedOvfGenerationIdsInDb;
private Set<Guid> executedOvfUpdatedDomains;
private Map<Guid, Pair<List<StorageDomainOvfInfo>, StorageDomain>> poolDomainsOvfInfo;
@ClassRule
public static MockConfigRule mcr = new MockConfigRule(
mockConfig(ConfigValues.StorageDomainOvfStoreCount, 1),
mockConfig(ConfigValues.OvfItemsCountPerUpdate, ITEMS_COUNT_PER_UPDATE)
);
@Before
public void setUp() {
// init members
initMembers();
// mock ovf data updater methods
doNothing().when(ovfUpdateProcessHelper).loadTemplateData(any(VmTemplate.class));
doNothing().when(ovfUpdateProcessHelper).loadVmData(any(VM.class));
doNothing().when(command).updateVmDisksFromDb(any(VM.class));
doNothing().when(command).updateTemplateDisksFromDb(any(VmTemplate.class));
// dao related mocks.
doReturn(1L).when(vmStaticDao).getDbGeneration(any(Guid.class));
doReturn(pool1).when(command).getStoragePool();
List<Snapshot> snapshots = new ArrayList<>();
doReturn(snapshots).when(snapshotDao).getAllWithConfiguration(any(Guid.class));
// needed for ovf writer utility
injectorRule.bind(ClusterDao.class, clusterDao);
mockAnswers();
}
private void initMembers() {
executedUpdatedOvfGenerationIdsInDb = new HashMap<>();
poolDomainsOvfInfo = new HashMap<>();
vms = new HashMap<>();
templates = new HashMap<>();
pool1 = new StoragePool();
pool1.setId(Guid.newGuid());
pool1.setStatus(StoragePoolStatus.Maintenance);
performStoragePoolInitOps(pool1);
}
private void performStoragePoolInitOps(StoragePool pool) {
executedUpdatedOvfGenerationIdsInDb = new HashMap<>();
for (int i = 0; i < 2; i++) {
Guid domainId = Guid.newGuid();
StorageDomainOvfInfo ovfInfo = new StorageDomainOvfInfo(domainId, null, null, StorageDomainOvfInfoStatus.UPDATED, null);
StorageDomain domain = new StorageDomain();
domain.setId(domainId);
domain.setStoragePoolIsoMapData(new StoragePoolIsoMap(domainId, pool.getId(), StorageDomainStatus.Active));
poolDomainsOvfInfo.put(domainId, new Pair<>(Collections.singletonList(ovfInfo), domain));
}
}
private void mockAnswers() {
doAnswer(invocation -> {
VM vm = (VM) invocation.getArguments()[0];
return vm.getId().toString();
}).when(ovfUpdateProcessHelper).generateVmMetadata(any(VM.class), anyList());
doAnswer(invocation -> {
VmTemplate template = (VmTemplate) invocation.getArguments()[0];
return template.getId().toString();
}).when(ovfUpdateProcessHelper).generateVmTemplateMetadata(any(VmTemplate.class), anyList());
doAnswer(invocation -> {
List<Guid> neededIds = (List<Guid>) invocation.getArguments()[0];
return neededIds.stream().map(id -> vms.get(id)).collect(Collectors.toList());
}).when(vmDao).getVmsByIds(anyList());
doAnswer(invocation -> {
List<Guid> neededIds = (List<Guid>) invocation.getArguments()[0];
return neededIds.stream().map(id -> templates.get(id)).collect(Collectors.toList());
}).when(vmTemplateDao).getVmTemplatesByIds(anyList());
doAnswer(invocation -> {
Map<Guid, KeyValuePairCompat<String, List<Guid>>> updateMap =
(Map<Guid, KeyValuePairCompat<String, List<Guid>>>) invocation.getArguments()[1];
assertTrue("too many ovfs were sent in one vdsm call", updateMap.size() <= ITEMS_COUNT_PER_UPDATE);
return true;
}).when(ovfUpdateProcessHelper).executeUpdateVmInSpmCommand(any(Guid.class), anyMap(), any(Guid.class));
doReturn(true).when(ovfUpdateProcessHelper).executeRemoveVmInSpm(any(Guid.class), any(Guid.class), any(Guid.class));
doAnswer(invocation -> {
List<Guid> ids = (List<Guid>) invocation.getArguments()[0];
List<Long> values = (List<Long>) invocation.getArguments()[1];
assertFalse("update of ovf version in db shouldn't be called with an empty value list",
values.isEmpty());
assertTrue("update of ovf version in db shouldn't be called with more items then MAX_ITEMS_PER_SQL_STATEMENT",
values.size() <= StorageConstants.OVF_MAX_ITEMS_PER_SQL_STATEMENT);
assertEquals("the size of the list of ids for update is not the same as the size of the " +
"list with the new ovf values", values.size(), ids.size());
Guid[] ids_array = ids.toArray(new Guid[ids.size()]);
Long[] values_array = values.toArray(new Long[values.size()]);
for (int i = 0; i < ids_array.length; i++) {
executedUpdatedOvfGenerationIdsInDb.put(ids_array[i],
values_array[i]);
}
return null;
}).when(vmAndTemplatesGenerationsDao).updateOvfGenerations(anyList(), anyList(), anyList());
doAnswer(invocation -> {
StoragePoolStatus desiredStatus = (StoragePoolStatus) invocation.getArguments()[0];
return buildStoragePoolsList().stream()
.filter(p -> desiredStatus.equals(p.getStatus()))
.collect(Collectors.toList());
}).when(storagePoolDao).getAllByStatus(any(StoragePoolStatus.class));
doReturn(poolDomainsOvfInfo.values().stream().map(Pair::getSecond).collect(Collectors.toList()))
.when(storageDomainDao).getAllForStoragePool(any(Guid.class));
doAnswer(invocation -> {
Guid domainId = (Guid) invocation.getArguments()[0];
Pair<List<StorageDomainOvfInfo>, StorageDomain> pair = poolDomainsOvfInfo.get(domainId);
if (pair != null) {
return pair.getFirst();
}
return null;
}).when(storageDomainOvfInfoDao).getAllForDomain(any(Guid.class));
}
private List<StoragePool> buildStoragePoolsList() {
return Collections.singletonList(pool1);
}
private VM createVm(Guid id, VMStatus status) {
VM vm = new VM();
vm.setStatus(status);
vm.setStaticData(createVmStatic());
vm.setId(id);
return vm;
}
public VmStatic createVmStatic() {
VmStatic vms = new VmStatic();
vms.setDbGeneration(1L);
return vms;
}
private VmTemplate createVmTemplate(Guid id, VmTemplateStatus templateStatus) {
VmTemplate template = new VmTemplate();
template.setStatus(templateStatus);
template.setDbGeneration(1L);
template.setId(id);
return template;
}
private List<Guid> generateGuidList(int size) {
return IntStream.range(0, size).mapToObj(x -> Guid.newGuid()).collect(Collectors.toList());
}
private Map<Guid, VM> generateVmsMapByGuids(List<Guid> ids,
int diskCount,
VMStatus vmStatus,
ImageStatus diskStatus) {
Map<Guid, VM> toReturn = new HashMap<>();
for (Guid id : ids) {
VM vm = createVm(id, vmStatus);
for (int i = 0; i < diskCount; i++) {
DiskImage image = createDiskImage(diskStatus);
vm.getDiskMap().put(image.getId(), image);
vm.getDiskList().add(image);
}
toReturn.put(vm.getId(), vm);
}
return toReturn;
}
private Map<Guid, VmTemplate> generateVmTemplatesMapByGuids(List<Guid> ids,
int diskCount,
VmTemplateStatus templateStatus,
ImageStatus diskStatus) {
Map<Guid, VmTemplate> toReturn = new HashMap<>();
for (Guid id : ids) {
VmTemplate template = createVmTemplate(id, templateStatus);
for (int i = 0; i < diskCount; i++) {
DiskImage image = createDiskImage(diskStatus);
template.getDiskTemplateMap().put(image.getId(), image);
template.getDiskList().add(image);
}
toReturn.put(template.getId(), template);
}
return toReturn;
}
private DiskImage createDiskImage(ImageStatus status) {
DiskImage disk = new DiskImage();
disk.setId(Guid.newGuid());
disk.setImageStatus(status);
ArrayList<Guid> storageIds = new ArrayList<>();
storageIds.add(poolDomainsOvfInfo.keySet().iterator().next());
disk.setStorageIds(storageIds);
return disk;
}
private void initTestForPool(StoragePool pool, List<Guid> vmGuids, List<Guid> templatesGuids, List<Guid> removedGuids) {
Guid poolId = pool.getId();
doReturn(vmGuids).when(vmAndTemplatesGenerationsDao).getVmsIdsForOvfUpdate(poolId);
doReturn(templatesGuids).when(vmAndTemplatesGenerationsDao).getVmTemplatesIdsForOvfUpdate(poolId);
doReturn(removedGuids).when(vmAndTemplatesGenerationsDao).getIdsForOvfDeletion(poolId);
pool.setStatus(StoragePoolStatus.Up);
}
private void verifyCorrectOvfDataUpdaterRun(Collection<Guid> needToBeUpdated) {
assertTrue("not all needed vms/templates were updated in db",
CollectionUtils.isEqualCollection(executedUpdatedOvfGenerationIdsInDb.keySet(),
needToBeUpdated));
for (Map.Entry<Guid, Long> storagePoolGenerationEntry : executedUpdatedOvfGenerationIdsInDb.entrySet()) {
boolean isCorrectVersion = false;
if (vms.get(storagePoolGenerationEntry.getKey()) != null) {
isCorrectVersion =
storagePoolGenerationEntry.getValue()
.equals(vms.get(storagePoolGenerationEntry.getKey()).getDbGeneration());
} else if (templates.get(storagePoolGenerationEntry.getKey()) != null) {
isCorrectVersion =
storagePoolGenerationEntry.getValue()
.equals(templates.get(storagePoolGenerationEntry.getKey()).getDbGeneration());
}
assertTrue("wrong new ovf version persisted for vm/template", isCorrectVersion);
}
}
private void addVms(List<Guid> vmGuids, int diskCount, VMStatus vmStatus, ImageStatus vmImageStatus) {
vms.putAll(generateVmsMapByGuids(vmGuids, diskCount, vmStatus, vmImageStatus));
}
private void verifyOvfUpdatedForSupportedPools(List<Guid> poolsRequiredUpdate,
Map<Guid, List<Guid>> domainsRequiredUpdateForPool) {
for (Guid storagePoolId : poolsRequiredUpdate) {
for (Guid updatedDomainForPool : executedOvfUpdatedDomains) {
assertTrue("ovf update for domain has been executed with wrong pool",
poolDomainsOvfInfo.containsKey(updatedDomainForPool));
if (domainsRequiredUpdateForPool.get(storagePoolId) != null) {
assertTrue("ovf updated hasn't been executed on needed domain",
domainsRequiredUpdateForPool.get(storagePoolId).contains(updatedDomainForPool));
}
}
}
}
private void addTemplates(List<Guid> templatesGuids,
int diskCount,
VmTemplateStatus templateStatus,
ImageStatus templateImageStatus) {
templates.putAll(generateVmTemplatesMapByGuids(templatesGuids,
diskCount,
templateStatus,
templateImageStatus));
}
@Test
public void testOvfDataUpdaterRunWithUpdateAndRemoveHigherThanCountOnePool() {
int size = 3 * ITEMS_COUNT_PER_UPDATE + 10;
List<Guid> vmGuids = generateGuidList(size);
List<Guid> templatesGuids = generateGuidList(size);
List<Guid> removedGuids = generateGuidList(size);
addVms(vmGuids, 2, VMStatus.Down, ImageStatus.OK);
addTemplates(templatesGuids, 2, VmTemplateStatus.OK, ImageStatus.OK);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
executeCommand();
verify(command, times(numberOfTimesToBeCalled(size, true))).performOvfUpdate(anyMap());
List<Guid> idsThatNeededToBeUpdated = new LinkedList<>(vmGuids);
idsThatNeededToBeUpdated.addAll(templatesGuids);
verifyCorrectOvfDataUpdaterRun(idsThatNeededToBeUpdated);
verifyOvfUpdatedForSupportedPools(Collections.singletonList(pool1.getId()), Collections.emptyMap());
}
private void executeCommand() {
command.executeCommand();
executedOvfUpdatedDomains = (Set<Guid>)command.getActionReturnValue();
}
@Test
public void testOvfDataUpdaterRunWithUpdateAndRemoveLowerThanCount() {
int size = ITEMS_COUNT_PER_UPDATE - 1;
List<Guid> vmGuids = generateGuidList(size);
addVms(vmGuids, 2, VMStatus.Down, ImageStatus.OK);
List<Guid> templatesGuids = generateGuidList(size);
addTemplates(templatesGuids, 2, VmTemplateStatus.OK, ImageStatus.OK);
List<Guid> removedGuids = generateGuidList(size);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
executeCommand();
verify(command, times(numberOfTimesToBeCalled(size, true))).performOvfUpdate(anyMap());
List<Guid> needToBeUpdated = new LinkedList<>(vmGuids);
needToBeUpdated.addAll(templatesGuids);
verifyCorrectOvfDataUpdaterRun(needToBeUpdated);
verifyOvfUpdatedForSupportedPools(Collections.singletonList(pool1.getId()), Collections.emptyMap());
}
@Test
public void testOvfDataUpdaterAllDisksAreLockedNonToRemove() {
int size = ITEMS_COUNT_PER_UPDATE - 1;
List<Guid> vmGuids = generateGuidList(size);
List<Guid> removedGuids = Collections.emptyList();
List<Guid> templatesGuids = generateGuidList(size);
addTemplates(templatesGuids, 2, VmTemplateStatus.OK, ImageStatus.LOCKED);
addVms(vmGuids, 2, VMStatus.Down, ImageStatus.LOCKED);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
executeCommand();
verify(command, never()).performOvfUpdate(anyMap());
verifyCorrectOvfDataUpdaterRun(Collections.emptyList());
}
@Test
public void testOvfDataUpdaterPartOfDisksAreLocked() {
int size = ITEMS_COUNT_PER_UPDATE - 1;
// unlocked vms/templates
List<Guid> vmGuids = generateGuidList(size);
List<Guid> templatesGuids = generateGuidList(size);
addVms(vmGuids, 2, VMStatus.Down, ImageStatus.OK);
addTemplates(templatesGuids, 2, VmTemplateStatus.OK, ImageStatus.OK);
// locked vms/templates
List<Guid> lockedVmGuids = generateGuidList(size);
List<Guid> lockedTemplatesGuids = generateGuidList(size);
addVms(lockedVmGuids, 2, VMStatus.Down, ImageStatus.LOCKED);
addTemplates(lockedTemplatesGuids, 2, VmTemplateStatus.OK, ImageStatus.LOCKED);
// ids for removal
List<Guid> removedGuids = generateGuidList(size);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
executeCommand();
verify(command, times(numberOfTimesToBeCalled(size, true))).performOvfUpdate(anyMap());
// list of ids that should have been updated.
List<Guid> needToBeUpdated = new LinkedList<>(vmGuids);
needToBeUpdated.addAll(templatesGuids);
verifyCorrectOvfDataUpdaterRun(needToBeUpdated);
verifyOvfUpdatedForSupportedPools(Collections.emptyList(), Collections.emptyMap());
}
private int numberOfTimesToBeCalled(int size, boolean isBothVmAndTemplates) {
int toReturn = 0;
if (size % ITEMS_COUNT_PER_UPDATE != 0) {
toReturn++;
}
toReturn += size / ITEMS_COUNT_PER_UPDATE;
if (isBothVmAndTemplates) {
toReturn *= 2;
}
return toReturn;
}
@Test
public void testOvfDataUpdaterAllVmsAndTemplatesAreLocked() {
int size = ITEMS_COUNT_PER_UPDATE - 1;
List<Guid> vmGuids = generateGuidList(size);
addVms(vmGuids, 2, VMStatus.ImageLocked, ImageStatus.OK);
List<Guid> removedGuids = generateGuidList(size);
List<Guid> templatesGuids = generateGuidList(size);
addTemplates(templatesGuids, 2, VmTemplateStatus.Locked, ImageStatus.OK);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
command.executeCommand();
verify(command, never()).performOvfUpdate(anyMap());
verifyCorrectOvfDataUpdaterRun(Collections.emptyList());
verifyOvfUpdatedForSupportedPools(Collections.emptyList(), Collections.emptyMap());
}
@Test
public void testOvfDataUpdaterPartOfVmsAndTemplatesAreLocked() {
int size = ITEMS_COUNT_PER_UPDATE;
List<Guid> vmGuids = generateGuidList(size);
List<Guid> removedGuids = generateGuidList(size);
List<Guid> templatesGuids = generateGuidList(size);
addVms(vmGuids, 2, VMStatus.ImageLocked, ImageStatus.OK);
addTemplates(templatesGuids, 2, VmTemplateStatus.Locked, ImageStatus.OK);
List<Guid> vmGuidsUnlocked = generateGuidList(size);
List<Guid> templatesGuidsUnlocked = generateGuidList(size);
addVms(vmGuidsUnlocked, 2, VMStatus.Down, ImageStatus.OK);
addTemplates(templatesGuidsUnlocked, 2, VmTemplateStatus.OK, ImageStatus.OK);
vmGuids.addAll(vmGuidsUnlocked);
templatesGuids.addAll(templatesGuidsUnlocked);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
executeCommand();
List<Guid> neededToBeUpdated = new LinkedList<>(vmGuidsUnlocked);
neededToBeUpdated.addAll(templatesGuidsUnlocked);
verify(command, times(numberOfTimesToBeCalled(size, true))).performOvfUpdate(anyMap());
verifyCorrectOvfDataUpdaterRun(neededToBeUpdated);
verifyOvfUpdatedForSupportedPools(Collections.emptyList(), Collections.emptyMap());
}
@Test
public void testUpdatedDbGeneration() {
int size = 3 * ITEMS_COUNT_PER_UPDATE + 10;
List<Guid> vmGuids = generateGuidList(size);
List<Guid> templatesGuids = generateGuidList(size);
List<Guid> removedGuids = Collections.emptyList();
addVms(vmGuids, 2, VMStatus.Down, ImageStatus.OK);
addTemplates(templatesGuids, 2, VmTemplateStatus.OK, ImageStatus.OK);
initTestForPool(pool1, vmGuids, templatesGuids, removedGuids);
doReturn(2L).when(vmStaticDao).getDbGeneration(any(Guid.class));
executeCommand();
verify(command, never()).performOvfUpdate(anyMap());
List<Guid> idsThatNeededToBeUpdated = new LinkedList<>(vmGuids);
idsThatNeededToBeUpdated.addAll(templatesGuids);
verifyCorrectOvfDataUpdaterRun(Collections.emptyList());
verifyOvfUpdatedForSupportedPools(Collections.emptyList(), Collections.emptyMap());
}
@Test
public void testUpdateCalledForUnupdatedDomain() {
Guid poolId = pool1.getId();
StorageDomainOvfInfo ovfInfo = poolDomainsOvfInfo.entrySet().iterator().next().getValue().getFirst().get(0);
ovfInfo.setStatus(StorageDomainOvfInfoStatus.OUTDATED);
initTestForPool(pool1,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList());
executeCommand();
verify(command, never()).performOvfUpdate(anyMap());
Map<Guid, List<Guid>> domainsRequiredUpdateForPool =
Collections.singletonMap(poolId, Collections.singletonList(ovfInfo.getStorageDomainId()));
verifyOvfUpdatedForSupportedPools(Collections.singletonList(poolId), domainsRequiredUpdateForPool);
}
}