/* * Copyright 2000-2020 JetBrains s.r.o. * * Licensed 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 jetbrains.buildServer.clouds.vmware; import com.intellij.openapi.util.Pair; import com.intellij.util.WaitFor; import com.vmware.vim25.*; import com.vmware.vim25.mo.Datacenter; import com.vmware.vim25.mo.ManagedEntity; import com.vmware.vim25.mo.Task; import java.io.File; import java.net.MalformedURLException; import java.rmi.RemoteException; import java.security.KeyStore; import java.security.KeyStoreException; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import com.vmware.vim25.mo.VirtualMachine; import jetbrains.buildServer.BaseTestCase; import jetbrains.buildServer.clouds.*; import jetbrains.buildServer.clouds.base.connector.AbstractInstance; import jetbrains.buildServer.clouds.base.errors.CheckedCloudException; import jetbrains.buildServer.clouds.server.*; import jetbrains.buildServer.clouds.server.impl.CloudRegistryImpl; import jetbrains.buildServer.clouds.server.impl.profile.CloudClientParametersImpl; import jetbrains.buildServer.clouds.vmware.connector.VMWareApiConnector; import jetbrains.buildServer.clouds.vmware.connector.VmwareInstance; import jetbrains.buildServer.clouds.vmware.errors.VmwareCheckedCloudException; import jetbrains.buildServer.clouds.vmware.stubs.FakeApiConnector; import jetbrains.buildServer.clouds.vmware.stubs.FakeModel; import jetbrains.buildServer.clouds.vmware.stubs.FakeVirtualMachine; import jetbrains.buildServer.clouds.vmware.tasks.VmwarePooledUpdateInstanceTask; import jetbrains.buildServer.clouds.vmware.tasks.VmwareUpdateTaskManager; import jetbrains.buildServer.clouds.vmware.web.VMWareWebConstants; import jetbrains.buildServer.serverSide.ServerPaths; import jetbrains.buildServer.serverSide.ServerResponsibility; import jetbrains.buildServer.serverSide.TeamCityProperties; import jetbrains.buildServer.serverSide.impl.ServerSettingsImpl; import jetbrains.buildServer.util.TestFor; import jetbrains.buildServer.util.ThreadUtil; import jetbrains.buildServer.web.openapi.PluginDescriptor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jmock.Expectations; import org.jmock.Mockery; import org.testng.SkipException; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static jetbrains.buildServer.clouds.vmware.connector.VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_ID; /** * @author Sergey.Pak * Date: 5/13/2014 * Time: 1:04 PM */ @Test public class VmwareCloudIntegrationTest extends BaseTestCase { protected static final String PROFILE_ID = "cp1"; protected static final String PROJECT_ID = "project123"; protected static final String TEST_SERVER_UUID = "1234-5678-9012"; private VMWareCloudClient myClient; private FakeApiConnector myFakeApi; private CloudClientParameters myClientParameters; private CloudProfile myProfile; private VmwareUpdateTaskManager myTaskManager; private File myIdxStorage; private AtomicLong myStuckTime; private AtomicLong myLastRunTime; @BeforeMethod public void setUp() throws Exception { super.setUp(); //org.apache.log4j.Logger.getLogger("jetbrains.buildServer").setLevel(Level.DEBUG); //org.apache.log4j.Logger.getRootLogger().addAppender(new ConsoleAppender()); myIdxStorage = createTempDir(); setInternalProperty("teamcity.vsphere.instance.status.update.delay.ms", "50"); setInternalProperty("teamcity.vmware.instance.status.check.delay", "50"); final String json = "[{sourceVmName:'image1', behaviour:'START_STOP'}," + "{sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp',maxInstances:3,behaviour:'ON_DEMAND_CLONE', " + "customizationSpec:'someCustomization'}," + "{sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "',folder:'cf',pool:'rp',maxInstances:3,behaviour:'FRESH_CLONE', customizationSpec: 'linux'}]"; myClientParameters = VmwareTestUtils.getClientParameters(PROJECT_ID, json); myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID, null); FakeModel.instance().addDatacenter("dc"); FakeModel.instance().addFolder("cf").setParent("dc", Datacenter.class); FakeModel.instance().addResourcePool("rp").setParentFolder("cf"); FakeModel.instance().addVM("image1").setParentFolder("cf"); FakeModel.instance().addVM("image2").setParentFolder("cf"); FakeModel.instance().addVM("image_template").setParentFolder("cf"); FakeModel.instance().addVMSnapshot("image2", "snap"); FakeModel.instance().getCustomizationSpecs().put("someCustomization", new CustomizationSpec()); FakeModel.instance().getCustomizationSpecs().put("linux", new CustomizationSpec()); myLastRunTime = new AtomicLong(0); myStuckTime = new AtomicLong(2*60*1000); myTaskManager = new VmwareUpdateTaskManager(){ @Override protected VmwarePooledUpdateInstanceTask createNewPooledTask(@NotNull final VMWareApiConnector connector, @NotNull final VMWareCloudClient client) { return new VmwarePooledUpdateInstanceTask(connector, client, myStuckTime.get(), false){ @Override public void run() { super.run(); myLastRunTime.set(System.currentTimeMillis()); } }; } }; recreateClient(); assertNull(myClient.getErrorInfo()); } public void validate_objects_on_client_creation() throws MalformedURLException, RemoteException { throw new SkipException("TODO: Add validation"); /* FakeModel.instance().removeFolder("cf"); recreateClient(); assertNotNull(myClient.getErrorInfo()); assertEquals(wrapWithArraySymbols(VMWareCloudErrorInfoFactory.noSuchFolder("cf").getMessage()), myClient.getErrorInfo().getMessage()); FakeModel.instance().addFolder("cf"); FakeModel.instance().removeResourcePool("rp"); recreateClient(); assertNotNull(myClient.getErrorInfo()); assertEquals(wrapWithArraySymbols(VMWareCloudErrorInfoFactory.noSuchResourcePool("rp").getMessage()), myClient.getErrorInfo().getMessage()); FakeModel.instance().addResourcePool("rp"); FakeModel.instance().removeVM("image1"); recreateClient(); assertNotNull(myClient.getErrorInfo()); assertEquals(wrapWithArraySymbols(VMWareCloudErrorInfoFactory.noSuchVM("image1").getMessage()), myClient.getErrorInfo().getMessage()); FakeModel.instance().addVM("image1"); FakeModel.instance().removeVM("image2"); recreateClient(); assertNotNull(myClient.getErrorInfo()); assertEquals(wrapWithArraySymbols(VMWareCloudErrorInfoFactory.noSuchVM("image2").getMessage()), myClient.getErrorInfo().getMessage()); FakeModel.instance().addVM("image2");*/ } public void check_start_type() throws MalformedURLException { myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID) { @Override public List<VmwareInstance> getVirtualMachines(boolean filterClones) throws VmwareCheckedCloudException { final List<VmwareInstance> instances = super.getVirtualMachines(filterClones); instances.add(new VmwareInstance(new FakeVirtualMachine("image_template", true, false), "datacenter-10")); instances.add(new VmwareInstance(new FakeVirtualMachine("image1", false, false), "datacenter-10")); return instances; } }; } public void check_startup_parameters() throws CheckedCloudException { startNewInstanceAndWait("image1", Collections.singletonMap("customParam1", "customValue1")); final VmwareInstance vm = myFakeApi.getAllVMsMap(true).get("image1"); final String userDataEncoded = vm.getProperty(VMWarePropertiesNames.USER_DATA); assertNotNull(userDataEncoded); final CloudInstanceUserData cloudInstanceUserData = CloudInstanceUserData.deserialize(userDataEncoded); assertEquals("customValue1", cloudInstanceUserData.getCustomAgentConfigurationParameters().get("customParam1")); } public void check_vm_clone() throws Exception { startAndCheckCloneDeletedAfterTermination("image1", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertEquals("image1", data.getInstanceId()); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertNull(vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_CLONED_INSTANCE)); assertNull(vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_NAME)); } }, false); assertEquals(3, FakeModel.instance().getVms().size()); startAndCheckCloneDeletedAfterTermination("image_template", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertTrue("image_template-1".equals(data.getInstanceId())); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("true", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_CLONED_INSTANCE)); assertEquals("image_template", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_NAME)); assertEquals(VmwareConstants.CURRENT_STATE, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); } }, true); assertEquals(3, FakeModel.instance().getVms().size()); startAndCheckCloneDeletedAfterTermination("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertTrue("image2-1".equals(data.getInstanceId())); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("true", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_CLONED_INSTANCE)); assertEquals("image2", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_NAME)); assertEquals("snap", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); } }, false); assertEquals(4, FakeModel.instance().getVms().size()); } public void on_demand_clone_should_use_existing_vm_when_one_exists() throws Exception { final AtomicReference<String> instanceId = new AtomicReference<String>(); startAndCheckCloneDeletedAfterTermination("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { instanceId.set(data.getInstanceId()); assertTrue(data.getInstanceId().startsWith("image2")); assertTrue("image2-1".equals(data.getInstanceId())); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("true", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_CLONED_INSTANCE)); assertEquals("image2", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_NAME)); assertEquals("snap", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); } }, false); assertEquals(4, FakeModel.instance().getVms().size()); assertContains(FakeModel.instance().getVms().keySet(), instanceId.get()); startAndCheckCloneDeletedAfterTermination("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertEquals(instanceId.get(), data.getInstanceId()); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("true", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_CLONED_INSTANCE)); assertEquals("image2", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_NAME)); assertEquals("snap", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); } }, false); } public void on_demand_clone_should_create_more_when_not_enough() throws Exception { final AtomicReference<String> instanceId = new AtomicReference<String>(); startAndCheckCloneDeletedAfterTermination("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) { instanceId.set(data.getInstanceId()); } }, false); assertEquals(4, FakeModel.instance().getVms().size()); final VmwareCloudInstance instance1 = startAndCheckInstance("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws RemoteException { assertEquals(instanceId.get(), data.getInstanceId()); } }); assertEquals(4, FakeModel.instance().getVms().size()); final VmwareCloudInstance instance2 = startAndCheckInstance("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws RemoteException { assertNotSame(instanceId.get(), data.getInstanceId()); } }); assertEquals(5, FakeModel.instance().getVms().size()); final Map<String, FakeVirtualMachine> vms = FakeModel.instance().getVms(); assertEquals(VirtualMachinePowerState.poweredOn, vms.get(instance1.getName()).getRuntime().getPowerState()); assertEquals(VirtualMachinePowerState.poweredOn, vms.get(instance2.getName()).getRuntime().getPowerState()); } public void on_demand_clone_should_create_new_when_latest_snapshot_changes() throws Exception { final AtomicReference<String> instanceId = new AtomicReference<String>(); startAndCheckCloneDeletedAfterTermination("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("snap", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); instanceId.set(data.getInstanceId()); } }, false); assertEquals(4, FakeModel.instance().getVms().size()); FakeModel.instance().addVMSnapshot("image2", "snap2"); final VmwareCloudInstance instance1 = startAndCheckInstance("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertNotSame(instanceId.get(), data.getInstanceId()); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("snap2", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); } }); assertEquals(4, FakeModel.instance().getVms().size()); assertNotContains(FakeModel.instance().getVms().keySet(), instanceId.get()); final VmwareCloudInstance instance2 = startAndCheckInstance("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertNotSame(instanceId.get(), data.getInstanceId()); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("snap2", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); } }); assertEquals(5, FakeModel.instance().getVms().size()); final Map<String, FakeVirtualMachine> vms = FakeModel.instance().getVms(); assertEquals(VirtualMachinePowerState.poweredOn, vms.get(instance1.getName()).getRuntime().getPowerState()); assertEquals(VirtualMachinePowerState.poweredOn, vms.get(instance2.getName()).getRuntime().getPowerState()); } public void on_demand_clone_should_create_new_when_version_changes() throws Exception { updateClientParameters(VmwareTestUtils.getImageParameters(PROJECT_ID,"[{sourceVmName:'image1', snapshot:'" + VmwareConstants.CURRENT_STATE + "',folder:'cf',pool:'rp',maxInstances:3, behaviour:'ON_DEMAND_CLONE'}," + "{sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp',maxInstances:3,behaviour:'ON_DEMAND_CLONE'}," + "{sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "', folder:'cf',pool:'rp',maxInstances:3,behaviour:'FRESH_CLONE'}]")); recreateClient(); final AtomicReference<String> instanceId = new AtomicReference<String>(); final String originalChangeVersion = FakeModel.instance().getVirtualMachine("image1").getConfig().getChangeVersion(); startAndCheckCloneDeletedAfterTermination("image1", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals(VmwareConstants.CURRENT_STATE, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); assertEquals(originalChangeVersion, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_CHANGE_VERSION)); instanceId.set(data.getInstanceId()); } }, false); assertEquals(4, FakeModel.instance().getVms().size()); // start and stop Instance final Task powerOnTask = FakeModel.instance().getVirtualMachine("image1").powerOnVM_Task(null); assertEquals("success", powerOnTask.waitForTask()); Thread.sleep(2000); // to ensure that version will change FakeModel.instance().getVirtualMachine("image1").shutdownGuest(); new WaitFor(1000){ @Override protected boolean condition() { return FakeModel.instance().getVirtualMachine("image1").getRuntime().getPowerState() == VirtualMachinePowerState.poweredOff; } }.assertCompleted("Must shutdown in time"); final String updatedChangeVersion = FakeModel.instance().getVirtualMachine("image1").getConfig().getChangeVersion(); assertNotSame(originalChangeVersion, updatedChangeVersion); final VmwareCloudInstance instance1 = startAndCheckInstance("image1", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertNotSame(instanceId.get(), data.getInstanceId()); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals(updatedChangeVersion, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_CHANGE_VERSION)); } }); assertEquals(4, FakeModel.instance().getVms().size()); assertNotContains(FakeModel.instance().getVms().keySet(), instanceId.get()); final VmwareCloudInstance instance2 = startAndCheckInstance("image1", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertNotSame(instanceId.get(), data.getInstanceId()); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals(updatedChangeVersion, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_CHANGE_VERSION)); } }); assertEquals(5, FakeModel.instance().getVms().size()); final Map<String, FakeVirtualMachine> vms = FakeModel.instance().getVms(); assertEquals(VirtualMachinePowerState.poweredOn, vms.get(instance1.getName()).getRuntime().getPowerState()); assertEquals(VirtualMachinePowerState.poweredOn, vms.get(instance2.getName()).getRuntime().getPowerState()); } private void updateClientParameters(final Collection<CloudImageParameters> cloudImageParameters) { myClientParameters = updateClientParameters(myClientParameters, cloudImageParameters); } private CloudClientParameters updateClientParameters(CloudClientParameters clientParameters, final Collection<CloudImageParameters> cloudImageParameters) { return new CloudClientParametersImpl( clientParameters.getParameters(), cloudImageParameters); } private void updateClientParameters(Map<String, String> params) { final Map<String, String> parameters = new HashMap<>( myClientParameters.getParameters() ); parameters.putAll(params); myClientParameters = new CloudClientParametersImpl(parameters, myClientParameters.getCloudImages()); } public void catch_tc_started_instances_on_startup() throws MalformedURLException, RemoteException { final FakeVirtualMachine image2 = FakeModel.instance().getVirtualMachine("image2"); final FakeVirtualMachine image_template = FakeModel.instance().getVirtualMachine("image_template"); startNewInstanceAndWait("image1"); startNewInstanceAndWait("image2"); startNewInstanceAndWait("image_template"); assertEquals(5, FakeModel.instance().getVms().size()); recreateClient(); assertNull(myClient.getErrorInfo()); new WaitFor(5*1000){ protected boolean condition() { int cnt = 0; for (VmwareCloudImage image : myClient.getImages()) { final Collection<VmwareCloudInstance> instances = image.getInstances(); cnt += instances.size(); for (VmwareCloudInstance instance : instances) { assertEquals(InstanceStatus.RUNNING, instance.getStatus()); } } return cnt == 3; } }; for (VmwareCloudImage image : myClient.getImages()) { final Collection<VmwareCloudInstance> instances = image.getInstances(); assertEquals(1, instances.size()); final VmwareCloudInstance instance = instances.iterator().next(); if ("image1".equals(image.getName())){ assertEquals("image1", instance.getName()); } else if ("image2".equals(image.getName())) { assertTrue(instance.getName().startsWith(image.getName())); assertEquals("snap", instance.getSourceState().getSnapshotName()); assertEquals(image2.getMOR().getVal(), instance.getSourceState().getSourceVmId()); } else if ("image_template".equals(image.getName())) { assertTrue(instance.getName().startsWith(image.getName())); assertEquals(image_template.getMOR().getVal(), instance.getSourceState().getSourceVmId()); } } } public void sync_start_stop_instance_status() throws RemoteException { final VmwareCloudImage img1 = getImageByName("image1"); assertEquals(1, img1.getInstances().size()); final VmwareCloudInstance inst1 = img1.getInstances().iterator().next(); assertEquals(InstanceStatus.STOPPED, inst1.getStatus()); startNewInstanceAndWait("image1"); assertEquals(InstanceStatus.RUNNING, inst1.getStatus()); FakeModel.instance().getVirtualMachine("image1").shutdownGuest(); new WaitFor(3000){ protected boolean condition() { return img1.getInstances().iterator().next().getStatus() == InstanceStatus.STOPPED; } }.assertCompleted("Should have caught the stopped status"); FakeModel.instance().getVirtualMachine("image1").powerOnVM_Task(null); new WaitFor(3000){ protected boolean condition() { return img1.getInstances().iterator().next().getStatus() == InstanceStatus.RUNNING; } }.assertCompleted("Should have caught the running status"); } public void sync_clone_status() throws RemoteException { final VmwareCloudImage img1 = getImageByName("image_template"); assertEquals(0, img1.getInstances().size()); final VmwareCloudInstance inst = startNewInstanceAndWait("image_template"); assertEquals(InstanceStatus.RUNNING, inst.getStatus()); FakeModel.instance().getVirtualMachine(inst.getName()).shutdownGuest(); new WaitFor(3000){ protected boolean condition() { return img1.getInstances().iterator().next().getStatus() == InstanceStatus.STOPPED; } }.assertCompleted("Should have caught the stopped status"); FakeModel.instance().getVirtualMachine(inst.getName()).powerOnVM_Task(null); new WaitFor(3000){ protected boolean condition() { return img1.getInstances().iterator().next().getStatus() == InstanceStatus.RUNNING; } }.assertCompleted("Should have caught the running status"); } public void should_limit_new_instances_count(){ int countStarted = 0; final VmwareCloudImage image_template = getImageByName("image_template"); while (myClient.canStartNewInstanceWithDetails(image_template).isPositive()){ final CloudInstanceUserData userData = createUserData(image_template + "_agent"); myClient.startNewInstance(image_template, userData); countStarted++; assertTrue(countStarted <= 3); } } public void shouldnt_start_when_snapshot_is_missing(){ final VmwareCloudImage image2 = getImageByName("image2"); FakeModel.instance().removeVmSnaphot(image2.getName(), "snap"); startNewInstanceAndCheck("image2", new HashMap<>(), false); } public void should_power_off_if_no_guest_tools_avail() throws InterruptedException { final VmwareCloudImage image_template = getImageByName("image_template"); final VmwareCloudInstance instance = startNewInstanceAndWait("image_template"); assertContains(image_template.getInstances(), instance); FakeModel.instance().getVirtualMachine(instance.getName()).disableGuestTools(); myClient.terminateInstance(instance); new WaitFor(2000) { @Override protected boolean condition() { return instance.getStatus() == InstanceStatus.STOPPED && !image_template.getInstances().contains(instance); } }; assertNull(FakeModel.instance().getVirtualMachine(instance.getName())); assertNotContains(image_template.getInstances(), instance); } public void existing_clones_with_start_stop() { final VmwareCloudInstance cloneInstance = startNewInstanceAndWait("image2"); updateClientParameters(VmwareTestUtils.getImageParameters(PROJECT_ID, "[{sourceVmName:'image1', behaviour:'START_STOP'}," + "{sourceVmName:'image2', behaviour:'START_STOP'}," + "{sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "', folder:'cf',pool:'rp',maxInstances:3,behaviour:'FRESH_CLONE'}]")); recreateClient(); boolean checked = false; for (VmwareCloudImage image : myClient.getImages()) { if (!"image2".equals(image.getName())) continue; final Collection<VmwareCloudInstance> instances = image.getInstances(); final VmwareCloudInstance singleInstance = instances.iterator().next(); assertEquals(1, instances.size()); assertEquals("image2", singleInstance.getName()); checked = true; } assertTrue(checked); } public void dont_exceed_max_instances_limit_fresh_clones() throws RemoteException { final VmwareCloudInstance[] instances = new VmwareCloudInstance[]{startNewInstanceAndWait("image_template"), startNewInstanceAndWait("image_template")}; // shutdown all instances for (VmwareCloudInstance instance : instances) { FakeModel.instance().getVirtualMachine(instance.getName()).powerOffVM_Task(); } recreateClient(); startNewInstanceAndWait("image_template"); // instance should not start startNewInstanceAndCheck("image_template", new HashMap<>(), false); } @Test(expectedExceptions = QuotaException.class, expectedExceptionsMessageRegExp = "Unable to start more instances of image image2") public void check_max_instances_count_on_profile_start() { startNewInstanceAndWait("image2"); startNewInstanceAndWait("image2"); startNewInstanceAndWait("image2"); System.setProperty("teamcity.vsphere.instance.status.update.delay.ms", "25000"); recreateClient(); startNewInstanceAndWait("image2"); } public void do_not_clear_image_instances_list_on_error() throws ExecutionException, InterruptedException, MalformedURLException { final AtomicBoolean failure = new AtomicBoolean(false); final AtomicLong lastApiCallTime = new AtomicLong(0); myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID){ @Override protected <T extends ManagedEntity> Collection<T> findAllEntitiesOld(final Class<T> instanceType) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.findAllEntitiesOld(instanceType); } @Override protected <T extends ManagedEntity> Map<String, T> findAllEntitiesAsMapOld(final Class<T> instanceType) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.findAllEntitiesAsMapOld(instanceType); } @Override protected <T extends ManagedEntity> Pair<T,Datacenter> findEntityByIdNameOld(final String name, final Class<T> instanceType) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.findEntityByIdNameOld(name, instanceType); } }; recreateClient(250); startNewInstanceAndWait("image2"); startNewInstanceAndWait("image2"); startNewInstanceAndWait("image2"); Thread.sleep(5*1000); failure.set(true); final long problemStart = System.currentTimeMillis(); new WaitFor(5*1000){ @Override protected boolean condition() { return myLastRunTime.get() > problemStart; } }.assertCompleted("Should have been checked at least once - delay set to 2 sec"); assertEquals(3, getImageByName("image2").getInstances().size()); } public void canstart_check_shouldnt_block_thread() throws InterruptedException, MalformedURLException { final Lock lock = new ReentrantLock(); final AtomicBoolean shouldLock = new AtomicBoolean(false); myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID){ @Override public void test() throws VmwareCheckedCloudException { if (shouldLock.get()){ lock.lock(); // will stuck here } super.test(); } }; recreateClient(); shouldLock.set(true); lock.lock(); final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.execute(new Runnable() { public void run() { getImageByName("image1").canStartNewInstance(); getImageByName("image2").canStartNewInstance(); getImageByName("image_template").canStartNewInstance(); } }); executor.shutdown(); assertTrue("canStart method blocks the thread!", executor.awaitTermination(100, TimeUnit.MILLISECONDS)); } public void check_same_datacenter() throws InterruptedException { FakeModel.instance().addDatacenter("dc2"); FakeModel.instance().addFolder("cf2").setParent("dc2", Datacenter.class); FakeModel.instance().addResourcePool("rp2").setParentFolder("cf2"); FakeModel.instance().addVM("image3").setParentFolder("cf"); updateClientParameters(VmwareTestUtils.getImageParameters(PROJECT_ID, "[{sourceVmName:'image1', behaviour:'START_STOP'}," + "{sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp',maxInstances:3,behaviour:'ON_DEMAND_CLONE'}," + "{sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "',folder:'cf',pool:'rp',maxInstances:3,behaviour:'FRESH_CLONE'}, " + "{sourceVmName:'image3',snapshot:'" + VmwareConstants.CURRENT_STATE + "'," + "folder:'cf2',pool:'rp2',maxInstances:3,behaviour:'ON_DEMAND_CLONE'}]")); recreateClient(); final CloudInstanceUserData userData = createUserData("image3_agent"); final VmwareCloudInstance vmwareCloudInstance = myClient.startNewInstance(getImageByName("image3"), userData); new WaitFor(10 * 1000) { @Override protected boolean condition() { return vmwareCloudInstance.getStatus() == InstanceStatus.ERROR && vmwareCloudInstance.getErrorInfo() != null; } }.assertCompleted(); final String msg = vmwareCloudInstance.getErrorInfo().getMessage(); assertContains(msg, "Unable to find folder cf2 in datacenter dc"); } public void check_nickname(){ updateClientParameters(VmwareTestUtils.getImageParameters(PROJECT_ID, "[{sourceVmName:'image1', behaviour:'START_STOP'}," + "{nickname:'image2Nick1', sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp',maxInstances:3,behaviour:'ON_DEMAND_CLONE'}," + "{nickname:'image2Nick2', sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp',maxInstances:3,behaviour:'ON_DEMAND_CLONE'}," + "{sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "',folder:'cf',pool:'rp',maxInstances:3,behaviour:'FRESH_CLONE'}]")); recreateClient(); startNewInstanceAndWait("image2Nick1"); boolean checked1 = false; boolean checked2 = false; String startedInstanceName = null; for (VmwareCloudImage image : myClient.getImages()) { if ("image2Nick1".equals(image.getName())){ final Collection<VmwareCloudInstance> instances = image.getInstances(); final VmwareCloudInstance singleInstance = instances.iterator().next(); startedInstanceName = singleInstance.getName(); assertEquals(1, instances.size()); assertTrue(singleInstance.getName().startsWith("image2Nick1")); checked1 = true; } else if ("image2Nick2".equals(image.getName())){ assertEquals(0, image.getInstances().size()); checked2 = true; } } startNewInstanceAndWait("image2Nick2"); startNewInstanceAndWait("image2Nick2"); for (VmwareCloudImage image : myClient.getImages()) { if ("image2Nick1".equals(image.getName())){ final Collection<VmwareCloudInstance> instances = image.getInstances(); final VmwareCloudInstance singleInstance = instances.iterator().next(); assertEquals(startedInstanceName, singleInstance.getName()); assertTrue(singleInstance.getName().startsWith("image2Nick1")); assertEquals(1, instances.size()); checked1 = true; } else if ("image2Nick2".equals(image.getName())){ assertEquals(2, image.getInstances().size()); for (VmwareCloudInstance instance : image.getInstances()) { assertTrue(instance.getName().startsWith("image2Nick2")); } checked2 = true; } } assertTrue(checked1 && checked2); } public void profile_creation_should_not_block_ui() throws ExecutionException, InterruptedException, MalformedURLException { final int extraProfileCount = 5; final List<CloudClientParameters> profileParams = new ArrayList<CloudClientParameters>(); for (int i=0; i< extraProfileCount; i++){ profileParams.add(new CloudClientParametersImpl( createMap(VMWareWebConstants.SERVER_URL, "http://localhost:8080", VMWareWebConstants.USERNAME, "un", VMWareWebConstants.PASSWORD, "pw"), VmwareTestUtils.getImageParameters(PROJECT_ID, "[{sourceVmName:'image_new" + i + "', behaviour:'START_STOP'}]") )); } myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID){ @Override protected <T extends ManagedEntity> Collection<T> findAllEntitiesOld(final Class<T> instanceType) throws VmwareCheckedCloudException { try {Thread.sleep(1000);} catch (InterruptedException e) {} return super.findAllEntitiesOld(instanceType); } @Override protected <T extends ManagedEntity> Map<String, T> findAllEntitiesAsMapOld(final Class<T> instanceType) throws VmwareCheckedCloudException { try {Thread.sleep(1000);} catch (InterruptedException e) {} return super.findAllEntitiesAsMapOld(instanceType); } @Override protected <T extends ManagedEntity> T findEntityByIdNameNullableOld(final String name, final Class<T> instanceType, final Datacenter dc) throws VmwareCheckedCloudException { try {Thread.sleep(1000);} catch (InterruptedException e) {} return super.findEntityByIdNameNullableOld(name, instanceType, dc); } @Override public void test() throws VmwareCheckedCloudException { try {Thread.sleep(1000);} catch (InterruptedException e) {} super.test(); } }; final CloudEventDispatcher dispatcher = new CloudEventDispatcher(); final Mockery m = new Mockery(); final ServerResponsibility serverResponsibility = m.mock(ServerResponsibility.class); final CloudRegistryImpl cloudRegistrar = new CloudRegistryImpl(dispatcher, serverResponsibility); final CloudManagerBase cloudManagerBase = m.mock(CloudManagerBase.class); final PluginDescriptor pd = m.mock(PluginDescriptor.class); final CloudState state = m.mock(CloudState.class); m.checking(new Expectations(){{ allowing(pd).getPluginResourcesPath("vmware-settings.html"); will(returnValue("aaa.html")); allowing(state).getProfileId(); will(returnValue(PROFILE_ID)); allowing(state).getProjectId(); will(returnValue(PROJECT_ID)); allowing(cloudManagerBase).findProfileById(PROJECT_ID, PROFILE_ID); will(returnValue(myProfile)); allowing(serverResponsibility).canManageClouds(); will(returnValue(true)); }}); final CloudInstancesProvider instancesProvider = new CloudInstancesProvider() { public void iterateInstances(@NotNull final CloudInstancesProviderExtendedCallback callback) {} public void iterateProfileInstances(@NotNull final CloudProfile profile, @NotNull final CloudInstancesProviderExtendedCallback callback) {} public void markInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) {} public boolean isInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) {return false;} }; final VMWareCloudClientFactory factory = new VMWareCloudClientFactory(cloudRegistrar, pd, new ServerPaths(myIdxStorage.getAbsolutePath()), instancesProvider, cloudManagerBase, new ServerSettingsImpl(), myTaskManager, () -> { try {return KeyStore.getInstance("pkcs12");} catch (KeyStoreException e) {throw new RuntimeException(e);} }){ @NotNull @Override protected VMWareApiConnector createConnectorFromParams(@NotNull final CloudState state, final CloudClientParameters params) { return myFakeApi; } }; Runnable r = new Runnable() { public void run() { for (CloudClientParameters param : profileParams) { factory.createNewClient(state, param); } } }; final ExecutorService executorService = Executors.newSingleThreadExecutor(); try { final Future<?> future = executorService.submit(r); future.get(); new WaitFor(500){ @Override protected boolean condition() { return future.isDone(); } }.assertCompleted("Recreation of cloud profiles takes too long"); } finally { ThreadUtil.shutdownGracefully(executorService, "test executor"); } } public void enforce_change_of_stuck_instance_status() throws RemoteException, ExecutionException, InterruptedException { myStuckTime.set(3*1000); recreateClient(250); final VmwareCloudInstance instance = startNewInstanceAndWait("image1"); FakeModel.instance().getVms().get(instance.getName()).shutdownGuest(); instance.setStatus(InstanceStatus.STOPPING); new WaitFor(6*1000){ @Override protected boolean condition() { return instance.getStatus() == InstanceStatus.STOPPED; } }.assertCompleted("should have changed the status"); } public void shouldDeleteOldInstancesIfLimitReached() throws VmwareCheckedCloudException, RemoteException, InterruptedException { final List<VmwareCloudInstance> instances2stop = new ArrayList<VmwareCloudInstance>(); for (int i=0; i<3; i++){ final VmwareCloudInstance instance = startNewInstanceAndWait("image_template"); if (i%2 == 0) instances2stop.add(instance); } new WaitFor(3*1000){ @Override protected boolean condition() { return getImageByName("image_template").getInstances().size() == 3; } }.assertCompleted("should have started and catched"); for (VmwareCloudInstance inst : instances2stop) { FakeModel.instance().getVirtualMachine(inst.getName()).shutdownGuest(); } new WaitFor(3*1000){ @Override protected boolean condition() { boolean stoppedAll = true; for (VmwareCloudInstance inst : instances2stop) { stoppedAll = stoppedAll && FakeModel.instance().getVirtualMachine(inst.getName()).getRuntime().getPowerState() == VirtualMachinePowerState.poweredOff; } if (stoppedAll){ final VmwareCloudImage img = getImageByName("image_template"); if (System.currentTimeMillis() % 20 == 0){ System.out.printf("%s%n", img.getInstances()); } return img.canStartNewInstance(); } return false; } }.assertCompleted("Should have stopped and state updated"); new WaitFor(10*1000){ @Override protected boolean condition() { final VmwareCloudImage img = getImageByName("image_template"); int stoppedCount = 0; for (VmwareCloudInstance instance : img.getInstances()) { if (instance.getStatus() ==InstanceStatus.STOPPED) stoppedCount++; } return stoppedCount ==2; } }.assertCompleted("Should have stopped"); // requires time for orphaned timeout Thread.sleep(500); System.setProperty("teamcity.vmware.stopped.orphaned.timeout", "200"); startNewInstanceAndCheck("image_template", new HashMap<>(), false); new WaitFor(5000){ @Override protected boolean condition() { boolean instancesDeleted = true; for (VmwareCloudInstance inst : instances2stop) { instancesDeleted = instancesDeleted && (FakeModel.instance().getVirtualMachine(inst.getName()) == null); } return instancesDeleted; } }.assertCompleted("Should have deleted"); new WaitFor(10*1000){ @Override protected boolean condition() { final VmwareCloudImage img = getImageByName("image_template"); return img.getInstances().size() == 1; } }.assertCompleted("should have only 1 instance"); for (int i=0; i<2; i++){ final VmwareCloudInstance instance = startNewInstanceAndWait("image_template"); assertEquals("image_template-" + (i+5), instance.getName()); } } public void markInstanceExpiredWhenSnapshotNameChanges() throws MalformedURLException { final Map<String, CloudInstance> instancesMarkedExpired = new HashMap<String, CloudInstance>(); final CloudInstancesProvider instancesProviderStub = new CloudInstancesProvider() { public void iterateInstances(@NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateInstances"); // } @Override public void iterateProfileInstances(@NotNull final CloudProfile profile, @NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateProfileInstances"); } public void markInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { instancesMarkedExpired.put(instance.getInstanceId(),instance ); } public boolean isInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { return instancesMarkedExpired.containsKey(instance.getInstanceId()); } }; myFakeApi = new FakeApiConnector(null, null, instancesProviderStub); recreateClient(); final VmwareCloudInstance in = startNewInstanceAndWait("image2"); assertEquals(VirtualMachinePowerState.poweredOn, FakeModel.instance().getVirtualMachine(in.getName()).getRuntime().getPowerState()); FakeModel.instance().addVMSnapshot("image2", "snap2"); myFakeApi.checkImage(getImageByName("image2")); assertTrue(instancesProviderStub.isInstanceExpired(myProfile, in)); } public void mark_instance_expired_when_sourcevmid_changes() throws MalformedURLException { final Map<String, CloudInstance> instancesMarkedExpired = new HashMap<String, CloudInstance>(); final CloudInstancesProvider instancesProviderStub = new CloudInstancesProvider() { public void iterateInstances(@NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateInstances"); } @Override public void iterateProfileInstances(@NotNull final CloudProfile profile, @NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateProfileInstances"); } public void markInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { instancesMarkedExpired.put(instance.getInstanceId(),instance ); } public boolean isInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { return instancesMarkedExpired.containsKey(instance.getInstanceId()); } }; myFakeApi = new FakeApiConnector(null, null, instancesProviderStub); recreateClient(250); final VmwareCloudInstance in = startNewInstanceAndWait("image2"); assertEquals(VirtualMachinePowerState.poweredOn, FakeModel.instance().getVirtualMachine(in.getName()).getRuntime().getPowerState()); final FakeVirtualMachine image2 = FakeModel.instance().getVirtualMachine("image2"); final ManagedObjectReference mor = new ManagedObjectReference(); mor.setVal("vm-123456"); mor.setType("VirtualMachine"); image2.setMOR(mor); myFakeApi.checkImage(getImageByName("image2")); assertTrue(instancesProviderStub.isInstanceExpired(myProfile, in)); } public void shouldnt_mark_expired_if_vmsourceid_is_absent() throws MalformedURLException { final Map<String, CloudInstance> instancesMarkedExpired = new HashMap<String, CloudInstance>(); final CloudInstancesProvider instancesProviderStub = new CloudInstancesProvider() { @Override public void iterateInstances(@NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateInstances"); } @Override public void iterateProfileInstances(@NotNull final CloudProfile profile, @NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateProfileInstances"); } public void markInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { instancesMarkedExpired.put(instance.getInstanceId(),instance ); } public boolean isInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { return instancesMarkedExpired.containsKey(instance.getInstanceId()); } }; myFakeApi = new FakeApiConnector(null, null, instancesProviderStub); recreateClient(250); final VmwareCloudInstance in = startNewInstanceAndWait("image2"); assertEquals(VirtualMachinePowerState.poweredOn, FakeModel.instance().getVirtualMachine(in.getName()).getRuntime().getPowerState()); final FakeVirtualMachine clonedVM = FakeModel.instance().getVirtualMachine(in.getName()); final VirtualMachineConfigInfo oldConfig = clonedVM.getConfig(); clonedVM.setConfigInfo(new VirtualMachineConfigInfo(){ @Override public String getName() { return oldConfig.getName(); } @Override public boolean isTemplate() { return oldConfig.isTemplate(); } @Override public String getChangeVersion() { return oldConfig.getChangeVersion(); } @Override public OptionValue[] getExtraConfig() { final OptionValue[] extraConfig = oldConfig.getExtraConfig(); return Arrays.stream(extraConfig).filter(o->!o.getKey().equals(TEAMCITY_VMWARE_IMAGE_SOURCE_VM_ID)).toArray(OptionValue[]::new); } }); recreateClient(250); new WaitFor(5*1000){ protected boolean condition() { return getImageByName("image2").getInstances().size() == 1; } }.assertCompleted("Should have found started instance"); final ManagedObjectReference mor = new ManagedObjectReference(); mor.setVal("vm-123456"); mor.setType("VirtualMachine"); FakeModel.instance().getVirtualMachine("image2").setMOR(mor); myFakeApi.checkImage(getImageByName("image2")); assertFalse(instancesProviderStub.isInstanceExpired(myProfile, in)); } public void shouldConsiderProfileInstancesLimit(){ updateClientParameters(createMap(VMWareWebConstants.PROFILE_INSTANCE_LIMIT, "1")); recreateClient(); assertTrue(myClient.canStartNewInstanceWithDetails(getImageByName("image2")).isPositive()); assertTrue(myClient.canStartNewInstanceWithDetails(getImageByName("image1")).isPositive()); assertTrue(myClient.canStartNewInstanceWithDetails(getImageByName("image_template")).isPositive()); startNewInstanceAndWait("image2"); assertFalse(myClient.canStartNewInstanceWithDetails(getImageByName("image2")).isPositive()); assertFalse(myClient.canStartNewInstanceWithDetails(getImageByName("image1")).isPositive()); assertFalse(myClient.canStartNewInstanceWithDetails(getImageByName("image_template")).isPositive()); } public void checkCustomization(){ final CustomizationSpec linuxSpec = FakeModel.instance().getCustomizationSpec("linux"); final CustomizationLinuxOptions linuxOptions = new CustomizationLinuxOptions(); linuxSpec.setOptions(linuxOptions); final VmwareCloudInstance newInstance = startNewInstanceAndWait("image_template"); final FakeVirtualMachine virtualMachine = FakeModel.instance().getVirtualMachine(newInstance.getName()); assertEquals(linuxSpec, virtualMachine.getCustomizationSpec()); } public void handle_instances_deleted_in_the_middle_of_check() throws MalformedURLException, VmwareCheckedCloudException { if (true) throw new SkipException("Not relevant, because we now retrieve all data at once, without additional calls"); final Set<String> instances2RemoveAfterGet = new HashSet<>(); myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID){ @NotNull @Override protected <T extends ManagedEntity> Map<String, T> findAllEntitiesAsMapOld(final Class<T> instanceType) throws VmwareCheckedCloudException { final Map<String, T> entities = super.findAllEntitiesAsMapOld(instanceType); final Map<String, T> result = new HashMap<String, T>(); for (Map.Entry<String, T> entry : entities.entrySet()) { if (instances2RemoveAfterGet.contains(entry.getKey()) && entry.getValue() instanceof FakeVirtualMachine){ final FakeVirtualMachine fvm = (FakeVirtualMachine) entry.getValue(); fvm.setGone(true); } result.put(entry.getKey(), entry.getValue()); } return result; } }; final VmwareCloudInstance i1 = startNewInstanceAndWait("image2"); final VmwareCloudInstance i2 = startNewInstanceAndWait("image2"); final VmwareCloudInstance i3 = startNewInstanceAndWait("image2"); instances2RemoveAfterGet.add(i1.getInstanceId()); instances2RemoveAfterGet.add(i2.getInstanceId()); final Map<String, AbstractInstance> image2Instances = myFakeApi.fetchInstances(getImageByName("image2")); assertEquals(1, image2Instances.size()); } public void check_profile_id_and_server_uuid() throws Exception { startAndCheckCloneDeletedAfterTermination("image2", new Checker<VmwareCloudInstance>() { public void check(final VmwareCloudInstance data) throws CheckedCloudException { assertTrue("image2-1".equals(data.getInstanceId())); final Map<String, String> vmParams = myFakeApi.getVMParams(data.getInstanceId()); assertEquals("true", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_CLONED_INSTANCE)); assertEquals("image2", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SOURCE_VM_NAME)); assertEquals("snap", vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_IMAGE_SNAPSHOT)); assertEquals(TEST_SERVER_UUID, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_SERVER_UUID)); assertEquals(PROFILE_ID, vmParams.get(VMWareApiConnector.TEAMCITY_VMWARE_PROFILE_ID)); } }, false); } public void do_not_catch_instances_from_another_server() throws MalformedURLException { startNewInstanceAndWait("image2"); myFakeApi = new FakeApiConnector("2345-6789-0123", PROFILE_ID); recreateClient(); assertEquals(0, getImageByName("image2").getInstances().size()); startNewInstanceAndWait("image2"); recreateClient(); assertEquals(1, getImageByName("image2").getInstances().size()); } public void do_catch_instances_from_another_profile() throws MalformedURLException { startNewInstanceAndWait("image2"); recreateClient(); assertEquals(1, getImageByName("image2").getInstances().size()); myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, "cp2"); recreateClient(); assertEquals(1, getImageByName("image2").getInstances().size()); } public void start_instance_should_not_block_ui() throws MalformedURLException, InterruptedException, CheckedCloudException { final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); final Lock lock = new ReentrantLock(); final AtomicBoolean shouldLock = new AtomicBoolean(false); try { myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID) { @Override protected <T extends ManagedEntity> T findEntityByIdNameNullableOld(final String name, final Class<T> instanceType, final Datacenter dc) throws VmwareCheckedCloudException { try { if (shouldLock.get()) { lock.lock(); // will stuck here } return super.findEntityByIdNameNullableOld(name, instanceType, dc); } finally { if (shouldLock.get()) lock.unlock(); } } @Override protected <T extends ManagedEntity> Collection<T> findAllEntitiesOld(final Class<T> instanceType) throws VmwareCheckedCloudException { try { if (shouldLock.get()) { lock.lock(); // will stuck here } return super.findAllEntitiesOld(instanceType); } finally { if (shouldLock.get()) lock.unlock(); } } @Override protected <T extends ManagedEntity> Map<String, T> findAllEntitiesAsMapOld(final Class<T> instanceType) throws VmwareCheckedCloudException { try { if (shouldLock.get()) { lock.lock(); // will stuck here } return super.findAllEntitiesAsMapOld(instanceType); } finally { if (shouldLock.get()) lock.unlock(); } } }; recreateClient(); final VmwareCloudInstance startedInstance = startNewInstanceAndWait("image2"); terminateAndDeleteIfNecessary(false, startedInstance); shouldLock.set(true); lock.lock(); executor.execute(new Runnable() { public void run() { // start already existing clone myClient.startNewInstance(getImageByName("image2"), createUserData("image2_agent")); // start-stop instance myClient.startNewInstance(getImageByName("image1"), createUserData("image1_agent")); // clone a new one myClient.startNewInstance(getImageByName("image_template"), createUserData("image_template_agent")); } }); executor.shutdown(); assertTrue("canStart method blocks the thread!", executor.awaitTermination(100000, TimeUnit.MILLISECONDS)); } finally { lock.unlock(); executor.shutdownNow(); } } public void should_consider_profile_limit_on_reload(){ final CloudClientParameters clientParameters2 = new CloudClientParametersImpl(Collections.emptyMap(), VmwareTestUtils.getImageParameters(PROJECT_ID, "[{sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp'," + "maxInstances:1,behaviour:'ON_DEMAND_CLONE',customizationSpec:'someCustomization'}]")); final CloudClientParameters clientParameters3 = new CloudClientParametersImpl(Collections.emptyMap(), VmwareTestUtils.getImageParameters(PROJECT_ID, "[{'source-id':'image_template',sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "',folder:'cf',pool:'rp',maxInstances:1,behaviour:'FRESH_CLONE', customizationSpec: 'linux'}]")); final VMWareCloudClient client2 = recreateClient(myClient, clientParameters2); final VMWareCloudClient client3 = recreateClient(null, clientParameters3); startNewInstanceAndWait(client2, "image2"); startNewInstanceAndWait(client3, "image_template"); client2.dispose(); client3.dispose(); final VMWareCloudClient client3_1 = recreateClient(null, clientParameters3); final VMWareCloudClient client2_1 = recreateClient(null, clientParameters2); try { startNewInstanceAndWait(client3_1, "image_template"); fail("Shouldn't start more of client3"); } catch (Exception ex) { } try { startNewInstanceAndWait(client2_1, "image2"); fail("Shouldn't start more of image2"); } catch (Exception ex) { } } public void should_consider_profile_limit_on_reload_2(){ final CloudClientParameters clientParameters2 = new CloudClientParametersImpl( Collections.emptyMap(), VmwareTestUtils.getImageParameters(PROJECT_ID, "[{sourceVmName:'image2',snapshot:'snap*',folder:'cf',pool:'rp'," + "maxInstances:1,behaviour:'ON_DEMAND_CLONE',customizationSpec:'someCustomization'}]")); final CloudClientParameters clientParameters3 = new CloudClientParametersImpl( Collections.emptyMap(), VmwareTestUtils.getImageParameters(PROJECT_ID, "[{'source-id':'image_template',sourceVmName:'image_template', snapshot:'" + VmwareConstants.CURRENT_STATE + "',folder:'cf',pool:'rp',maxInstances:1,behaviour:'FRESH_CLONE', customizationSpec: 'linux'}]")); final VMWareCloudClient client2 = recreateClient(myClient, clientParameters2); final VMWareCloudClient client3 = recreateClient(null, clientParameters3); startNewInstanceAndWait(client2, "image2"); startNewInstanceAndWait(client3, "image_template"); client2.dispose(); client3.dispose(); final VMWareCloudClient client3_1 = recreateClient(null, clientParameters3, false); final VMWareCloudClient client2_1 = recreateClient(null, clientParameters2, false); new WaitFor(5000) { @Override protected boolean condition() { return client3_1.isInitialized() || client2_1.isInitialized(); } }; try { if (client3_1.isInitialized() && client3_1.canStartNewInstanceWithDetails(getImageByName("image_template")).isPositive()) { startNewInstanceAndWait(client3_1, "image_template"); fail("Shouldn't start more of client3"); } } catch (Exception ex) { } try { if (client2_1.isInitialized() && client2_1.canStartNewInstanceWithDetails(getImageByName("image2")).isPositive()) { startNewInstanceAndWait(client2_1, "image2"); fail("Shouldn't start more of image2"); } } catch (Exception ex) { } new WaitFor(5000) { @Override protected boolean condition() { return client3_1.isInitialized() && client2_1.isInitialized(); } }.assertCompleted("clients should be initialized in time"); } @TestFor(issues = "TW-47486") public void shouldnt_throw_error_when_stopping_nonexisting_instance(){ setInternalProperty("teamcity.vsphere.instance.status.update.delay.ms", "250000"); recreateClient(); final VmwareCloudInstance startedInstance = startNewInstanceAndWait("image_template"); final FakeVirtualMachine vm = FakeModel.instance().getVirtualMachine(startedInstance.getName()); assertNotNull(vm); FakeModel.instance().removeVM(vm.getName()); final VmwareCloudImage image = startedInstance.getImage(); assertContains(image.getInstances(), startedInstance); myClient.terminateInstance(startedInstance); new WaitFor(1000) { @Override protected boolean condition() { return !image.getInstances().contains(startedInstance); } }; assertNotContains(image.getInstances(), startedInstance); } @TestFor(issues = "TW-55838") public void should_poweroff_when_instance_doesnt_stop_in_time() throws MalformedURLException { setInternalProperty("teamcity.vmware.guest.shutdown.timeout", "1000"); final VmwareCloudInstance startedInstance = startNewInstanceAndWait("image_template"); final FakeVirtualMachine vm = FakeModel.instance().getVirtualMachine(startedInstance.getName()); final AtomicLong powerOffCalled = new AtomicLong(); final AtomicLong guestShutdownCalled = new AtomicLong(); assertNotNull(vm); FakeModel.instance().getEvents().forEach(event->{ if (event.second.equals("powerOffVM_Task") && event.first.equals(startedInstance.getName())){ assertTrue(powerOffCalled.compareAndSet(0, event.third)); } if (event.second.equals("shutdownGuest") && event.first.equals(startedInstance.getName())){ assertTrue(guestShutdownCalled.compareAndSet(0, event.third)); } }); assertEquals(0, powerOffCalled.get() + guestShutdownCalled.get()); setInternalProperty("test.guest.shutdown.sleep.interval", "3000"); myClient.terminateInstance(startedInstance); new WaitFor(2*1000){ @Override protected boolean condition() { return FakeModel.instance().getVirtualMachine(startedInstance.getName()) == null; } }; FakeModel.instance().getEvents().forEach(event->{ if (event.second.equals("powerOffVM_Task") && event.first.equals(startedInstance.getName())){ assertTrue(powerOffCalled.compareAndSet(0, event.third)); } if (event.second.equals("shutdownGuest") && event.first.equals(startedInstance.getName())){ assertTrue(guestShutdownCalled.compareAndSet(0, event.third)); } }); assertTrue(guestShutdownCalled.get() > 0); long diff = powerOffCalled.get() - guestShutdownCalled.get(); assertTrue( diff > 0 && diff < 2000 ); } @TestFor(issues = "TW-56111") public void dont_check_not_started_instances_for_expiration() throws MalformedURLException { final Map<String, CloudInstance> instancesMarkedExpired = new HashMap<String, CloudInstance>(); final CloudInstancesProvider instancesProviderStub = new CloudInstancesProvider(){ public void iterateInstances(@NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateInstances"); } public void iterateProfileInstances(@NotNull final CloudProfile profile, @NotNull final CloudInstancesProviderExtendedCallback callback) { throw new UnsupportedOperationException(".iterateProfileInstances"); } public void markInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { instancesMarkedExpired.put(instance.getInstanceId(),instance ); } public boolean isInstanceExpired(@NotNull final CloudProfile profile, @NotNull final CloudInstance instance) { return instancesMarkedExpired.containsKey(instance.getInstanceId()); } }; final CountDownLatch latch = new CountDownLatch(1); myFakeApi = new FakeApiConnector(null, null, instancesProviderStub){ @Override public Task reconfigureInstance(@NotNull final VmwareCloudInstance instance, @NotNull final String agentName, @NotNull final CloudInstanceUserData userData) throws VmwareCheckedCloudException { try { latch.await(); } catch (InterruptedException e) { fail("Should have waited"); } return super.reconfigureInstance(instance, agentName, userData); } }; recreateClient(250); final VmwareCloudInstance instance = startNewInstance("image2"); new WaitFor(1000) { @Override protected boolean condition() { final FakeVirtualMachine vm = FakeModel.instance().getVirtualMachine(instance.getName()); return vm != null && vm.getRuntime().getPowerState() == VirtualMachinePowerState.poweredOn; } }; assertEquals(VirtualMachinePowerState.poweredOn, FakeModel.instance().getVirtualMachine(instance.getName()).getRuntime().getPowerState()); final FakeVirtualMachine image2 = FakeModel.instance().getVirtualMachine("image2"); final ManagedObjectReference mor = new ManagedObjectReference(); mor.setVal("vm-123456"); mor.setType("VirtualMachine"); image2.setMOR(mor); myFakeApi.checkImage(getImageByName("image2")); assertFalse(instancesProviderStub.isInstanceExpired(myProfile, instance)); latch.countDown(); new WaitFor(1000){ @Override protected boolean condition() { return instance.getStatus() == InstanceStatus.RUNNING; } }; myFakeApi.checkImage(getImageByName("image2")); assertTrue(instancesProviderStub.isInstanceExpired(myProfile, instance)); } @TestFor(issues = "TW-56632") public void should_poweroff_and_delete_when_instance_doesnt_stop_in_time(){ setInternalProperty("teamcity.vmware.guest.shutdown.timeout.seconds", "1"); final VmwareCloudInstance startedInstance = startNewInstanceAndWait("image_template"); final FakeVirtualMachine vm = FakeModel.instance().getVirtualMachine(startedInstance.getName()); final AtomicLong powerOffCalled = new AtomicLong(); final AtomicLong guestShutdownCalled = new AtomicLong(); final AtomicLong destroyCalled = new AtomicLong(); assertNotNull(vm); FakeModel.instance().getEvents().forEach(event->{ if (event.second.equals("powerOffVM_Task") && event.first.equals(startedInstance.getName())){ assertTrue(powerOffCalled.compareAndSet(0, event.third)); } if (event.second.equals("shutdownGuest") && event.first.equals(startedInstance.getName())){ assertTrue(guestShutdownCalled.compareAndSet(0, event.third)); } }); assertEquals(0, powerOffCalled.get() + guestShutdownCalled.get()); setInternalProperty("test.guest.shutdown.sleep.interval", "3000"); myClient.terminateInstance(startedInstance); new WaitFor(2*1000){ @Override protected boolean condition() { return FakeModel.instance().getVirtualMachine(startedInstance.getName()) == null; } }; assertTrue(FakeModel.instance().getVirtualMachine(startedInstance.getName()) == null); FakeModel.instance().getEvents().forEach(event->{ if (event.second.equals("powerOffVM_Task") && event.first.equals(startedInstance.getName())){ assertTrue(powerOffCalled.compareAndSet(0, event.third)); } if (event.second.equals("shutdownGuest") && event.first.equals(startedInstance.getName())){ assertTrue(guestShutdownCalled.compareAndSet(0, event.third)); } if (event.second.equals("destroy_Task") && event.first.equals(startedInstance.getName())){ assertTrue(destroyCalled.compareAndSet(0, event.third)); } }); assertTrue(guestShutdownCalled.get() > 0); long diff = powerOffCalled.get() - guestShutdownCalled.get(); assertTrue( diff > 0 && diff < 2000 ); assertTrue( destroyCalled.get() > 0 ); } @TestFor(issues = "TW-61456") public void recover_if_cant_connect_on_start() throws MalformedURLException { final AtomicBoolean failure = new AtomicBoolean(false); final AtomicLong lastApiCallTime = new AtomicLong(0); myFakeApi = new FakeApiConnector(TEST_SERVER_UUID, PROFILE_ID){ @Override protected <T extends ManagedEntity> Collection<T> findAllEntitiesOld(final Class<T> instanceType) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.findAllEntitiesOld(instanceType); } @Override protected <T extends ManagedEntity> Map<String, T> findAllEntitiesAsMapOld(final Class<T> instanceType) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.findAllEntitiesAsMapOld(instanceType); } @Override protected <T extends ManagedEntity> Pair<T,Datacenter> findEntityByIdNameOld(final String name, final Class<T> instanceType) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.findEntityByIdNameOld(name, instanceType); } @Override protected Map<String, VirtualMachine> searchVMsByNames(@NotNull Collection<String> names, @Nullable Datacenter dc) throws VmwareCheckedCloudException { lastApiCallTime.set(System.currentTimeMillis()); if (failure.get()){ throw new VmwareCheckedCloudException("Cannot connect"); } return super.searchVMsByNames(names, dc); } }; failure.set(true); recreateClient(250); new WaitFor(1000){ @Override protected boolean condition() { return myClient.isInitialized(); } }; myClient.getImages().forEach(img-> assertNotNull(img.getErrorInfo())); failure.set(false); new WaitFor(1000){ @Override protected boolean condition() { AtomicBoolean result = new AtomicBoolean(true); myClient.getImages().forEach(img-> result.compareAndSet(true, img.getErrorInfo()==null)); return result.get(); } }; myClient.getImages().forEach(img-> assertNull(img.getErrorInfo())); } /* * * * Helper methods * * * */ private static CloudInstanceUserData createUserData(String agentName){ return createUserData(agentName, Collections.<String, String>emptyMap()); } private static CloudInstanceUserData createUserData(String agentName, Map<String, String> parameters){ Map<String, String> map = new HashMap<String, String>(parameters); map.put(CloudConstants.PROFILE_ID, PROFILE_ID); CloudInstanceUserData userData = new CloudInstanceUserData(agentName, "authToken", "http://localhost:8080", 3 * 60 * 1000l, "My profile", map); return userData; } private static String wrapWithArraySymbols(String str) { return String.format("[%s]", str); } private VmwareCloudInstance startAndCheckCloneDeletedAfterTermination(String imageName, Checker<VmwareCloudInstance> instanceChecker, boolean shouldBeDeleted) throws Exception { final VmwareCloudInstance instance = startAndCheckInstance(imageName, instanceChecker); terminateAndDeleteIfNecessary(shouldBeDeleted, instance); return instance; } private void terminateAndDeleteIfNecessary(final boolean shouldBeDeleted, final VmwareCloudInstance instance) throws CheckedCloudException { myClient.terminateInstance(instance); new WaitFor(5*1000){ protected boolean condition() { return instance.getStatus()==InstanceStatus.STOPPED; } }.assertCompleted(); final String name = instance.getName(); final WaitFor waitFor = new WaitFor(10 * 1000) { @Override protected boolean condition() { try { if (shouldBeDeleted) { return (myFakeApi.getAllVMsMap(false).get(name) == null); } else { return myFakeApi.getInstanceDetails(name).getInstanceStatus() == InstanceStatus.STOPPED; } } catch (CheckedCloudException e) { return false; } } }; waitFor.assertCompleted("template clone should be deleted after execution"); } private VmwareCloudInstance startAndCheckInstance(final String imageName, final Checker<VmwareCloudInstance> instanceChecker) throws Exception { final VmwareCloudInstance instance = startNewInstanceAndWait(imageName); new WaitFor(3 * 1000) { @Override protected boolean condition() { final VmwareInstance vm; try { vm = myFakeApi.getAllVMsMap(false).get(instance.getName()); return vm != null && vm.getInstanceStatus() == InstanceStatus.RUNNING; } catch (CheckedCloudException e) { return false; } } }; final VmwareInstance vm = myFakeApi.getAllVMsMap(false).get(instance.getName()); assertNotNull("instance " + instance.getName() + " must exists", vm); assertEquals("Must be running", InstanceStatus.RUNNING, vm.getInstanceStatus()); if (instanceChecker != null) { instanceChecker.check(instance); } return instance; } private VmwareCloudInstance startNewInstanceAndWait(VMWareCloudClient client, String imageName) { return startNewInstanceAndCheck(client, imageName, new HashMap<String, String>(), true); } private VmwareCloudInstance startNewInstanceAndWait(String imageName) { return startNewInstanceAndWait(imageName, new HashMap<String, String>()); } private VmwareCloudInstance startNewInstanceAndWait(String imageName, Map<String, String> parameters) { return startNewInstanceAndCheck(imageName, parameters, true); } private VmwareCloudInstance startNewInstanceAndCheck(String imageName, Map<String, String> parameters, boolean instanceShouldStart) { return startNewInstanceAndCheck(myClient, imageName, parameters, instanceShouldStart); } private VmwareCloudInstance startNewInstanceAndCheck(VMWareCloudClient client, String imageName, Map<String, String> parameters, boolean instanceShouldStart) { return startNewInstanceAndCheck(client, imageName, parameters, instanceShouldStart, true); } private VmwareCloudInstance startNewInstanceAndCheck(VMWareCloudClient client, String imageName, Map<String, String> parameters, boolean instanceShouldStart, boolean waitForInstance2Start) { final CloudInstanceUserData userData = createUserData(imageName + "_agent", parameters); final VmwareCloudImage image = getImageByName(client, imageName); final Collection<VmwareCloudInstance> runningInstances = image .getInstances() .stream() .filter(i->i.getStatus() == InstanceStatus.RUNNING) .collect(Collectors.toList()); final VmwareCloudInstance vmwareCloudInstance = client.startNewInstance(image, userData); final boolean ready = vmwareCloudInstance.isReady(); System.out.printf("Instance '%s'. Ready: %b%n", vmwareCloudInstance.getName(), ready); if (!waitForInstance2Start) return vmwareCloudInstance; final WaitFor waitFor = new WaitFor(2 * 1000) { @Override protected boolean condition() { if (ready) { return vmwareCloudInstance.getStatus() == InstanceStatus.RUNNING; } else { return image .getInstances() .stream() .anyMatch(i->i.getStatus() == InstanceStatus.RUNNING && !runningInstances.contains(i)); } } }; if (instanceShouldStart) { waitFor.assertCompleted(); if (!ready) { final VmwareCloudInstance startedInstance = image .getInstances() .stream() .filter(i -> i.getStatus() == InstanceStatus.RUNNING && !runningInstances.contains(i)).findAny().get(); assertNotNull(startedInstance); return startedInstance; } else { return vmwareCloudInstance; } } else { assertFalse(waitFor.isConditionRealized()); return null; } } private VmwareCloudInstance startNewInstance(String imageName){ return startNewInstanceAndCheck(myClient, imageName, createMap(), true, false); } private static String getExtraConfigValue(final OptionValue[] extraConfig, final String key) { for (OptionValue param : extraConfig) { if (param.getKey().equals(key)) { return String.valueOf(param.getValue()); } } return null; } private VmwareCloudImage getImageByName(final String name) { return getImageByName(myClient, name); } private VmwareCloudImage getImageByName(final VMWareCloudClient client, final String name) { for (CloudImage image : client.getImages()) { if (image.getName().equals(name)) { return (VmwareCloudImage)image; } } throw new RuntimeException("unable to find image by name: " + name); } private void recreateClient(){ recreateClient(TeamCityProperties.getLong("teamcity.vsphere.instance.status.update.delay.ms", 250)); } private void recreateClient(final long updateDelay) { myClient = recreateClient(myClient, myClientParameters, updateDelay, true); } private VMWareCloudClient recreateClient(final VMWareCloudClient oldClient, final CloudClientParameters parameters){ final long updateTime = TeamCityProperties.getLong("teamcity.vsphere.instance.status.update.delay.ms", 250); return recreateClient(oldClient, parameters, updateTime, true); } private VMWareCloudClient recreateClient(final VMWareCloudClient oldClient, final CloudClientParameters parameters, boolean waitForInitialization){ final long updateTime = TeamCityProperties.getLong("teamcity.vsphere.instance.status.update.delay.ms", 250); return recreateClient(oldClient, parameters, updateTime, waitForInitialization); } private VMWareCloudClient recreateClient(final VMWareCloudClient oldClient, final CloudClientParameters parameters, long updateTime, boolean waitForInitialization){ if (oldClient != null) { oldClient.dispose(); } myProfile = VmwareTestUtils.createProfileFromProps(parameters); final VMWareCloudClient newClient = new VMWareCloudClient(myProfile, myFakeApi, myTaskManager, myIdxStorage); final Collection<VmwareCloudImageDetails> images = VMWareCloudClientFactory.parseImageDataInternal(parameters); newClient.populateImagesData(images, updateTime, updateTime); if (waitForInitialization) { new WaitFor(5000) { @Override protected boolean condition() { return newClient.isInitialized(); } }.assertCompleted("Must be initialized"); } return newClient; } @AfterMethod public void tearDown() throws Exception { if (myClient != null) { myClient.dispose(); myClient = null; } FakeModel.instance().clear(); super.tearDown(); } private interface Checker<T> { void check(T data) throws Exception; } }