From da37474d2e9ce3b829ccb24580a3fe481244bc84 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 10:51:53 -0700 Subject: [PATCH 1/7] Add a Dynamic 'Stemcells' model information provider In preparation of implementing dynamic CA and checking of stemcells block. --- .../bosh/BoshDeploymentManifestSchema.java | 13 ++- .../ide/vscode/bosh/BoshLanguageServer.java | 5 +- .../springframework/ide/vscode/bosh/Main.java | 5 +- .../models/BoshCommandBasedModelProvider.java | 94 +++++++++++++++++++ .../BoshCommandCloudConfigProvider.java | 62 ++---------- .../models/BoshCommandStemcellsProvider.java | 62 ++++++++++++ .../bosh/models/DynamicModelProvider.java | 3 +- .../ide/vscode/bosh/models/JSONCursor.java | 66 +++++++++++++ .../vscode/bosh/models/StemcellsModel.java | 22 +++++ .../BoshCommandCloudConfigProviderTest.java | 5 +- .../ide/vscode/bosh/BoshEditorTest.java | 20 +++- .../vscode/bosh/BoshLanguageServerTest.java | 6 +- .../bosh/mocks/MockCloudConfigProvider.java | 7 +- .../BoshCommandStemcellsProviderTest.java | 45 +++++++++ .../src/test/resources/cmd-out/stemcells.json | 38 ++++++++ .../vscode/commons/util/CollectorUtil.java | 37 +++++++- .../ide/vscode/commons/yaml/util/Streams.java | 6 +- 17 files changed, 423 insertions(+), 73 deletions(-) create mode 100644 headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java create mode 100644 headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProvider.java create mode 100644 headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/JSONCursor.java create mode 100644 headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/StemcellsModel.java create mode 100644 headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java create mode 100644 headless-services/bosh-language-server/src/test/resources/cmd-out/stemcells.json 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 880c5e0e4..297cbdfb6 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 @@ -19,6 +19,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.springframework.ide.vscode.bosh.models.CachingModelProvider; import org.springframework.ide.vscode.bosh.models.CloudConfigModel; import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; +import org.springframework.ide.vscode.bosh.models.StemcellsModel; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.CollectorUtil; import org.springframework.ide.vscode.commons.util.Renderable; @@ -84,11 +85,17 @@ public class BoshDeploymentManifestSchema implements YamlSchema { private YType t_var_name_def; private final ASTTypeCache astTypes; private DynamicModelProvider cloudConfigProvider; + private DynamicModelProvider stemcellsProvider; private List> defAndRefTypes; - public BoshDeploymentManifestSchema(ASTTypeCache astTypes, DynamicModelProvider cloudConfigProvider) { + public BoshDeploymentManifestSchema( + ASTTypeCache astTypes, + DynamicModelProvider cloudConfigProvider, + DynamicModelProvider stemcellsProvider + ) { this.astTypes = astTypes; this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider); + this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider); TYPE_UTIL = f.TYPE_UTIL; V2_TOPLEVEL_TYPE = createV2Schema(); @@ -228,7 +235,9 @@ public class BoshDeploymentManifestSchema implements YamlSchema { YBeanType t_variable = f.ybean("Variable"); addProp(t_variable, "name", t_var_name_def).isPrimary(true); - addProp(t_variable, "type", f.yenum("VariableType", "certificate", "password", "rsa", "ssh")).isRequired(true); + YType t_variable_type = f.yenum("VariableType", "certificate", "password", "rsa", "ssh") + .parseWith(ValueParsers.NE_STRING); //Overrid the parser -> no errors / warnings... in theory there could be other valid values. + addProp(t_variable, "type", t_variable_type).isRequired(true); addProp(t_variable, "options", t_params); addProp(v2Schema, "variables", f.yseq(t_variable)); 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 541970a8e..bb062ed63 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 @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.bosh; import org.springframework.ide.vscode.bosh.models.CloudConfigModel; import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; +import org.springframework.ide.vscode.bosh.models.StemcellsModel; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngine; @@ -37,13 +38,13 @@ public class BoshLanguageServer extends SimpleLanguageServer { private final VscodeCompletionEngineAdapter completionEngine; - public BoshLanguageServer(DynamicModelProvider cloudConfigProvider) { + public BoshLanguageServer(DynamicModelProvider cloudConfigProvider, DynamicModelProvider stemcellsProvider) { super("vscode-bosh"); YamlAstCache asts = new YamlAstCache(); SimpleTextDocumentService documents = getTextDocumentService(); ASTTypeCache astTypeCache = new ASTTypeCache(); - BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache, cloudConfigProvider); + BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache, cloudConfigProvider, stemcellsProvider); YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT; YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java index 7e1a1403d..ebcbe0bbc 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java @@ -17,6 +17,9 @@ import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; public class Main { public static void main(String[] args) throws IOException, InterruptedException { - LaunguageServerApp.start(() -> new BoshLanguageServer(new BoshCommandCloudConfigProvider())); + LaunguageServerApp.start(() -> new BoshLanguageServer( + new BoshCommandCloudConfigProvider(), + (dc) -> null //TODO: real model provider here! + )); } } diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java new file mode 100644 index 000000000..76cd3b31d --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * 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.models; + +import java.io.File; +import java.time.Duration; + +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.ExternalCommand; +import org.springframework.ide.vscode.commons.util.ExternalProcess; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; +import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.representer.Representer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Abstract base class to aid in implementing a Dynamic model provider that executes a bosh + * command (with `--json`) swtich and then extracts information from its json output. + */ +public abstract class BoshCommandBasedModelProvider implements DynamicModelProvider { + + private final YamlParser yamlParser; + protected final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + protected Duration CMD_TIMEOUT = Duration.ofSeconds(10); + + protected BoshCommandBasedModelProvider() { + Representer representer = new Representer(); + representer.getPropertyUtils().setSkipMissingProperties(true); + yamlParser = new YamlParser(new Yaml()); + } + + /** + * For deserializing the output from bosh cloud-config command. + */ + public static class BoshCommandResponse { + private String[] blocks; + + @JsonProperty("Blocks") + public String[] getBlocks() { + return blocks; + } + public void setBlocks(String[] blocks) { + this.blocks = blocks; + } + } + + protected String getBlock() throws Exception { + String out = executeCommand(getCommand()); + BoshCommandResponse response = mapper.readValue(out, BoshCommandResponse.class); + String[] blocks = response.getBlocks(); + Assert.isLegal(blocks!=null); + Assert.isLegal(blocks.length==1); + return blocks[0]; + } + + protected JsonNode getJsonTree() throws Exception { + String out = executeCommand(getCommand()); + return mapper.readTree(out); + } + + protected String executeCommand(ExternalCommand command) throws Exception { + ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, CMD_TIMEOUT); + String out = process.getOut(); + return out; + } + + protected File getWorkingDir() { + return new File(".").getAbsoluteFile(); + } + + protected abstract ExternalCommand getCommand(); + + protected YamlFileAST parseYaml(String block) throws Exception { + TextDocument doc = new TextDocument(null, LanguageId.BOSH_CLOUD_CONFIG); + doc.setText(block); + YamlFileAST ast = yamlParser.getAST(doc); + return ast; + } +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java index 0d2021751..095188251 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java @@ -10,30 +10,18 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh.models; -import java.io.File; import java.time.Duration; import java.util.Collection; import java.util.stream.Stream; -import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.CollectorUtil; import org.springframework.ide.vscode.commons.util.ExternalCommand; -import org.springframework.ide.vscode.commons.util.ExternalProcess; import org.springframework.ide.vscode.commons.util.StringUtil; -import org.springframework.ide.vscode.commons.util.text.LanguageId; -import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; -import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.representer.Representer; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; /** * Concrete implementation of {@link CloudConfigProvider} that runs `bosh cloud-config` command @@ -41,33 +29,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; * * @author Kris De Volder */ -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 class BoshCommandCloudConfigProvider extends BoshCommandBasedModelProvider { public BoshCommandCloudConfigProvider() { - Representer representer = new Representer(); - representer.getPropertyUtils().setSkipMissingProperties(true); - yamlParser = new YamlParser(new Yaml()); - } - - /** - * For deserializing the output from bosh cloud-config command. - */ - public static class CloudConfigResponse { - private String[] blocks; - - @JsonProperty("Blocks") - public String[] getBlocks() { - return blocks; - } - - public void setBlocks(String[] blocks) { - this.blocks = blocks; - } } private static final YamlTraversal VM_TYPE_NAMES = YamlPath.EMPTY @@ -98,10 +62,8 @@ public class BoshCommandCloudConfigProvider implements DynamicModelProvider getVMTypes() { @@ -141,15 +103,6 @@ public class BoshCommandCloudConfigProvider implements DynamicModelProvider { + + YamlTraversal STEMCELL_NAMES = YamlPath.EMPTY + .thenValAt("Tables") + .thenAnyChild() + .thenValAt("Rows") + .thenAnyChild() + .thenValAt("name"); + + @Override + public StemcellsModel getModel(DynamicSchemaContext dc) throws Exception { + JSONCursor cursor = new JSONCursor(getJsonTree()); + return new StemcellsModel() { + + @Override + public Collection getStemcellNames() { + return getNames(STEMCELL_NAMES); + } + + private Collection getNames(YamlTraversal path) { + return path.traverseAmbiguously(cursor) + .flatMap((cursor) -> { + String text = cursor.target.asText(); + if (StringUtil.hasText(text)) { + return Stream.of(text); + } else { + return Stream.empty(); + } + }) + .collect(CollectorUtil.toImmutableSet()); + } + }; + } + + @Override + protected ExternalCommand getCommand() { + return new ExternalCommand("bosh", "stemcells", "--json"); + } + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java index 27c817576..171d0fe24 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java @@ -3,7 +3,8 @@ package org.springframework.ide.vscode.bosh.models; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; /** - * Responsible for somehow obtaining {@link CloudConfigModel} relative to a {@link DynamicSchemaContext} + * Responsible for somehow obtaining some type of `dynamic` model information relative + * to a {@link DynamicSchemaContext} */ public interface DynamicModelProvider { T getModel(DynamicSchemaContext dc) throws Exception; diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/JSONCursor.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/JSONCursor.java new file mode 100644 index 000000000..30ee9213b --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/JSONCursor.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.models; + +import java.util.stream.Stream; + +import org.springframework.ide.vscode.commons.yaml.path.YamlNavigable; +import org.springframework.ide.vscode.commons.yaml.path.YamlPath; +import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; +import org.springframework.ide.vscode.commons.yaml.util.Streams; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Allows using {@link YamlPath} / {@link YamlNavigable} on {@link JsonNode}s + */ +public class JSONCursor implements YamlNavigable { + + public final JsonNode target; + + public JSONCursor(JsonNode target) { + super(); + this.target = target; + } + + @Override + public Stream traverseAmbiguously(YamlPathSegment s) { + return oneStep(s).map(JSONCursor::new); + } + + private Stream oneStep(YamlPathSegment s) { + if (target==null) { + return Stream.empty(); + } + switch (s.getType()) { + case KEY_AT_KEY: { + return Streams.fromNullable(target.get(s.toPropString())); + } + case ANY_CHILD: { + return Streams.fromIterable(target); + } + case VAL_AT_INDEX: { + return Streams.fromNullable(target.get(s.toIndex())); + } + case VAL_AT_KEY: { + return Streams.fromNullable(target.get(s.toPropString())); + } + default: + throw new IllegalStateException("Missing case for "+s.getType()); + } + } + + @Override + public String toString() { + return ""+target; + } + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/StemcellsModel.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/StemcellsModel.java new file mode 100644 index 000000000..22ed422b1 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/StemcellsModel.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * 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.models; + +import java.util.Collection; + +/** + * Represents Stemcells information as might be retrieved from bosh director. + */ +public interface StemcellsModel { + + Collection getStemcellNames(); + +} 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 ecc3c5667..a0a551059 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 @@ -15,6 +15,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import org.mockito.Mockito; import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.BoshCommandBasedModelProvider; import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.bosh.models.CloudConfigModel; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; @@ -23,13 +24,13 @@ import com.google.common.collect.ImmutableMultiset; public class BoshCommandCloudConfigProviderTest { - public final MockCloudConfigProvider mockProvider = new MockCloudConfigProvider(); + public final BoshCommandCloudConfigProvider 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(); @Test public void getVMTypes() throws Exception { - BoshCommandCloudConfigProvider provider = mockProvider; + BoshCommandBasedModelProvider provider = mockProvider; DynamicSchemaContext dc = Mockito.mock(DynamicSchemaContext.class); CloudConfigModel cloudConfig = provider.getModel(dc); 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 3acb9bba7..30de9531d 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 @@ -20,7 +20,10 @@ import java.util.concurrent.TimeoutException; import org.eclipse.lsp4j.CompletionItem; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; +import org.springframework.ide.vscode.bosh.models.StemcellsModel; 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; @@ -31,10 +34,11 @@ public class BoshEditorTest { LanguageServerHarness harness; private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider(); + private DynamicModelProvider stemcellsProvider = Mockito.mock(DynamicModelProvider.class); @Before public void setup() throws Exception { harness = new LanguageServerHarness(() -> { - return new BoshLanguageServer(cloudConfigProvider) + return new BoshLanguageServer(cloudConfigProvider, stemcellsProvider) .setMaxCompletions(100); }, LanguageId.BOSH_DEPLOYMENT @@ -561,6 +565,20 @@ public class BoshEditorTest { editor.assertCompletionLabels("certificate", "password", "rsa", "ssh"); } + @Test public void variablesBlockReconciling() throws Exception { + Editor editor = harness.newEditor( + "variables:\n" + + "- name: admin-passcode\n" + + " type: something-that-might-work-in-theory\n" + //shouldn't be a warning/error + " bogus-propt: bah\n" + + " options: {}" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "bogus-propt|Unknown property" + ); + } + @Test public void variablesBlockHovers() throws Exception { Editor editor = harness.newEditor( "variables:\n" + 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 c3eb682f1..c30d1195f 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 @@ -19,7 +19,9 @@ import java.nio.file.Paths; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; public class BoshLanguageServerTest { @@ -31,7 +33,7 @@ public class BoshLanguageServerTest { @Test public void createAndInitializeServerWithWorkspace() throws Exception { LanguageServerHarness harness = new LanguageServerHarness(() -> - new BoshLanguageServer(new MockCloudConfigProvider()) + new BoshLanguageServer(new MockCloudConfigProvider(), Mockito.mock(DynamicModelProvider.class)) ); File workspaceRoot = getTestResource("/workspace/"); assertExpectedInitResult(harness.intialize(workspaceRoot)); @@ -41,7 +43,7 @@ public class BoshLanguageServerTest { public void createAndInitializeServerWithoutWorkspace() throws Exception { File workspaceRoot = null; LanguageServerHarness harness = new LanguageServerHarness(() -> - new BoshLanguageServer(new MockCloudConfigProvider()) + new BoshLanguageServer(new MockCloudConfigProvider(), Mockito.mock(DynamicModelProvider.class)) ); 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 index 99a28b3a0..02e96ea39 100644 --- 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 @@ -14,6 +14,7 @@ import java.util.concurrent.Callable; import org.springframework.ide.vscode.bosh.BoshCommandCloudConfigProviderTest; import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.IOUtil; public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvider { @@ -33,15 +34,15 @@ public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvide * without requiring a real bosh setup. */ @Override - protected String executeBoshCloudConfigCommand() throws Exception { + protected String executeCommand(ExternalCommand cmd) throws Exception { readCount++; return cloudConfigCmdExecutor.call(); } @Override - protected String getCloudConfigBlock() throws Exception { + protected String getBlock() throws Exception { if (reader==null) { - return super.getCloudConfigBlock(); + return super.getBlock(); } readCount++; return reader.call(); diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java new file mode 100644 index 000000000..3288cd572 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.models; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.ide.vscode.bosh.BoshCommandCloudConfigProviderTest; +import org.springframework.ide.vscode.commons.util.IOUtil; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +import com.google.common.collect.ImmutableSet; + +public class BoshCommandStemcellsProviderTest { + + private static final String MOCK_DATA_RSRC = "/cmd-out/stemcells.json";; + public BoshCommandStemcellsProvider provider = Mockito.spy(BoshCommandStemcellsProvider.class); + + @Before + public void settup() throws Exception { + Mockito.doReturn(IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC))) + .when(provider).executeCommand(Mockito.any()); + } + + @Test public void getStemcellNames() throws Exception { + assertEquals(ImmutableSet.of( + "bosh-vsphere-esxi-centos-7-go_agent", + "bosh-vsphere-esxi-ubuntu-trusty-go_agent" + ), + provider.getModel(Mockito.mock(DynamicSchemaContext.class)) + .getStemcellNames() + ); + } + +} diff --git a/headless-services/bosh-language-server/src/test/resources/cmd-out/stemcells.json b/headless-services/bosh-language-server/src/test/resources/cmd-out/stemcells.json new file mode 100644 index 000000000..be1e5d053 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/resources/cmd-out/stemcells.json @@ -0,0 +1,38 @@ +{ + "Tables": [ + { + "Content": "stemcells", + "Header": { + "cid": "CID", + "cpi": "CPI", + "name": "Name", + "os": "OS", + "version": "Version" + }, + "Rows": [ + { + "cid": "sc-cd694110-4b23-4361-b5e7-b817874eadca", + "cpi": "", + "name": "bosh-vsphere-esxi-centos-7-go_agent", + "os": "centos-7", + "version": "3421.11" + }, + { + "cid": "sc-3378b7e1-5a80-4351-b521-7e9aeeb417bc", + "cpi": "", + "name": "bosh-vsphere-esxi-ubuntu-trusty-go_agent", + "os": "ubuntu-trusty", + "version": "3421.11" + } + ], + "Notes": [ + "(*) Currently deployed" + ] + } + ], + "Blocks": null, + "Lines": [ + "Using environment '10.194.4.35' as client 'admin'", + "Succeeded" + ] +} \ No newline at end of file diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/CollectorUtil.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/CollectorUtil.java index aee5ef083..8d9db24ed 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/CollectorUtil.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/CollectorUtil.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.commons.util; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import java.util.function.BiConsumer; @@ -62,7 +63,7 @@ public class CollectorUtil { @Override public Set characteristics() { - return ImmutableSet.of(Collector.Characteristics.UNORDERED); + return EnumSet.of(Collector.Characteristics.UNORDERED); } }; } @@ -94,7 +95,39 @@ public class CollectorUtil { @Override public Set characteristics() { - return ImmutableSet.of(Collector.Characteristics.UNORDERED); + return EnumSet.of(Collector.Characteristics.UNORDERED); + } + }; + } + + public static Collector, ImmutableSet> toImmutableSet() { + return new Collector, ImmutableSet>() { + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, T> accumulator() { + return (a, e) -> a.add(e); + } + + @Override + public BinaryOperator> combiner() { + return (a1, a2) -> { + a1.addAll(a2); + return a1; + }; + } + @Override + public Function, ImmutableSet> finisher() { + return ImmutableSet::copyOf; + } + + @Override + public Set characteristics() { + return EnumSet.of(Collector.Characteristics.UNORDERED); } }; } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java index f02993420..077b0cb6f 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/Streams.java @@ -8,12 +8,12 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.commons.yaml.util; import java.util.ArrayList; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; public class Streams { @@ -35,4 +35,8 @@ public class Streams { return e==null ? Stream.empty() : Stream.of(e); } + public static Stream fromIterable(Iterable target) { + return StreamSupport.stream(target.spliterator(), false); + } + } From dc07bbf32aa9a8db835e2f88bd9135890a403523 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 11:47:40 -0700 Subject: [PATCH 2/7] Missing sha1 error demoted to a warning --- .../ide/vscode/bosh/BoshConstraints.java | 6 +++--- .../ide/vscode/bosh/BoshSchemaProblems.java | 21 +++++++++++++++++++ .../ide/vscode/bosh/BoshEditorTest.java | 7 ++++++- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshSchemaProblems.java diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java index 5c80ef16d..bb3e226ca 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; -import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.MISSING_PROPERTY; +import static org.springframework.ide.vscode.bosh.BoshSchemaProblems.MISSING_SHA1_PROPERTY; import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.problem; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; @@ -24,7 +24,7 @@ import org.yaml.snakeyaml.nodes.NodeTuple; public class BoshConstraints { public static final Constraint SHA1_REQUIRED_FOR_HTTP_URL = new Constraint() { - + @Override public void verify(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) { NodeTuple urlProp = NodeUtil.getPropertyTuple(node, "url"); @@ -33,7 +33,7 @@ public class BoshConstraints { if (url!=null && url.startsWith("http")) { Node sha1 = NodeUtil.getProperty(node, "sha1"); if (sha1==null) { - problems.accept(problem(MISSING_PROPERTY, "'sha1' is required when the 'url' is http(s)", urlProp.getKeyNode())); + problems.accept(problem(MISSING_SHA1_PROPERTY, "'sha1' is recommended when the 'url' is http(s)", urlProp.getKeyNode())); } } } diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshSchemaProblems.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshSchemaProblems.java new file mode 100644 index 000000000..489a241ca --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshSchemaProblems.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * 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; + +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; +import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; + +public class BoshSchemaProblems { + + public static final ProblemType MISSING_SHA1_PROPERTY = YamlSchemaProblems.problemType("MISSING_SHA1_PROPERTY", ProblemSeverity.WARNING); + +} 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 30de9531d..eaf89436d 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 @@ -18,6 +18,8 @@ import java.io.IOException; import java.util.concurrent.TimeoutException; import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -276,10 +278,13 @@ public class BoshEditorTest { "#x" ); editor.assertProblems( - "url|'sha1' is required when the 'url' is http(s)", + "url|'sha1' is recommended when the 'url' is http(s)", "proto|Url scheme must be one of [http, https, file]", "x|are required" ); + Diagnostic missingSha1Problem = editor.assertProblem("url"); + assertContains("'sha1' is recommended", missingSha1Problem.getMessage()); + assertEquals(DiagnosticSeverity.Warning, missingSha1Problem.getSeverity()); } @Test public void releasesBlockPropertyReconcileAndHovers() throws Exception { From ebcd3f41cbb75a4e78bac00442390848631aeadc Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 12:09:07 -0700 Subject: [PATCH 3/7] Temporarily disable dynamic cloud-config features in bosh editor --- .../main/java/org/springframework/ide/vscode/bosh/Main.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java index ebcbe0bbc..96aba5a0e 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java @@ -12,13 +12,12 @@ package org.springframework.ide.vscode.bosh; import java.io.IOException; -import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; public class Main { public static void main(String[] args) throws IOException, InterruptedException { LaunguageServerApp.start(() -> new BoshLanguageServer( - new BoshCommandCloudConfigProvider(), + (dc) -> null, //TODO: temporarily disabled.. reenable: new BoshCommandCloudConfigProvider(), (dc) -> null //TODO: real model provider here! )); } From e40b81bc872e16e8c265bd35484f2c2c1b3bd072 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 12:17:31 -0700 Subject: [PATCH 4/7] Revert "Temporarily disable dynamic cloud-config features in bosh editor" This reverts commit ebcd3f41cbb75a4e78bac00442390848631aeadc. --- .../main/java/org/springframework/ide/vscode/bosh/Main.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java index 96aba5a0e..ebcbe0bbc 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java @@ -12,12 +12,13 @@ package org.springframework.ide.vscode.bosh; import java.io.IOException; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; public class Main { public static void main(String[] args) throws IOException, InterruptedException { LaunguageServerApp.start(() -> new BoshLanguageServer( - (dc) -> null, //TODO: temporarily disabled.. reenable: new BoshCommandCloudConfigProvider(), + new BoshCommandCloudConfigProvider(), (dc) -> null //TODO: real model provider here! )); } From 8b71803a80718330dc7693068e460ddaa9c5cc77 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 13:39:19 -0700 Subject: [PATCH 5/7] Cleanup: messy use of mixed spaces and tabs --- .../ide/vscode/manifest/yaml/Main.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java index 96a424b8b..199165ab5 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java @@ -17,16 +17,16 @@ import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; public class Main { - SimpleLanguageServer server = new ManifestYamlLanguageServer(); + SimpleLanguageServer server = new ManifestYamlLanguageServer(); - public static void main(String[] args) throws IOException, InterruptedException { - File logfile = null; - if (System.getProperty("org.slf4j.simpleLogger.logFile") == null) { - logfile = File.createTempFile("manifest-yaml-language-server", ".log"); - System.setProperty("org.slf4j.simpleLogger.logFile", logfile.toString()); - } else { - logfile = new File(System.getProperty("org.slf4j.simpleLogger.logFile")); - } + public static void main(String[] args) throws IOException, InterruptedException { + File logfile = null; + if (System.getProperty("org.slf4j.simpleLogger.logFile") == null) { + logfile = File.createTempFile("manifest-yaml-language-server", ".log"); + System.setProperty("org.slf4j.simpleLogger.logFile", logfile.toString()); + } else { + logfile = new File(System.getProperty("org.slf4j.simpleLogger.logFile")); + } System.err.println("Redirecting log output to: "+logfile); LaunguageServerApp.start(ManifestYamlLanguageServer::new); } From 2b0829e1f6fee54fb78710d8dc78883fb7c881ab Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 14:07:29 -0700 Subject: [PATCH 6/7] Add some logging around bosh cmd exec, trying to see why it is taking so long! --- .../ide/vscode/bosh/models/BoshCommandBasedModelProvider.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java index 76cd3b31d..4b8c899e6 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java @@ -16,6 +16,7 @@ import java.time.Duration; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.ExternalProcess; +import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; @@ -74,7 +75,9 @@ public abstract class BoshCommandBasedModelProvider implements DynamicModelPr } protected String executeCommand(ExternalCommand command) throws Exception { + Log.info("executing cmd: "+command); ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, CMD_TIMEOUT); + Log.info("executing cmd DONE: "+process); String out = process.getOut(); return out; } From 962805f9497201f1c254a41a3297727a563ecf9d Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 21 Jul 2017 17:13:41 -0700 Subject: [PATCH 7/7] Fix issue with CachinhModelProvider not caching 'failed' results Also tweak some timeout values to try to make dynamic CA and reconcile more responsive. --- .../.settings/org.eclipse.jdt.ui.prefs | 2 +- .../models/BoshCommandBasedModelProvider.java | 15 +++-- .../bosh/models/CachingModelProvider.java | 21 +++++-- .../bosh/mocks/MockCloudConfigProvider.java | 2 +- .../BoshCommandCloudConfigProviderTest.java | 2 +- .../BoshCommandStemcellsProviderTest.java | 1 - .../bosh/models/CachingModelProviderTest.java | 56 +++++++++++++++++++ 7 files changed, 86 insertions(+), 13 deletions(-) rename headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/{ => models}/BoshCommandCloudConfigProviderTest.java (97%) create mode 100644 headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java diff --git a/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs b/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs index 75f829439..68f26e829 100644 --- a/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs +++ b/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs @@ -27,7 +27,7 @@ sp_cleanup.make_variable_declarations_final=false sp_cleanup.never_use_blocks=false sp_cleanup.never_use_parentheses_in_expressions=true sp_cleanup.on_save_use_additional_actions=true -sp_cleanup.organize_imports=true +sp_cleanup.organize_imports=false sp_cleanup.qualify_static_field_accesses_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java index 4b8c899e6..d6227aaa9 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandBasedModelProvider.java @@ -37,7 +37,7 @@ public abstract class BoshCommandBasedModelProvider implements DynamicModelPr private final YamlParser yamlParser; protected final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - protected Duration CMD_TIMEOUT = Duration.ofSeconds(10); + protected Duration CMD_TIMEOUT = Duration.ofSeconds(3); protected BoshCommandBasedModelProvider() { Representer representer = new Representer(); @@ -76,10 +76,15 @@ public abstract class BoshCommandBasedModelProvider implements DynamicModelPr protected String executeCommand(ExternalCommand command) throws Exception { Log.info("executing cmd: "+command); - ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, CMD_TIMEOUT); - Log.info("executing cmd DONE: "+process); - String out = process.getOut(); - return out; + try { + ExternalProcess process = new ExternalProcess(getWorkingDir(), command, true, CMD_TIMEOUT); + Log.info("executing cmd SUCCESS: "+process); + String out = process.getOut(); + return out; + } catch (Exception e) { + Log.log("executing cmd FAILED", e); + throw e; + } } protected File getWorkingDir() { diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java index c843daed7..964d93814 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh.models; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -29,9 +30,9 @@ public class CachingModelProvider implements DynamicModelProvider { */ private static final Object NULL_KEY = new Object(); - private long timeout = 15; + private long timeout = 30; private TimeUnit timeoutUnit = TimeUnit.SECONDS; - private Cache cache = createCache(); + private Cache> cache = createCache(); private final DynamicModelProvider delegate; @@ -48,7 +49,7 @@ public class CachingModelProvider implements DynamicModelProvider { */ private Function keyGetter = (dc) -> "WHATEVER"; - protected Cache createCache() { + protected Cache> createCache() { return CacheBuilder.newBuilder() .expireAfterWrite(timeout, timeoutUnit) .build(); @@ -67,7 +68,19 @@ public class CachingModelProvider implements DynamicModelProvider { //guava cache doesn't like null key key = NULL_KEY; } - return cache.get(key, () -> delegate.getModel(dc)); + CompletableFuture cached; + synchronized (this) { + cached = cache.get(key, () -> { + try { + return CompletableFuture.completedFuture(delegate.getModel(dc)); + } catch (Throwable e) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(e); + return failed; + } + }); + } + return cached.get(); } } 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 index 02e96ea39..e70e76fd2 100644 --- 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 @@ -12,8 +12,8 @@ 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.models.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProviderTest; import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.IOUtil; 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/models/BoshCommandCloudConfigProviderTest.java similarity index 97% rename from headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java rename to headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProviderTest.java index a0a551059..04c14df5f 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/models/BoshCommandCloudConfigProviderTest.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.bosh; +package org.springframework.ide.vscode.bosh.models; import static org.junit.Assert.assertEquals; diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java index 3288cd572..5d0b48a17 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/BoshCommandStemcellsProviderTest.java @@ -15,7 +15,6 @@ import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import org.springframework.ide.vscode.bosh.BoshCommandCloudConfigProviderTest; import org.springframework.ide.vscode.commons.util.IOUtil; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java new file mode 100644 index 000000000..86d7ff2da --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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.models; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.junit.Test; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; + +@SuppressWarnings("unchecked") +public class CachingModelProviderTest { + + @Test public void goodValuesAreCached() throws Exception { + DynamicModelProvider modelProvider = mock(DynamicModelProvider.class); + when(modelProvider.getModel(any())).thenReturn("RESULT"); + + DynamicModelProvider cached = new CachingModelProvider<>(modelProvider); + + assertEquals("RESULT", cached.getModel(null)); + assertEquals("RESULT", cached.getModel(null)); + assertEquals("RESULT", cached.getModel(null)); + + verify(modelProvider, times(1)).getModel(any()); + } + + @Test public void timeoutExceptionsAreCached() throws Exception { + DynamicModelProvider modelProvider = mock(DynamicModelProvider.class); + when(modelProvider.getModel(any())).thenThrow(new TimeoutException("timed out")); + + DynamicModelProvider cached = new CachingModelProvider<>(modelProvider); + for (int i = 0; i < 3; i++) { + try { + cached.getModel(null); + fail("Should have thrown"); + } catch (Exception _e) { + Throwable e = ExceptionUtil.getDeepestCause(_e); + assertEquals(TimeoutException.class, e.getClass()); + } + } + verify(modelProvider, times(1)).getModel(any()); + } + +}