blob: ef545d31e5930097bb42b79ffa3b25dda218ea07 [file]
// 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.vm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.cloudstack.context.CallContext;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.dc.DataCenter;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.offering.ServiceOffering;
import com.cloud.storage.GuestOSCategoryVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.dao.GuestOSCategoryDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account;
import com.cloud.uservm.UserVm;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackWithException;
import com.cloud.utils.db.UUIDManager;
import com.cloud.vm.dao.UserVmDao;
public class UserVmImportTemplateTest {
private final UserVmManagerImpl manager = new UserVmManagerImpl();
private final VMTemplateDao templateDao = mock(VMTemplateDao.class);
private final UserVmDao vmDao = mock(UserVmDao.class);
private final DataCenter zone = mock(DataCenter.class);
private final Account owner = mock(Account.class);
private final ServiceOffering offering = mock(ServiceOffering.class);
@Before
public void setUp() {
UUIDManager uuidManager = mock(UUIDManager.class);
GuestOSDao guestOSDao = mock(GuestOSDao.class);
GuestOSCategoryDao categoryDao = mock(GuestOSCategoryDao.class);
GuestOSVO guestOS = mock(GuestOSVO.class);
ReflectionTestUtils.setField(manager, "_templateDao", templateDao);
ReflectionTestUtils.setField(manager, "_vmDao", vmDao);
ReflectionTestUtils.setField(manager, "_uuidMgr", uuidManager);
ReflectionTestUtils.setField(manager, "_itMgr", mock(VirtualMachineManager.class));
ReflectionTestUtils.setField(manager, "_guestOSDao", guestOSDao);
ReflectionTestUtils.setField(manager, "_guestOSCategoryDao", categoryDao);
when(zone.getId()).thenReturn(1L);
when(owner.getId()).thenReturn(10L);
when(owner.getDomainId()).thenReturn(11L);
when(offering.getId()).thenReturn(12L);
when(vmDao.getNextInSequence(Long.class, "id")).thenReturn(42L);
when(uuidManager.generateUuid(UserVm.class, null)).thenReturn("b90b3f01-bbbd-4129-af59-d628c92d935f");
when(guestOSDao.findById(5L)).thenReturn(guestOS);
when(guestOS.getCategoryId()).thenReturn(6L);
when(categoryDao.findById(6L)).thenReturn(mock(GuestOSCategoryVO.class));
}
@Test
public void importsSoftDeletedDummyTemplateWithoutReloadingIt() throws InsufficientCapacityException {
VMTemplateVO template = createTemplate();
template.setRemoved(new Date());
template.setState(VirtualMachineTemplate.State.Inactive);
doAnswer(invocation -> {
template.setDetails(new HashMap<>());
return null;
}).when(templateDao).loadDetails(template);
UserVmVO vm = importTemplate(template);
assertNotNull(vm);
assertEquals(203L, vm.getTemplateId());
assertNotNull(template.getRemoved());
verify(templateDao).loadDetails(template);
verifyNoMoreInteractions(templateDao);
verify(vmDao).persist(vm);
}
@Test
public void copiesLoadedCpuModeFromActiveTemplateIntoImportedVm() throws InsufficientCapacityException {
VMTemplateVO template = createTemplate();
template.setState(VirtualMachineTemplate.State.Active);
doAnswer(invocation -> {
template.setDetails(Collections.singletonMap("guest.cpu.mode", "host-passthrough"));
return null;
}).when(templateDao).loadDetails(template);
UserVmVO vm = importTemplate(template);
assertEquals("host-passthrough", vm.getDetail("guest.cpu.mode"));
verify(templateDao).loadDetails(template);
verifyNoMoreInteractions(templateDao);
verify(vmDao).saveDetails(vm, Collections.emptyList());
}
@Test
public void importsNonVoTemplateUsingItsExistingDetails() throws InsufficientCapacityException {
VirtualMachineTemplate template = mock(VirtualMachineTemplate.class);
when(template.getId()).thenReturn(204L);
when(template.getFormat()).thenReturn(ImageFormat.QCOW2);
when(template.getDetails()).thenReturn(Collections.singletonMap("guest.cpu.mode", "host-model"));
UserVmVO vm = importTemplate(template);
assertEquals(204L, vm.getTemplateId());
assertEquals("host-model", vm.getDetail("guest.cpu.mode"));
verifyNoInteractions(templateDao);
}
@Test
public void explicitVmCpuDetailsOverrideTemplateDefaults() throws InsufficientCapacityException {
VMTemplateVO template = createTemplate();
doAnswer(invocation -> {
template.setDetails(Map.of(VmDetailConstants.GUEST_CPU_MODE, "custom",
VmDetailConstants.GUEST_CPU_MODEL, "Skylake-Client"));
return null;
}).when(templateDao).loadDetails(template);
Map<String, String> vmDetails = Map.of(VmDetailConstants.GUEST_CPU_MODE, "custom",
VmDetailConstants.GUEST_CPU_MODEL, "EPYC");
UserVmVO vm = importTemplate(template, vmDetails);
assertEquals("custom", vm.getDetail(VmDetailConstants.GUEST_CPU_MODE));
assertEquals("EPYC", vm.getDetail(VmDetailConstants.GUEST_CPU_MODEL));
assertEquals("Skylake-Client", template.getDetails().get(VmDetailConstants.GUEST_CPU_MODEL));
}
@Test
public void explicitVmCpuModeOverridesTemplateMode() throws InsufficientCapacityException {
VMTemplateVO template = createTemplate();
doAnswer(invocation -> {
template.setDetails(Collections.singletonMap(VmDetailConstants.GUEST_CPU_MODE, "host-passthrough"));
return null;
}).when(templateDao).loadDetails(template);
UserVmVO vm = importTemplate(template, Collections.singletonMap(VmDetailConstants.GUEST_CPU_MODE, "host-model"));
assertEquals("host-model", vm.getDetail(VmDetailConstants.GUEST_CPU_MODE));
}
@Test
public void stillRejectsMissingTemplate() {
InvalidParameterValueException exception = assertThrows(InvalidParameterValueException.class, () -> importTemplate(null));
assertEquals("Unable to import virtual machine without a template", exception.getMessage());
verifyNoInteractions(templateDao, vmDao);
}
private VMTemplateVO createTemplate() {
return VMTemplateVO.createSystemIso(203L, "import-test-template", "import-test-template", false,
"", true, 64, Account.ACCOUNT_ID_SYSTEM, "", "Import test template", false, 5L);
}
private UserVmVO importTemplate(VirtualMachineTemplate template) throws InsufficientCapacityException {
return importTemplate(template, new HashMap<>());
}
private UserVmVO importTemplate(VirtualMachineTemplate template, Map<String, String> vmDetails) throws InsufficientCapacityException {
try (MockedStatic<Transaction> transaction = mockStatic(Transaction.class);
MockedStatic<CallContext> context = mockStatic(CallContext.class)) {
transaction.when(() -> Transaction.execute(any(TransactionCallbackWithException.class))).thenAnswer(invocation -> {
TransactionCallbackWithException<UserVm, InsufficientCapacityException> callback = invocation.getArgument(0);
return callback.doInTransaction(null);
});
context.when(CallContext::current).thenReturn(mock(CallContext.class));
return (UserVmVO) manager.importVM(zone, null, template, "import-test-vm", "Import test VM", owner,
null, owner, true, null, 10L, 20L, offering, null, 5L, null, HypervisorType.KVM,
vmDetails, VirtualMachine.PowerState.PowerOff, new LinkedHashMap<>());
}
}
}