/******************************************************************************* * Copyright (c) 2015, 2016 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.test; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.IDocument; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryBootDashModel; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryRunTarget; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryRunTargetType; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryTargetWizardModel; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFClientParams; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CFSpace; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.CloudFoundryClientFactory; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.deployment.CloudApplicationDeploymentProperties; import org.springframework.ide.eclipse.boot.dash.dialogs.DeploymentPropertiesDialogModel; import org.springframework.ide.eclipse.boot.dash.dialogs.DeploymentPropertiesDialogModel.ManifestType; import org.springframework.ide.eclipse.boot.dash.dialogs.ManifestDiffDialogModel; import org.springframework.ide.eclipse.boot.dash.dialogs.PasswordDialogModel; import org.springframework.ide.eclipse.boot.dash.dialogs.StoreCredentialsMode; import org.springframework.ide.eclipse.boot.dash.metadata.IPropertyStore; import org.springframework.ide.eclipse.boot.dash.model.BootDashModel; import org.springframework.ide.eclipse.boot.dash.model.BootDashModelContext; import org.springframework.ide.eclipse.boot.dash.model.LocalBootDashModel; import org.springframework.ide.eclipse.boot.dash.model.RunTarget; import org.springframework.ide.eclipse.boot.dash.model.SecuredCredentialsStore; import org.springframework.ide.eclipse.boot.dash.model.UserInteractions; import org.springframework.ide.eclipse.boot.dash.model.runtargettypes.RunTargetType; import org.springframework.ide.eclipse.boot.dash.model.runtargettypes.RunTargetTypes; import org.springframework.ide.eclipse.boot.dash.test.mocks.MockRunnableContext; import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition; import org.springsource.ide.eclipse.commons.frameworks.test.util.Asserter1; import com.google.common.collect.ImmutableSet; /** * @author Kris De Volder */ public class CloudFoundryTestHarness extends BootDashViewModelHarness { @FunctionalInterface public interface DeploymentAnswerer { void apply(DeploymentPropertiesDialogModel model) throws Exception; } @FunctionalInterface public interface PasswordAnswerer { void apply(PasswordDialogModel model) throws Exception; } /** * How long to wait for deleted app to disapear from the model. */ public static final long APP_DELETE_TIMEOUT = TimeUnit.MINUTES.toMillis(1); /** * How long to wait for deleted app to disapear from the model. */ public static final long SERVICE_DELETE_TIMEOUT = TimeUnit.MINUTES.toMillis(1); /** * How long to wait for a deployed app to show up in the model? This should * be relatively short. */ public static final long APP_IS_VISIBLE_TIMEOUT = 20_000; /** * How long to wait for a deployed app to transition to running state. */ public static final long APP_DEPLOY_TIMEOUT = TimeUnit.MINUTES.toMillis(6); /** * How long to wait on retrieving request mappings from a CF app. */ public static final long FETCH_REQUEST_MAPPINGS_TIMEOUT = 5_000; /** * How long to wait for runtarget to become 'connected'. */ public static final long CONNECT_TARGET_TIMEOUT = 30_000; public static CloudFoundryTestHarness create(BootDashModelContext context) throws Exception { CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE; return create(context, clientFactory); } protected static CloudFoundryTestHarness create( BootDashModelContext context, CloudFoundryClientFactory clientFactory ) throws Exception { CloudFoundryRunTargetType cfTargetType = new CloudFoundryRunTargetType(context, clientFactory); return new CloudFoundryTestHarness(context, clientFactory, cfTargetType); } public CloudFoundryBootDashModel getCfTargetModel() { return (CloudFoundryBootDashModel) getRunTargetModel(cfTargetType); } public CloudFoundryBootDashModel createCfTarget( CFClientParams params, StoreCredentialsMode storePassword ) throws Exception { return createCfTarget(params, storePassword, (wizard) -> assertOk(wizard.getValidator())); } public CloudFoundryBootDashModel createCfTarget( CFClientParams params, StoreCredentialsMode storePassword, Asserter1<CloudFoundryTargetWizardModel> wizardAsserter ) throws Exception { CloudFoundryTargetWizardModel wizard = new CloudFoundryTargetWizardModel(cfTargetType, clientFactory, NO_TARGETS, context); wizard.setUrl(params.getApiUrl()); wizard.setUsername(params.getUsername()); wizard.setStoreCredentials(storePassword); wizard.setMethod(params.getCredentials().getType().toLoginMethod()); wizard.setPassword(params.getCredentials().getSecret()); wizard.setSelfsigned(params.isSelfsigned()); wizard.skipSslValidation(params.skipSslValidation()); wizard.resolveSpaces(new MockRunnableContext()); assertNotNull(wizard.getRefreshToken()); wizard.setSpace(getSpace(wizard, params.getOrgName(), params.getSpaceName())); wizardAsserter.execute(wizard); final CloudFoundryRunTarget newTarget = wizard.finish(); if (newTarget!=null) { model.getRunTargets().add(newTarget); } final CloudFoundryBootDashModel targetModel = getCfModelFor(newTarget); //The created targetModel automatically connected, but this happens asynchly. new ACondition("Wait for connected state", CONNECT_TARGET_TIMEOUT) { public boolean test() throws Exception { return targetModel.isConnected(); } }; return targetModel; } public CloudFoundryBootDashModel createCfTarget(CFClientParams params) throws Exception { return createCfTarget(params, StoreCredentialsMode.STORE_PASSWORD); } public CloudFoundryBootDashModel getCfModelFor(CloudFoundryRunTarget cfTarget) { return (CloudFoundryBootDashModel) model.getSectionByTargetId(cfTarget.getId()); } private static final ImmutableSet<RunTarget> NO_TARGETS = ImmutableSet.of(); private CloudFoundryClientFactory clientFactory; public final CloudFoundryRunTargetType cfTargetType; private CloudFoundryTestHarness(BootDashModelContext context, CloudFoundryClientFactory clientFactory, CloudFoundryRunTargetType cfTargetType) throws Exception { super(context, RunTargetTypes.LOCAL, cfTargetType); Assert.isNotNull(clientFactory, "clientFactory"); this.clientFactory = clientFactory; this.cfTargetType = cfTargetType; } private CFSpace getSpace(CloudFoundryTargetWizardModel wizard, String orgName, String spaceName) { for (CFSpace space : wizard.getSpaces().getOrgSpaces(orgName)) { if (space.getName().equals(spaceName)) { return space; } } fail("Not found org/space = "+orgName+"/"+spaceName); return null; } public void answerDeploymentPrompt(UserInteractions ui, final String appName, final String hostName) throws Exception { when(ui.promptApplicationDeploymentProperties(any(DeploymentPropertiesDialogModel.class))) .thenAnswer(new Answer<CloudApplicationDeploymentProperties>() { @Override public CloudApplicationDeploymentProperties answer(InvocationOnMock invocation) throws Throwable { DeploymentPropertiesDialogModel dialog = (DeploymentPropertiesDialogModel) invocation.getArguments()[0]; dialog.setManifestType(ManifestType.MANUAL); dialog.setManualManifest( "applications:\n" + "- name: "+appName+"\n" + " host: "+hostName+"\n" ); dialog.okPressed(); return dialog.getDeploymentProperties(); } }); } public void answerDeploymentPrompt(UserInteractions ui, final String appName, final String hostName, final List<String> bindServices) throws Exception { //TODO: replace this method with something more 'generic' that accepts a function which is passed the deploymentProperties // so that it can add additional infos to it. answerDeploymentPrompt(ui, (dialog) -> { dialog.setManifestType(ManifestType.MANUAL); dialog.setManualManifest( "applications:\n" + "- name: "+appName+"\n" + " host: "+hostName+"\n" + createServicesBlock(bindServices) ); dialog.okPressed(); }); } private String createServicesBlock(List<String> bindServices) { if (bindServices==null || bindServices.isEmpty()) { return ""; } StringBuilder buf = new StringBuilder(" services:\n"); for (String s : bindServices) { buf.append(" - "+s+"\n"); } return buf.toString(); } public void answerDeploymentPrompt(UserInteractions ui, final String appName, final String hostName, final Map<String,String> env) throws Exception { when(ui.promptApplicationDeploymentProperties(any(DeploymentPropertiesDialogModel.class))) .thenAnswer(new Answer<CloudApplicationDeploymentProperties>() { @Override public CloudApplicationDeploymentProperties answer(InvocationOnMock invocation) throws Throwable { DeploymentPropertiesDialogModel dialog = (DeploymentPropertiesDialogModel) invocation.getArguments()[0]; dialog.setManifestType(ManifestType.MANUAL); dialog.setManualManifest( "applications:\n" + "- name: "+appName+"\n" + " host: "+hostName+"\n" + createEnvBlock(env) ); dialog.okPressed(); return dialog.getDeploymentProperties(); } private String createEnvBlock(Map<String, String> env) { if (env==null || env.isEmpty()) { return ""; } StringBuilder buf = new StringBuilder(" env:\n"); for (Entry<String, String> e : env.entrySet()) { buf.append(" "+e.getKey()+": "+e.getValue()); } return buf.toString(); } }); } /** * Does the same thing as what would happen if a user answered the deployment props dialog by selecting an * existing manifest file. */ public void answerDeploymentPrompt(UserInteractions ui, IFile manifestToSelect) throws Exception { when(ui.promptApplicationDeploymentProperties(any(DeploymentPropertiesDialogModel.class))) .thenAnswer(new Answer<CloudApplicationDeploymentProperties>() { @Override public CloudApplicationDeploymentProperties answer(InvocationOnMock invocation) throws Throwable { DeploymentPropertiesDialogModel dialog = (DeploymentPropertiesDialogModel) invocation.getArguments()[0]; dialog.setManifestType(ManifestType.FILE); dialog.setSelectedManifest(manifestToSelect); dialog.okPressed(); return dialog.getDeploymentProperties(); } }); } public void answerDeploymentPrompt(UserInteractions ui, DeploymentAnswerer answerer) throws Exception { when(ui.promptApplicationDeploymentProperties(any(DeploymentPropertiesDialogModel.class))) .thenAnswer(new Answer<CloudApplicationDeploymentProperties>() { @Override public CloudApplicationDeploymentProperties answer(InvocationOnMock invocation) throws Throwable { DeploymentPropertiesDialogModel dialog = (DeploymentPropertiesDialogModel) invocation.getArguments()[0]; System.out.println(">>>>>>> Opening DeploymentPropertiesDialog"); System.out.println("TYPE: " + dialog.type.getValue()); System.out.println("MANUAL MANIFEST: \n" + dialog.getManualDocument().get()); System.out.println("FILE MANIFEST: "+dialog.getFileLabel().getValue() +"\n"); System.out.println(getFileContent(dialog)); answerer.apply(dialog); System.out.println(">>>>>>> Closed DeploymentPropertiesDialog: "+(dialog.isCanceled()?"CANCEL":"OK")); System.out.println("TYPE: " + dialog.type.getValue()); System.out.println("MANUAL MANIFEST: \n" + dialog.getManualDocument().get()); System.out.println("FILE MANIFEST: "+dialog.getFileLabel().getValue() +"\n"); System.out.println(getFileContent(dialog)); return dialog.getDeploymentProperties(); } }); } public String privateStoreKey(CloudFoundryBootDashModel target) { return secureStoreKey(target)+":token"; } public String secureStoreKey(CloudFoundryBootDashModel target) { return target.getRunTarget().getType().getName()+":"+target.getRunTarget().getId(); } public void answerPasswordPrompt(UserInteractions ui, PasswordAnswerer answerer) { doAnswer(new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { PasswordDialogModel dialog = (PasswordDialogModel) invocation.getArguments()[0]; answerer.apply(dialog); return dialog.isOk(); } }).when(ui).openPasswordDialog(any(PasswordDialogModel.class)); } private String getFileContent(DeploymentPropertiesDialogModel dialog) { IDocument doc = dialog.getFileDocument().getValue(); if (doc!=null) { return doc.get(); } return null; } public List<BootDashModel> getCfRunTargetModels() { return getRunTargetModels(cfTargetType); } public CloudFoundryRunTargetType getCfTargetType() { for (RunTargetType type : model.getRunTargetTypes()) { if (type instanceof CloudFoundryRunTargetType) { return (CloudFoundryRunTargetType) type; } } return null; } /** * Raw fetch of environment variables (makes a request through the CF client, rather then get the cached data * from the model). */ public Map<String, String> fetchEnvironment(CloudFoundryBootDashModel model, String appName) throws Exception { return model.getRunTarget().getClient().getApplication(appName).getEnvAsMap(); } public LocalBootDashModel getLocalModel() { return (LocalBootDashModel) getRunTargetModel(RunTargetTypes.LOCAL); } public void answerManifestDiffDialog(UserInteractions ui, Function<ManifestDiffDialogModel, ManifestDiffDialogModel.Result> answerer) throws Exception { when(ui.openManifestDiffDialog(any())) .thenAnswer(new Answer<ManifestDiffDialogModel.Result>() { @Override public ManifestDiffDialogModel.Result answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); ManifestDiffDialogModel dialog = (ManifestDiffDialogModel) args[0]; return answerer.apply(dialog); } }); } public SecuredCredentialsStore getCredentialsStore() { return context.getSecuredCredentialsStore(); } public IPropertyStore getPrivateStore() { return context.getPrivatePropertyStore(); } }