diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java index 1d8a70f7f..f791ba36e 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java @@ -14,7 +14,8 @@ import java.util.Collection; import java.util.Map; import java.util.UUID; -import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigProvider; +import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; +import org.springframework.ide.vscode.bosh.cloudconfig.DynamicModelProvider; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; @@ -78,9 +79,9 @@ public class BoshDeploymentManifestSchema implements YamlSchema { private YType t_release_name_ref; private YType t_var_name_def; private final ASTTypeCache astTypes; - private CloudConfigProvider cloudConfigProvider; + private DynamicModelProvider cloudConfigProvider; - public BoshDeploymentManifestSchema(ASTTypeCache astTypes, CloudConfigProvider cloudConfigProvider) { + public BoshDeploymentManifestSchema(ASTTypeCache astTypes, DynamicModelProvider cloudConfigProvider) { this.astTypes = astTypes; this.cloudConfigProvider = cloudConfigProvider; TYPE_UTIL = f.TYPE_UTIL; @@ -146,8 +147,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema { YAtomicType t_vm_extension = f.yatomic("VMExtension"); //TODO: resolve dynamically from 'cloud config' ? https://www.pivotaltracker.com/story/show/148703877 t_vm_extension.parseWith(ValueParsers.NE_STRING); - YAtomicType t_vm_type = f.yenumFromDynamicValues("VMType", (dc) -> cloudConfigProvider.getCloudConfig(dc).getVMTypes()); - t_vm_type.parseWith(ValueParsers.NE_STRING); + YAtomicType t_vm_type = f.yenumFromDynamicValues("VMType", (dc) -> cloudConfigProvider.getModel(dc).getVMTypes()); YAtomicType t_az = f.yatomic("AvailabilityZone"); //TODO: resolve dynamically from 'cloud config': https://www.pivotaltracker.com/story/show/148704481 t_az.parseWith(ValueParsers.NE_STRING); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java index 6441898cf..71a90e822 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java @@ -10,7 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; -import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigProvider; +import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; +import org.springframework.ide.vscode.bosh.cloudconfig.DynamicModelProvider; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; @@ -41,7 +42,7 @@ public class BoshLanguageServer extends SimpleLanguageServer { private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving private final VscodeCompletionEngineAdapter completionEngine; - public BoshLanguageServer(CloudConfigProvider cloudConfigProvider) { + public BoshLanguageServer(DynamicModelProvider cloudConfigProvider) { super("vscode-bosh"); YamlASTProvider parser = new YamlParser(yaml); SimpleTextDocumentService documents = getTextDocumentService(); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java index 68cfac007..dffd92ace 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java @@ -41,11 +41,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; * * @author Kris De Volder */ -public class BoshCommandCloudConfigProvider implements CloudConfigProvider { +public class BoshCommandCloudConfigProvider implements DynamicModelProvider { ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); final YamlParser yamlParser; + private Duration CMD_TIMEOUT = Duration.ofSeconds(10); + public BoshCommandCloudConfigProvider() { Representer representer = new Representer(); representer.getPropertyUtils().setSkipMissingProperties(true); @@ -75,7 +77,7 @@ public class BoshCommandCloudConfigProvider implements CloudConfigProvider { .thenValAt("name"); @Override - public CloudConfigModel getCloudConfig(DynamicSchemaContext dc) throws Exception { + public CloudConfigModel getModel(DynamicSchemaContext dc) throws Exception { String out = executeBoshCloudConfigCommand(); CloudConfigResponse response = mapper.readValue(out, CloudConfigResponse.class); String[] blocks = response.getBlocks(); @@ -99,9 +101,18 @@ public class BoshCommandCloudConfigProvider implements CloudConfigProvider { }; } + /** + * Configure how long we wait for the command to fetch cloud config before + * raising timeout exception. (The command may block for long amounts of time + * of the director is unreachable on the network). + */ + public void setCommandTimeout(Duration duration) { + this.CMD_TIMEOUT = duration; + } + protected String executeBoshCloudConfigCommand() throws Exception { ExternalCommand command = new ExternalCommand("bosh", "cloud-config", "--json"); - ExternalProcess process = new ExternalProcess(new File(".").getAbsoluteFile(), command, true, Duration.ofSeconds(30)); + ExternalProcess process = new ExternalProcess(new File(".").getAbsoluteFile(), command, true, CMD_TIMEOUT); System.out.println("executeBoshCloudConfigCommand: "+process); String out = process.getOut(); return out; diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/DynamicModelProvider.java similarity index 68% rename from headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigProvider.java rename to headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/DynamicModelProvider.java index 5d1a3c306..69ee914f1 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/DynamicModelProvider.java @@ -5,8 +5,6 @@ import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; /** * Responsible for somehow obtaining {@link CloudConfigModel} relative to a {@link DynamicSchemaContext} */ -public interface CloudConfigProvider { - - CloudConfigModel getCloudConfig(DynamicSchemaContext dc) throws Exception; - +public interface DynamicModelProvider { + T getModel(DynamicSchemaContext dc) throws Exception; } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java index 26d4728d3..71d336777 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java @@ -16,25 +16,14 @@ import org.junit.Test; import org.mockito.Mockito; import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; -import org.springframework.ide.vscode.commons.util.IOUtil; +import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; import com.google.common.collect.ImmutableMultiset; public class BoshCommandCloudConfigProviderTest { - private static final String MOCK_DATA_RSRC = "/cmd-out/cloud-config.json"; - - public static final BoshCommandCloudConfigProvider mockProvider = new BoshCommandCloudConfigProvider() { - /** - * Override with a 'fake' which just returns some mock data. That way we can unit-test - * without requiring a real bosh setup. - */ - @Override - protected String executeBoshCloudConfigCommand() throws Exception { - return IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)); - }; - }; + public final MockCloudConfigProvider mockProvider = new MockCloudConfigProvider(); // For local testing only... in CI builds we don't have the means to use a real bosh director and cli. // private BoshCommandCloudConfigProvider realProvider = new BoshCommandCloudConfigProvider(); @@ -42,7 +31,7 @@ public class BoshCommandCloudConfigProviderTest { @Test public void getVMTypes() throws Exception { BoshCommandCloudConfigProvider provider = mockProvider; DynamicSchemaContext dc = Mockito.mock(DynamicSchemaContext.class); - CloudConfigModel cloudConfig = provider.getCloudConfig(dc); + CloudConfigModel cloudConfig = provider.getModel(dc); assertEquals(ImmutableMultiset.of("default", "large"), cloudConfig.getVMTypes()); } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java index 0c49e1071..f025d5a9c 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java @@ -12,8 +12,11 @@ package org.springframework.ide.vscode.bosh; import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION; +import java.io.IOException; + import org.junit.Before; import org.junit.Test; +import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -23,9 +26,11 @@ public class BoshEditorTest { LanguageServerHarness harness; + private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider(); + @Before public void setup() throws Exception { harness = new LanguageServerHarness(() -> { - return new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider) + return new BoshLanguageServer(cloudConfigProvider) .setMaxCompletions(100); }, LanguageId.BOSH_DEPLOYMENT @@ -58,7 +63,7 @@ public class BoshEditorTest { " release: redis\n" + " properties:\n" + " port: 3606\n" + - " vm_type: medium\n" + + " vm_type: large\n" + " vm_extensions: [public-lbs]\n" + " stemcell: default\n" + " persistent_disk_type: medium\n" + @@ -71,9 +76,9 @@ public class BoshEditorTest { " - name: redis-server\n" + " release: redis\n" + " properties: {}\n" + - " vm_type: medium\n" + + " vm_type: large\n" + " stemcell: default\n" + - " persistent_disk_type: medium\n" + + " persistent_disk_type: large\n" + " networks:\n" + " - name: default\n" + "variables:\n" + @@ -116,6 +121,9 @@ public class BoshEditorTest { //@Ignore //For now... because not passing yet. @Test public void reconcileCfManifest() throws Exception { Editor editor = harness.newEditorFromClasspath("/workspace/cf-deployment-manifest.yml"); + cloudConfigProvider.readWith(() -> { + throw new IOException("Couldn't contact the director"); + }); editor.assertProblems(/*NONE*/); } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java index 83f89d4f6..c3eb682f1 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java @@ -8,7 +8,6 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.bosh; import static org.assertj.core.api.Assertions.assertThat; @@ -20,7 +19,7 @@ import java.nio.file.Paths; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.junit.Test; -import org.springframework.ide.vscode.bosh.BoshLanguageServer; +import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; public class BoshLanguageServerTest { @@ -32,7 +31,7 @@ public class BoshLanguageServerTest { @Test public void createAndInitializeServerWithWorkspace() throws Exception { LanguageServerHarness harness = new LanguageServerHarness(() -> - new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider) + new BoshLanguageServer(new MockCloudConfigProvider()) ); File workspaceRoot = getTestResource("/workspace/"); assertExpectedInitResult(harness.intialize(workspaceRoot)); @@ -42,7 +41,7 @@ public class BoshLanguageServerTest { public void createAndInitializeServerWithoutWorkspace() throws Exception { File workspaceRoot = null; LanguageServerHarness harness = new LanguageServerHarness(() -> - new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider) + new BoshLanguageServer(new MockCloudConfigProvider()) ); assertExpectedInitResult(harness.intialize(workspaceRoot)); } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java new file mode 100644 index 000000000..170570f58 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2017 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.vscode.bosh.mocks; + +import java.util.concurrent.Callable; + +import org.springframework.ide.vscode.bosh.BoshCommandCloudConfigProviderTest; +import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.commons.util.IOUtil; + +public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvider { + + static final String MOCK_DATA_RSRC = "/cmd-out/cloud-config.json"; + + private Callable cloudConfigReader = () -> IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)); + + /** + * Override with a 'fake' which just returns some mock data. That way we can unit-test + * without requiring a real bosh setup. + */ + @Override + protected String executeBoshCloudConfigCommand() throws Exception { + return cloudConfigReader.call(); + } + + public MockCloudConfigProvider readWith(Callable reader) { + this.cloudConfigReader = reader; + return this; + } +} \ No newline at end of file diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index 1145a5b53..036c31ea2 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -216,7 +216,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { } for (YValueHint value : values) { double score = FuzzyMatcher.matchScore(query, value.getValue()); - if (score!=0 && !value.equals(query)) { + if (score!=0 && value!=null && !query.equals(value.getValue())) { int queryStart = offset-query.length(); DocumentEdits edits = new DocumentEdits(doc.getDocument()); edits.delete(queryStart, offset); @@ -358,7 +358,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { } return null; } - + protected Collection getDashedCompletions(YamlDocument doc, SNode current, int offset) { try { YamlAssistContext relaxed = relaxForDashes();