package org.ovirt.engine.core.vdsbroker.monitoring;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.transaction.TransactionManager;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.FullListVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.VmDeviceDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
import org.ovirt.engine.core.di.InjectorRule;
import org.ovirt.engine.core.vdsbroker.ResourceManager;
@RunWith(MockitoJUnitRunner.class)
public class VmDevicesMonitoringTest {
@ClassRule
public static InjectorRule injectorRule = new InjectorRule();
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private TransactionManager transactionManager;
@Mock
private VmDynamicDao vmDynamicDao;
@Mock
private VmDeviceDao vmDeviceDao;
@Mock
private ResourceManager resourceManager;
@InjectMocks
private VmDevicesMonitoring vmDevicesMonitoring;
private static final Guid VDS_ID = new Guid("b7dfe5e6-5667-4e40-8ecb-6d97c8df504d");
private static final Guid VM_ID = new Guid("7cfc3666-5185-4438-8381-646de77ca9a7");
private static final Guid VIDEO_DEVICE_ID = new Guid("5987c100-a653-4a6e-87ae-fe1f808225ed");
private static final Guid CDROM_DEVICE_ID = new Guid("dbf244e9-b91c-4304-a96e-f6868b362443");
private static final String VIDEO_DEVICE_ADDRESS = "address1";
private static final String CDROM_DEVICE_ADDRESS = "address2";
private static final String SERIAL_DEVICE_ADDRESS = "address3";
private static final String INITIAL_HASH = "123";
private static final String NEW_HASH = "012";
@Before
public void init() {
List<Pair<Guid, String>> initialHashes = new ArrayList<>();
initialHashes.add(new Pair<>(VM_ID, INITIAL_HASH));
doReturn(initialHashes).when(vmDynamicDao).getAllDevicesHashes();
injectorRule.bind(TransactionManager.class, transactionManager);
}
private static Map<String, Object> getDeviceInfo(Guid id, String deviceType, String device, String address) {
Map<String, Object> deviceInfo = new HashMap<>();
if (id != null) {
deviceInfo.put("deviceId", id.toString());
}
deviceInfo.put("deviceType", deviceType);
deviceInfo.put("device", device);
deviceInfo.put("type", deviceType);
deviceInfo.put("alias", device + "0");
deviceInfo.put("specParams", Collections.emptyMap());
if (address != null) {
deviceInfo.put("address", address);
}
return deviceInfo;
}
private static VmDevice getVmDevice(Guid deviceId,
Guid vmId,
VmDeviceGeneralType type,
String device,
boolean isManaged) {
return new VmDevice(new VmDeviceId(deviceId, vmId),
type,
device,
"",
Collections.emptyMap(),
isManaged,
true,
false,
"",
Collections.emptyMap(),
null,
null);
}
private void initDevices(VmDevice... devices) {
doReturn(Arrays.asList(devices)).when(vmDeviceDao).getVmDeviceByVmId(VM_ID);
for (VmDevice device : devices) {
doReturn(Collections.singletonList(device)).when(vmDeviceDao)
.getVmDevicesByDeviceId(device.getDeviceId(), device.getVmId());
}
}
private Map<String, Object> getFullList(Guid vmId, Map<String, Object>... deviceInfos) {
Map<String, Object> vmInfo = new HashMap<>();
vmInfo.put("vmId", vmId.toString());
vmInfo.put("devices", deviceInfos);
return vmInfo;
}
private void initFullList(Map<String, Object>... deviceInfos) {
VDSReturnValue returnValue = new VDSReturnValue();
returnValue.setReturnValue(new Map[] { getFullList(VM_ID, deviceInfos) });
returnValue.setSucceeded(true);
doReturn(returnValue).when(resourceManager).runVdsCommand(eq(VDSCommandType.FullList),
any(FullListVDSCommandParameters.class));
}
@Test
public void testIgnoreOutdatedHash() {
initDevices();
initFullList();
vmDevicesMonitoring.initDevicesStatuses(3L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(VDS_ID, 2L);
change.updateVm(VM_ID, NEW_HASH);
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, never()).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testHashNotChanged() {
initDevices();
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(VDS_ID, 2L);
change.updateVm(VM_ID, INITIAL_HASH);
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, never()).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testUpdateVm() {
final Guid usbControllerId = Guid.newGuid();
initDevices(
getVmDevice(usbControllerId, VM_ID, VmDeviceGeneralType.CONTROLLER, "usb", false),
getVmDevice(VIDEO_DEVICE_ID, VM_ID, VmDeviceGeneralType.VIDEO, "cirrus", true),
getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true)
);
initFullList(
getDeviceInfo(null, "balloon", "memballoon", null),
getDeviceInfo(null, "controller", "virtio-serial", SERIAL_DEVICE_ADDRESS),
getDeviceInfo(VIDEO_DEVICE_ID, "video", "cirrus", VIDEO_DEVICE_ADDRESS),
getDeviceInfo(CDROM_DEVICE_ID, "disk", "cdrom", CDROM_DEVICE_ADDRESS)
);
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(VDS_ID, 2L);
change.updateVm(VM_ID, NEW_HASH);
change.flush();
ArgumentCaptor<Collection> updateCaptor = ArgumentCaptor.forClass(Collection.class);
verify(vmDeviceDao, times(1)).updateAllInBatch(updateCaptor.capture());
assertEquals(2, updateCaptor.getValue().size());
ArgumentCaptor<List> removeCaptor = ArgumentCaptor.forClass(List.class);
verify(vmDeviceDao, times(1)).removeAll(removeCaptor.capture());
List removedDeviceIds = removeCaptor.getValue();
assertEquals(1, removedDeviceIds.size());
VmDeviceId deviceId = (VmDeviceId) removedDeviceIds.get(0);
assertEquals(deviceId.getDeviceId(), usbControllerId);
assertEquals(VM_ID, deviceId.getVmId());
ArgumentCaptor<List> saveCaptor = ArgumentCaptor.forClass(List.class);
verify(vmDeviceDao, times(1)).saveAll(saveCaptor.capture());
List savedDevices = saveCaptor.getValue();
assertEquals(1, savedDevices.size());
VmDevice device = (VmDevice) savedDevices.get(0);
assertEquals(SERIAL_DEVICE_ADDRESS, device.getAddress());
ArgumentCaptor<List> updateHashesCaptor = ArgumentCaptor.forClass(List.class);
verify(vmDynamicDao, times(1)).updateDevicesHashes(updateHashesCaptor.capture());
List updatedHashes = updateHashesCaptor.getValue();
assertEquals(1, updatedHashes.size());
Pair<Guid, String> hashInfo = (Pair<Guid, String>) updatedHashes.get(0);
assertEquals(VM_ID, hashInfo.getFirst());
assertEquals(NEW_HASH, hashInfo.getSecond());
}
@Test
public void testUpdateVmFromFullList() {
initDevices();
initFullList();
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(VDS_ID, 1L);
Map<String, Object> vmInfo = getFullList(VM_ID,
getDeviceInfo(null, "balloon", "memballoon", null),
getDeviceInfo(null, "controller", "virtio-serial", SERIAL_DEVICE_ADDRESS),
getDeviceInfo(VIDEO_DEVICE_ID, "video", "cirrus", VIDEO_DEVICE_ADDRESS),
getDeviceInfo(CDROM_DEVICE_ID, "disk", "cdrom", CDROM_DEVICE_ADDRESS)
);
change.updateVmFromFullList(vmInfo);
change.flush();
change = vmDevicesMonitoring.createChange(VDS_ID, 1L);
change.updateVm(VM_ID, INITIAL_HASH);
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, times(1)).saveAll(any());
ArgumentCaptor<List> updateHashesCaptor = ArgumentCaptor.forClass(List.class);
verify(vmDynamicDao, times(2)).updateDevicesHashes(updateHashesCaptor.capture());
List<List> values = updateHashesCaptor.getAllValues();
List updatedHashes = values.get(0);
assertEquals(1, updatedHashes.size());
Pair<Guid, String> hashInfo = (Pair<Guid, String>) updatedHashes.get(0);
assertEquals(VM_ID, hashInfo.getFirst());
assertEquals(VmDevicesMonitoring.UPDATE_HASH, hashInfo.getSecond());
updatedHashes = values.get(1);
assertEquals(1, updatedHashes.size());
hashInfo = (Pair<Guid, String>) updatedHashes.get(0);
assertEquals(VM_ID, hashInfo.getFirst());
assertEquals(INITIAL_HASH, hashInfo.getSecond());
}
@Test
public void testAddDevice() {
initDevices();
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(2L);
change.updateDevice(getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true));
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, times(1)).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testUpdateDevice() {
VmDevice device = getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true);
initDevices(device);
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(2L);
change.updateDevice(device);
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, times(1)).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, never()).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testRemoveDevice() {
initDevices(
getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true)
);
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(2L);
change.removeDevice(new VmDeviceId(CDROM_DEVICE_ID, VM_ID));
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, times(1)).removeAll(any());
verify(vmDeviceDao, never()).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testAddAndUpdateDevice() {
initDevices();
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(2L);
VmDevice device = getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true);
change.updateDevice(device);
change.flush();
initDevices(device);
change = vmDevicesMonitoring.createChange(3L);
change.updateDevice(device);
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, times(1)).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, times(1)).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testIgnoreOutdatedUpdateDevice() {
initDevices();
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(3L);
VmDevice device = getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true);
change.updateDevice(device);
change.flush();
initDevices(device);
change = vmDevicesMonitoring.createChange(2L);
change.updateDevice(device);
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, times(1)).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testIgnoreDeviceChangeBeforeFullList() {
initDevices(
getVmDevice(CDROM_DEVICE_ID, VM_ID, VmDeviceGeneralType.DISK, "cdrom", true)
);
initFullList();
vmDevicesMonitoring.initDevicesStatuses(2L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(1L);
change.removeDevice(new VmDeviceId(CDROM_DEVICE_ID, VM_ID));
change.flush();
verify(resourceManager, never()).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, never()).removeAll(any());
verify(vmDeviceDao, never()).saveAll(any());
verify(vmDynamicDao, never()).updateDevicesHashes(any());
}
@Test
public void testFullListErasesIndividualUpdateDevice() {
initDevices();
initFullList();
vmDevicesMonitoring.initDevicesStatuses(1L);
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(4L);
Guid controllerId = Guid.newGuid();
VmDevice controller = getVmDevice(controllerId, VM_ID, VmDeviceGeneralType.CONTROLLER, "usb", false);
change.updateDevice(controller);
change.flush();
initDevices(controller);
change = vmDevicesMonitoring.createChange(VDS_ID, 2L);
change.updateVm(VM_ID, NEW_HASH);
change.flush();
initDevices();
doReturn(Collections.emptyList()).when(vmDeviceDao).getVmDevicesByDeviceId(controllerId, VM_ID);
change = vmDevicesMonitoring.createChange(3L);
change.updateDevice(controller);
change.flush();
verify(resourceManager, times(1)).runVdsCommand(eq(VDSCommandType.FullList), any());
verify(vmDeviceDao, never()).updateAllInBatch(any());
verify(vmDeviceDao, times(1)).removeAll(any());
verify(vmDeviceDao, times(2)).saveAll(any());
verify(vmDynamicDao, times(1)).updateDevicesHashes(any());
}
}