Create CloudConfigProvider and wire it up...

... in preparation for implementing various stories that depend on this
infrastructure.
This commit is contained in:
Kris De Volder
2017-07-18 15:52:34 -07:00
parent eb440634b4
commit cecd98f7a5
19 changed files with 279 additions and 31 deletions

View File

@@ -45,6 +45,18 @@
<version>${dependencies.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>language-server-test-harness</artifactId>
<version>${dependencies.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@@ -14,6 +14,7 @@ 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.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
@@ -77,9 +78,11 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
private YType t_release_name_ref;
private YType t_var_name_def;
private final ASTTypeCache astTypes;
private CloudConfigProvider cloudConfigProvider;
public BoshDeploymentManifestSchema(ASTTypeCache astTypes) {
public BoshDeploymentManifestSchema(ASTTypeCache astTypes, CloudConfigProvider cloudConfigProvider) {
this.astTypes = astTypes;
this.cloudConfigProvider = cloudConfigProvider;
TYPE_UTIL = f.TYPE_UTIL;
V2_TOPLEVEL_TYPE = createV2Schema();

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.bosh;
import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigProvider;
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;
@@ -40,12 +41,12 @@ public class BoshLanguageServer extends SimpleLanguageServer {
private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving
private final VscodeCompletionEngineAdapter completionEngine;
public BoshLanguageServer() {
public BoshLanguageServer(CloudConfigProvider cloudConfigProvider) {
super("vscode-bosh");
YamlASTProvider parser = new YamlParser(yaml);
SimpleTextDocumentService documents = getTextDocumentService();
ASTTypeCache astTypeCache = new ASTTypeCache();
BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache);
BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache, cloudConfigProvider);
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);

View File

@@ -8,16 +8,15 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.bosh;
import java.io.IOException;
import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider;
import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
LaunguageServerApp.start(() -> new BoshLanguageServer());
LaunguageServerApp.start(() -> new BoshLanguageServer(new BoshCommandCloudConfigProvider()));
}
}

View File

@@ -0,0 +1,110 @@
/*******************************************************************************
* 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.cloudconfig;
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
* with bosh cli and parses the output.
*
* @author Kris De Volder
*/
public class BoshCommandCloudConfigProvider implements CloudConfigProvider {
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final YamlParser yamlParser;
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;
}
}
YamlTraversal VM_TYPE_NAMES = YamlPath.EMPTY
.thenAnyChild()
.thenValAt("vm_types")
.thenAnyChild()
.thenValAt("name");
@Override
public CloudConfigModel getCloudConfig(DynamicSchemaContext dc) throws Exception {
String out = executeBoshCloudConfigCommand();
CloudConfigResponse response = mapper.readValue(out, CloudConfigResponse.class);
String[] blocks = response.getBlocks();
Assert.isLegal(blocks!=null);
Assert.isLegal(blocks.length==1);
TextDocument doc = new TextDocument(null, LanguageId.BOSH_CLOUD_CONFIG);
doc.setText(blocks[0]);
YamlFileAST ast = yamlParser.getAST(doc);
return new CloudConfigModel() {
@Override
public Collection<String> getVMTypes() {
return VM_TYPE_NAMES.traverseAmbiguously(ast)
.flatMap(nameNode -> {
String name = NodeUtil.asScalar(nameNode);
return StringUtil.hasText(name)
? Stream.of(name)
: Stream.empty();
})
.collect(CollectorUtil.toMultiset());
}
};
}
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));
System.out.println("executeBoshCloudConfigCommand: "+process);
String out = process.getOut();
return out;
}
}

View File

@@ -0,0 +1,20 @@
/*******************************************************************************
* 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.cloudconfig;
import java.util.Collection;
/**
* Represents CloudConfig information as might be retrieved from bosh director.
*/
public interface CloudConfigModel {
Collection<String> getVMTypes();
}

View File

@@ -0,0 +1,12 @@
package org.springframework.ide.vscode.bosh.cloudconfig;
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;
}

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* 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 static org.junit.Assert.assertEquals;
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.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));
};
};
// 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;
DynamicSchemaContext dc = Mockito.mock(DynamicSchemaContext.class);
CloudConfigModel cloudConfig = provider.getCloudConfig(dc);
assertEquals(ImmutableMultiset.of("default", "large"), cloudConfig.getVMTypes());
}
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
package org.springframework.ide.vscode.bosh;
import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION;
@@ -26,7 +26,7 @@ public class BoshEditorTest {
@Before public void setup() throws Exception {
harness = new LanguageServerHarness(() -> {
return new BoshLanguageServer()
return new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider)
.setMaxCompletions(100);
},
LanguageId.BOSH_DEPLOYMENT

View File

@@ -9,7 +9,7 @@
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
package org.springframework.ide.vscode.bosh;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,7 +31,9 @@ public class BoshLanguageServerTest {
@Test
public void createAndInitializeServerWithWorkspace() throws Exception {
LanguageServerHarness harness = new LanguageServerHarness(() -> new BoshLanguageServer());
LanguageServerHarness harness = new LanguageServerHarness(() ->
new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider)
);
File workspaceRoot = getTestResource("/workspace/");
assertExpectedInitResult(harness.intialize(workspaceRoot));
}
@@ -39,7 +41,9 @@ public class BoshLanguageServerTest {
@Test
public void createAndInitializeServerWithoutWorkspace() throws Exception {
File workspaceRoot = null;
LanguageServerHarness harness = new LanguageServerHarness(() -> new BoshLanguageServer());
LanguageServerHarness harness = new LanguageServerHarness(() ->
new BoshLanguageServer(BoshCommandCloudConfigProviderTest.mockProvider)
);
assertExpectedInitResult(harness.intialize(workspaceRoot));
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
package org.springframework.ide.vscode.bosh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

View File

@@ -0,0 +1,10 @@
{
"Tables": null,
"Blocks": [
"azs:\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z1\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z2\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z3\ncompilation:\n az: z1\n network: default\n reuse_compilation_vms: true\n vm_type: default\n workers: 5\ndisk_types:\n- disk_size: 3000\n name: default\n- disk_size: 50000\n name: large\nnetworks:\n- name: default\n subnets:\n - azs:\n - z1\n - z2\n - z3\n cloud_properties:\n name: VLAN 40 - AF\n dns:\n - 10.192.2.10\n - 8.8.8.8\n gateway: 10.194.4.1\n range: 10.194.4.0/23\n reserved:\n - 10.194.4.1-10.194.4.34\n - 10.194.4.40-10.194.5.255\n type: manual\nvm_types:\n- cloud_properties:\n cpu: 2\n disk: 3240\n ram: 1024\n name: default\n- cloud_properties:\n cpu: 2\n disk: 30240\n ram: 4096\n name: large\n"
],
"Lines": [
"Using environment '10.194.4.35' as client 'admin'",
"Succeeded"
]
}

View File

@@ -32,7 +32,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<version>${mockito-version}</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -15,6 +15,7 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.ExternalProcess;
@@ -28,7 +29,7 @@ public class MavenBuilder {
private List<String> properties;
public void execute() throws IOException, InterruptedException {
public void execute() throws IOException, InterruptedException, TimeoutException {
Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") ? projectPath.resolve("mvnw.cmd")
: projectPath.resolve("mvnw");
mvnwPath.toFile().setExecutable(true);

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeoutException;
/**
* Encapsulates information about an 'external' command that can be run through the OS.
@@ -68,7 +69,7 @@ public class ExternalCommand {
* result of commands are logged to the console and if the command returns non
* 0 exit value an exception is thrown.
*/
public void exec(File workdir) throws IOException, InterruptedException {
public void exec(File workdir) throws IOException, InterruptedException, TimeoutException {
System.out.println(">>> exec: "+this);
ExternalProcess process = new ExternalProcess(workdir, this);
System.out.println(process);

View File

@@ -16,6 +16,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
@@ -113,13 +116,14 @@ public class ExternalProcess {
* Creates an external process and waits for it to terminate. The output and error streams
* will be read and forwarded to System.out and System.err
*/
public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException {
this(workingDir, cmd, false);
public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException, TimeoutException {
this(workingDir, cmd, false, null);
}
private void init(File workingDir, ExternalCommand cmd,
OutputStream outStream, OutputStream errStream) throws IOException,
InterruptedException {
OutputStream outStream, OutputStream errStream,
Duration timeout) throws IOException,
InterruptedException, TimeoutException {
this.cmd = cmd;
ProcessBuilder processBuilder = new ProcessBuilder(cmd.getProgramAndArgs());
processBuilder.directory(workingDir);
@@ -127,15 +131,33 @@ public class ExternalProcess {
process = processBuilder.start();
err = new StreamGobler(process.getErrorStream(), errStream);
out = new StreamGobler(process.getInputStream(), outStream);
exitValue = process.waitFor();
}
public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException {
if (captureStreams) {
init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream());
if (timeout==null) {
exitValue = process.waitFor();
} else {
init(workingDir, cmd, System.out, System.err);
if (process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
exitValue = process.exitValue();
} else {
process.destroy();
exitValue = 999; //Set some non-0 value as that is what some callers might use to determine 'failure' occurred.
throw new TimeoutException("Command timed out: "+this);
}
}
if (exitValue!=0) {
throw new IOException("Command execution failed:\n"+this);
}
}
public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams, Duration timeout) throws IOException, InterruptedException, TimeoutException {
if (captureStreams) {
init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream(), timeout);
} else {
init(workingDir, cmd, System.out, System.err, timeout);
}
}
public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException, TimeoutException {
this(workingDir, cmd, captureStreams, null);
}
public String getOut() throws InterruptedException {
@@ -156,13 +178,13 @@ public class ExternalProcess {
result.append("exitValue = "+exitValue+"\n");
String strOut = getOut();
if (strOut!=null) {
result.append("\n------- System.out -------\n");
result.append("------- System.out -------\n");
result.append(strOut);
}
String strErr = getErr();
if (strErr!=null) {
result.append("\n------- System.err -------\n");
result.append(getOut());
result.append("------- System.err -------\n");
result.append(strErr);
}
result.append("<<<< ExternalProcess");
return result.toString();

View File

@@ -21,10 +21,12 @@ public class LanguageId {
public static final LanguageId PLAINTEXT = of("plaintext");
public static final LanguageId CONCOURSE_TASK = of("concourse-task-yaml");
public static final LanguageId CONCOURSE_PIPELINE = of("concourse-pipeline-yaml");
public static final LanguageId BOSH_DEPLOYMENT = of("bosh-deployment-manifest");
public static final LanguageId CF_MANIFEST = of("manifest-yaml");
public static final LanguageId JAVA = of("java");
public static final LanguageId YAML = of("yaml");
public static final LanguageId BOSH_DEPLOYMENT = of("bosh-deployment-manifest");
public static final LanguageId BOSH_CLOUD_CONFIG = of("bosh-cloud-config");
private final String id;

View File

@@ -77,6 +77,7 @@
<assertj-version>3.5.2</assertj-version>
<slf4j-version>1.7.22</slf4j-version>
<guava-version>19.0</guava-version>
<mockito-version>1.10.19</mockito-version>
<jackson-2-version>2.5.0</jackson-2-version>
<jersey-2-version>2.10</jersey-2-version>
<lsp4j-version>0.2.1</lsp4j-version>

View File

@@ -57,7 +57,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<version>${mockito-version}</version>
<scope>test</scope>
</dependency>
<dependency>