package org.ovirt.engine.core.utils.ovf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.ovirt.engine.core.common.businessentities.ArchitectureType;
import org.ovirt.engine.core.common.businessentities.BusinessEntity;
import org.ovirt.engine.core.common.businessentities.DisplayType;
import org.ovirt.engine.core.common.businessentities.GraphicsType;
import org.ovirt.engine.core.common.businessentities.OriginType;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmBase;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface;
import org.ovirt.engine.core.common.businessentities.network.VmNetworkStatistics;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskInterface;
import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement;
import org.ovirt.engine.core.common.businessentities.storage.Image;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat;
import org.ovirt.engine.core.common.businessentities.storage.VolumeType;
import org.ovirt.engine.core.common.osinfo.OsRepository;
import org.ovirt.engine.core.common.queries.VmIconIdSizePair;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.utils.SimpleDependencyInjector;
import org.ovirt.engine.core.common.utils.VmDeviceType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.utils.MockConfigRule;
import org.ovirt.engine.core.utils.RandomUtils;
import org.ovirt.engine.core.utils.RandomUtilsSeedingRule;
@RunWith(MockitoJUnitRunner.class)
public class OvfManagerTest {
private static final Guid SMALL_DEFAULT_ICON_ID = Guid.createGuidFromString("00000000-0000-0000-0000-00000000000a");
private static final Guid LARGE_DEFAULT_ICON_ID = Guid.createGuidFromString("00000000-0000-0000-0000-00000000000b");
private static final Guid SMALL_ICON_ID = Guid.createGuidFromString("00000000-0000-0000-0000-00000000000c");
private static final Guid LARGE_ICON_ID = Guid.createGuidFromString("00000000-0000-0000-0000-00000000000d");
private static final int DEFAULT_OS_ID = OsRepository.DEFAULT_X86_OS;
private static final int EXISTING_OS_ID = 1;
private static final int NONEXISTING_OS_ID = 2;
private static final int MIN_ENTITY_NAME_LENGTH = 3;
private static final int MAX_ENTITY_NAME_LENGTH = 30;
@ClassRule
public static MockConfigRule mockConfigRule = new MockConfigRule();
@Rule
public RandomUtilsSeedingRule rusr = new RandomUtilsSeedingRule();
@InjectMocks
@Spy
private OvfManager manager = new OvfManager();
@Mock
private OvfVmIconDefaultsProvider iconDefaultsProvider;
@BeforeClass
public static void setUpClass() {
OsRepository osRepository = mock(OsRepository.class);
SimpleDependencyInjector.getInstance().bind(OsRepository.class, osRepository);
final HashMap<Integer, String> osIdsToNames = new HashMap<>();
osIdsToNames.put(DEFAULT_OS_ID, "os_name_a");
osIdsToNames.put(EXISTING_OS_ID, "os_name_b");
final List<Pair<GraphicsType, DisplayType>> gndDefaultOs = new ArrayList<>();
gndDefaultOs.add(new Pair<>(GraphicsType.SPICE, DisplayType.cirrus));
gndDefaultOs.add(new Pair<>(GraphicsType.VNC, DisplayType.cirrus));
final List<Pair<GraphicsType, DisplayType>> gndExistingOs = new ArrayList<>();
gndExistingOs.add(new Pair<>(GraphicsType.SPICE, DisplayType.cirrus));
when(osRepository.getArchitectureFromOS(anyInt())).thenReturn(ArchitectureType.x86_64);
when(osRepository.getUniqueOsNames()).thenReturn(osIdsToNames);
when(osRepository.getOsIdByUniqueName(anyString())).thenAnswer(
invocation-> osIdsToNames.entrySet()
.stream()
.filter(k -> invocation.getArguments()[0].equals(k.getValue()))
.map(Map.Entry::getKey)
.findFirst()
.orElse(0));
when(osRepository.getGraphicsAndDisplays(eq(DEFAULT_OS_ID), any(Version.class))).thenReturn(gndDefaultOs);
when(osRepository.getGraphicsAndDisplays(eq(EXISTING_OS_ID), any(Version.class))).thenReturn(gndExistingOs);
}
@Before
public void setUp() throws Exception {
doNothing().when(manager).updateBootOrderOnDevices(any(VmBase.class), anyBoolean());
Map<Integer, VmIconIdSizePair> iconDefaults = new HashMap<>();
iconDefaults.put(DEFAULT_OS_ID, new VmIconIdSizePair(SMALL_DEFAULT_ICON_ID, LARGE_DEFAULT_ICON_ID));
iconDefaults.put(EXISTING_OS_ID, new VmIconIdSizePair(SMALL_ICON_ID, LARGE_ICON_ID));
when(iconDefaultsProvider.getVmIconDefaults()).thenReturn(iconDefaults);
}
private static void assertVm(VM vm, VM newVm, long expectedDbGeneration) {
assertEquals("imported vm is different than expected", vm, newVm);
assertEquals("imported db generation is different than expected", expectedDbGeneration, newVm.getDbGeneration());
// Icons are actually not stored in snapshots, so they are excluded from comparison
newVm.getStaticData().setSmallIconId(vm.getStaticData().getSmallIconId());
newVm.getStaticData().setLargeIconId(vm.getStaticData().getLargeIconId());
assertEquals(vm.getStaticData(), newVm.getStaticData());
}
@Test
public void testVmOvfCreationDefaultGraphicsDevice() throws Exception {
VM vm = createVM();
vm.setDefaultDisplayType(DisplayType.cirrus);
vm.setVmOs(DEFAULT_OS_ID);
String xml = manager.exportVm(vm, new ArrayList<>(), Version.getLast());
assertNotNull(xml);
final VM newVm = new VM();
manager.importVm(xml, newVm, new ArrayList<>(), new ArrayList<>());
int graphicsDeviceCount = 0;
for (VmDevice device : newVm.getManagedVmDeviceMap().values()) {
if (device.getType() == VmDeviceGeneralType.GRAPHICS) {
graphicsDeviceCount++;
assertEquals(device.getDevice(), VmDeviceType.VNC.getName());
}
}
assertEquals(1, graphicsDeviceCount);
}
@Test
public void testVmOvfCreationDefaultGraphicsDeviceFallbackToSupported() throws Exception {
VM vm = createVM();
vm.setDefaultDisplayType(DisplayType.cirrus);
vm.setVmOs(EXISTING_OS_ID);
String xml = manager.exportVm(vm, new ArrayList<>(), Version.getLast());
assertNotNull(xml);
final VM newVm = new VM();
manager.importVm(xml, newVm, new ArrayList<>(), new ArrayList<>());
int graphicsDeviceCount = 0;
for (VmDevice device : newVm.getManagedVmDeviceMap().values()) {
if (device.getType() == VmDeviceGeneralType.GRAPHICS) {
graphicsDeviceCount++;
assertEquals(device.getDevice(), VmDeviceType.SPICE.getName());
}
}
assertEquals(1, graphicsDeviceCount);
}
@Test
public void testVmOvfImportWithoutDbGeneration() throws Exception {
VM vm = createVM();
String xml = manager.exportVm(vm, new ArrayList<>(), Version.v3_6);
assertNotNull(xml);
final VM newVm = new VM();
assertTrue(xml.contains("Generation"));
String replacedXml = xml.replaceAll("Generation", "test_replaced");
manager.importVm(replacedXml, newVm, new ArrayList<>(), new ArrayList<>());
assertVm(vm, newVm, 1);
}
@Test
public void testTemplateOvfCreation() throws Exception {
VmTemplate template = createVmTemplate();
String xml = manager.exportTemplate(template, new ArrayList<>(), Version.v3_6);
assertNotNull(xml);
final VmTemplate newtemplate = new VmTemplate();
manager.importTemplate(xml, newtemplate, new ArrayList<>(), new ArrayList<>());
assertEquals("imported template is different than expected", newtemplate, template);
assertEquals("imported db generation is different than expected", template.getDbGeneration(), newtemplate.getDbGeneration());
}
@Test
public void testIconsSetForKnownOs() throws Exception {
VM vm = createVM();
vm.setVmOs(EXISTING_OS_ID);
final VM newVm = serializeAndDeserialize(vm);
assertEquals(SMALL_ICON_ID, newVm.getStaticData().getSmallIconId());
assertEquals(LARGE_ICON_ID, newVm.getStaticData().getLargeIconId());
}
@Test
public void testIconsSetForUnknownOs() throws Exception {
VM vm = createVM();
vm.setVmOs(NONEXISTING_OS_ID);
final VM newVm = serializeAndDeserialize(vm);
assertEquals(SMALL_DEFAULT_ICON_ID, newVm.getStaticData().getSmallIconId());
assertEquals(LARGE_DEFAULT_ICON_ID, newVm.getStaticData().getLargeIconId());
}
@Test
public void testVmExportAndImportAndExportAgainSymmetrical() throws Exception {
VM vm = createVM();
ArrayList<DiskImage> disks = createDisksAndDiskVmElements(vm);
String xml = manager.exportVm(vm, disks, Version.v4_0);
assertNotNull(xml);
VM newVm = new VM();
ArrayList<DiskImage> newDisks = new ArrayList<>();
manager.importVm(xml, newVm, newDisks, new ArrayList<>());
String newXml = manager.exportVm(vm, disks, Version.v4_0);
assertEquals(deleteExportDateValueFromXml(xml), deleteExportDateValueFromXml(newXml));
}
@Test
public void testVmExportAndImportIdentical() throws Exception {
VM vm = createVM();
ArrayList<DiskImage> disks = createDisksAndDiskVmElements(vm);
String xml = manager.exportVm(vm, disks, Version.v4_0);
assertNotNull(xml);
VM newVm = new VM();
ArrayList<DiskImage> newDisks = new ArrayList<>();
ArrayList<VmNetworkInterface> newInterfaces = new ArrayList<>();
manager.importVm(xml, newVm, newDisks, newInterfaces);
assertVm(vm, newVm, vm.getDbGeneration());
assertCollection(vm.getInterfaces(), newInterfaces);
assertCollection(disks, newDisks,
diskPair -> diskPair.getFirst().getDiskVmElementForVm(vm.getId()).
equals(diskPair.getSecond().getDiskVmElementForVm(vm.getId())));
}
private <T extends BusinessEntity<?>> void assertCollection(List<T> colA, List<T> colB) {
assertCollection(colA, colB, null);
}
private <T extends BusinessEntity<?>> void assertCollection(List<T> colA, List<T> colB, Function<Pair<T, T>, Boolean> function) {
assertEquals(colA.size(), colB.size());
assertEquals(0, CollectionUtils.disjunction(colA, colB).size());
// Might look a bit overkill to check equals as well but disjunction is based on the hash so double checking is good
for (T itemA : colA) {
T itemB = colB.stream().filter(t -> t.getId().equals(itemA.getId())).findFirst().get();
assertEquals(itemA, itemB);
if (function != null) {
assertTrue(function.apply(new Pair<>(itemA, itemB)));
}
}
}
// TODO: An ugly hack since the writer writes the export date with the current time of the export which is
// different obviously given the time passed between the two exports.
// for now the export date will just be removed but in the future it's better to inject the date to the
// writer.
private String deleteExportDateValueFromXml(String xml) throws Exception{
return xml.replaceFirst("<ExportDate>[\\s\\S]*?<\\/ExportDate>", "");
}
private VM serializeAndDeserialize(VM inputVm) throws OvfReaderException {
String xml = manager.exportVm(inputVm, new ArrayList<>(), Version.v3_6);
assertNotNull(xml);
final VM resultVm = new VM();
assertTrue(xml.contains("Generation"));
String replacedXml = xml.replaceAll("Generation", "test_replaced");
manager.importVm(replacedXml, resultVm, new ArrayList<>(), new ArrayList<>());
return resultVm;
}
private static VM createVM() {
VM vm = new VM();
vm.setName("test-vm");
vm.setOrigin(OriginType.OVIRT);
vm.setId(Guid.newGuid());
vm.setVmDescription("test-description");
vm.setTimeZone("Israel Standard Time");
vm.setDbGeneration(2L);
vm.setSingleQxlPci(false);
vm.setClusterArch(ArchitectureType.x86_64);
vm.setVmOs(EXISTING_OS_ID);
initInterfaces(vm);
return vm;
}
private static void initInterfaces(VM vm) {
List<VmNetworkInterface> ifaces = new ArrayList<>();
RandomUtils rnd = RandomUtils.instance();
for (int i = 0; i < rnd.nextInt(2, 10); i++) {
VmNetworkInterface vmInterface = new VmNetworkInterface();
vmInterface.setStatistics(new VmNetworkStatistics());
vmInterface.setId(Guid.newGuid());
vmInterface.setVmId(vm.getId());
vmInterface.setName(generateRandomName());
vmInterface.setVnicProfileName(generateRandomName());
vmInterface.setNetworkName(generateRandomName());
vmInterface.setLinked(rnd.nextBoolean());
vmInterface.setSpeed(rnd.nextInt(1000));
vmInterface.setType(rnd.nextInt(10));
vmInterface.setMacAddress(rnd.nextMacAddress());
ifaces.add(vmInterface);
}
vm.setInterfaces(ifaces);
}
private static ArrayList<DiskImage> createDisksAndDiskVmElements(VM vm) {
ArrayList<DiskImage> disks = new ArrayList<>();
RandomUtils rnd = RandomUtils.instance();
for (int i = 0; i < rnd.nextInt(3, 10); i++) {
DiskImage disk = createVmDisk(vm);
disks.add(disk);
}
return disks;
}
private static DiskImage createVmDisk(VM vm) {
RandomUtils rnd = RandomUtils.instance();
DiskImage disk = new DiskImage();
disk.setId(Guid.newGuid());
disk.setVmSnapshotId(Guid.newGuid());
disk.setSize(rnd.nextLong(1000));
disk.setActualSize(rnd.nextLong(1000));
disk.setVolumeFormat(rnd.nextEnum(VolumeFormat.class));
disk.setVolumeType(rnd.nextEnum(VolumeType.class));
disk.setWipeAfterDelete(rnd.nextBoolean());
disk.setDiskAlias(generateRandomName());
disk.setDescription(generateRandomName());
disk.setImageId(Guid.newGuid());
disk.setStoragePoolId(Guid.newGuid());
disk.setPlugged(true);
disk.setAppList(rnd.nextPropertyString(100));
Image image = new Image();
image.setActive(true);
image.setVolumeFormat(rnd.nextEnum(VolumeFormat.class));
image.setId(disk.getImageId());
image.setSnapshotId(disk.getSnapshotId());
image.setStatus(ImageStatus.OK);
disk.setImage(image);
DiskVmElement diskVmElement = new DiskVmElement(disk.getId(), vm.getId());
diskVmElement.setBoot(rnd.nextBoolean());
diskVmElement.setDiskInterface(rnd.nextEnum(DiskInterface.class));
diskVmElement.setReadOnly(false);
disk.setDiskVmElements(Collections.singletonList(diskVmElement));
return disk;
}
private static String generateRandomName() {
RandomUtils rnd = RandomUtils.instance();
return rnd.nextPropertyString(rnd.nextInt(MIN_ENTITY_NAME_LENGTH, MAX_ENTITY_NAME_LENGTH));
}
private static VmTemplate createVmTemplate() {
VmTemplate template = new VmTemplate();
template.setName("test-template");
template.setOrigin(OriginType.OVIRT);
template.setId(Guid.newGuid());
template.setDescription("test-description");
template.setDbGeneration(2L);
template.setClusterArch(ArchitectureType.x86_64);
template.setOsId(EXISTING_OS_ID);
return template;
}
}