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 e5281771d..811b3375a 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 @@ -50,6 +50,8 @@ public class BoshDeploymentManifestSchema implements YamlSchema { public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer") .parseWith(ValueParsers.integerAtLeast(1)); public final YType t_uuid = f.yatomic("UUID").parseWith(UUID::fromString); + public final YType t_integer_or_range = f.yatomic("Integer or Range") + .parseWith(BoshValueParsers.INTEGER_OR_RANGE); public BoshDeploymentManifestSchema() { TYPE_UTIL = f.TYPE_UTIL; @@ -108,7 +110,12 @@ public class BoshDeploymentManifestSchema implements YamlSchema { t_stemcell.requireOneOf("name", "os"); addProp(TOPLEVEL_TYPE, "stemcells", f.yseq(t_stemcell)).isRequired(true); - YType t_update = t_params; //TODO: https://www.pivotaltracker.com/story/show/148627121 + YBeanType t_update = f.ybean("Update"); + addProp(t_update, "canaries", t_strictly_pos_integer).isRequired(true); + addProp(t_update, "max_in_flight", t_pos_integer).isRequired(true); + addProp(t_update, "canary_watch_time", t_integer_or_range).isRequired(true); + addProp(t_update, "update_watch_time", t_integer_or_range).isRequired(true); + addProp(t_update, "serial", t_boolean); addProp(TOPLEVEL_TYPE, "update", t_update).isRequired(true); YBeanType t_job = f.ybean("Job"); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshValueParsers.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshValueParsers.java new file mode 100644 index 000000000..e3ba47b77 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshValueParsers.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.util.ValueParseException; +import org.springframework.ide.vscode.commons.util.ValueParser; + +public class BoshValueParsers { + + public static final ValueParser INTEGER_OR_RANGE = new ValueParser() { + + private int findDash(String s) throws ValueParseException { + int firstDash = s.indexOf('-'); + if (firstDash<0) { + return firstDash; //no dash... and that's okay! + } + int secondDash = s.indexOf('-', firstDash+1); + if (secondDash>=0) { + //Only one dash is expected! + throw new ValueParseException("Should be either a Integer, or a range (of the form '-')"); + } + return firstDash; + } + + @Override + public Object parse(String s) throws Exception { + int dash = findDash(s); + if (dash>=0) { + int low, high; + //range + try { + low = Integer.parseInt(s.substring(0, dash)); + } catch (Exception e) { + throw new ValueParseException("Should be a Integer", 0, dash); + } + try { + high = Integer.parseInt(s.substring(dash+1)); + } catch (Exception e) { + throw new ValueParseException("Should be a Integer", dash+1, s.length()); + } + if (low>high) { + throw new ValueParseException(low + " should be smaller than "+high); + } + } else { + //integer + try { + return Integer.parseInt(s); + } catch (Exception e) { + throw new ValueParseException("Should be either a Integer, or a range (of the form '-')"); + } + } + return s; + } + }; + + +} diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/canaries.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/canaries.md new file mode 100644 index 000000000..ceb7725cf --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/canaries.md @@ -0,0 +1 @@ +*Required*. The number of [canary](https://bosh.io/docs/terminology.html#canary) instances. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/canary_watch_time.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/canary_watch_time.md new file mode 100644 index 000000000..bba938372 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/canary_watch_time.md @@ -0,0 +1,6 @@ +*Required*. Only applies to monit start operation. + +- If the `canary_watch_time` is an integer, the Director sleeps for that many milliseconds, then checks whether the canary instances are healthy. +- If the `canary_watch_time` is a range (low-high), the Director: + - Waits for `low` milliseconds + - Waits until instances are healthy or `high` milliseconds have passed since instances started updating diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/max_in_flight.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/max_in_flight.md new file mode 100644 index 000000000..dbf66283b --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/max_in_flight.md @@ -0,0 +1 @@ +*Required*. The maximum number of non-canary instances to update in parallel. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/serial.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/serial.md new file mode 100644 index 000000000..60ad4d83b --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/serial.md @@ -0,0 +1 @@ +If disabled (set to `false`), instance groups will be deployed in parallel, otherwise - sequentially. Instances within a group will still follow `canary` and `max_in_flight` configuration. Defaults to `true`. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/update_watch_time.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/update_watch_time.md new file mode 100644 index 000000000..fdd4c2898 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/update_watch_time.md @@ -0,0 +1,6 @@ +*Required*. Only applies to monit start operation. + +- If the `update_watch_time` is an integer, the Director sleeps for that many milliseconds, then checks whether the instances are healthy. +- If the `update_watch_time` is a range (low-high), the Director: + - Waits for `low` milliseconds + - Waits until instances are healthy or `high` milliseconds have passed since instances started updating diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java index bf05b89a7..0a55a8e34 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java @@ -387,7 +387,6 @@ public class BoshEditorTest { " provides:\n" + " blah: blah\n" ); - editor.assertHoverContains("name", 3, "The job name"); editor.assertHoverContains("release", "The release where the job exists"); editor.assertHoverContains("consumes", "Links consumed by the job"); @@ -422,5 +421,44 @@ public class BoshEditorTest { editor.assertHoverContains("bosh", "no description"); editor.assertHoverContains("password", "Crypted password"); } + + @Test public void updateBlockCompletions() throws Exception { + Editor editor = harness.newEditor( + "update:\n" + + " <*>" + ); + editor.assertCompletions(PLAIN_COMPLETION, + "update:\n" + + " canaries: <*>" + , // ===== + "update:\n" + + " canary_watch_time: <*>" + , // ===== + "update:\n" + + " max_in_flight: <*>" + , // ===== + "update:\n" + + " serial: <*>" + , // ===== + "update:\n" + + " update_watch_time: <*>" + ); + } + + @Test public void updateBlockHovers() throws Exception { + Editor editor = harness.newEditor( + "update:\n" + + " canaries: 1\n" + + " max_in_flight: 10\n" + + " canary_watch_time: 1000-30000\n" + + " update_watch_time: 1000-30000\n" + + " serial: false" + ); + editor.assertHoverContains("canaries", "The number of [canary]"); + editor.assertHoverContains("max_in_flight", "maximum number of non-canary instances"); + editor.assertHoverContains("canary_watch_time", "checks whether the canary instances"); + editor.assertHoverContains("update_watch_time", "checks whether the instances"); + editor.assertHoverContains("serial", "deployed in parallel"); + } } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshValueParserTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshValueParserTest.java new file mode 100644 index 000000000..604b1a82b --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshValueParserTest.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.concourse; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.ide.vscode.bosh.BoshValueParsers; +import org.springframework.ide.vscode.commons.util.ValueParseException; +import org.springframework.ide.vscode.commons.util.ValueParser; + +public class BoshValueParserTest { + + private static final String MARKER = "<*>"; + + @Test public void integerOrRangeOkay() throws Exception { + BoshValueParsers.INTEGER_OR_RANGE.parse("123"); + BoshValueParsers.INTEGER_OR_RANGE.parse("123-456"); + } + + @Test public void integerOrRangeGarbage() throws Exception { + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>garbage<*>", "Should be either a Integer, or a range (of the form '-')"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>123--456<*>", "Should be either a Integer, or a range (of the form '-')"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>garbage<*>-123", "Should be a Integer"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "123-<*>garbage<*>", "Should be a Integer"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>123-122<*>", "123 should be smaller than 122"); + } + + private void assertProblem(ValueParser parser, String input, String expectedMessage) throws Exception { + String unmarkedInput = input.replace(MARKER, ""); + int firstMarker = input.indexOf(MARKER); + assertTrue(firstMarker>=0); + int secondMarker = input.indexOf(MARKER, firstMarker+1)-MARKER.length(); + assertTrue(secondMarker>=firstMarker); + try { + parser.parse(unmarkedInput); + } catch (ValueParseException e) { + int startIndex = startIndex(unmarkedInput, e); + int endIndex = endIndex(unmarkedInput, e); + String markedInput = + unmarkedInput.substring(0, startIndex) + + MARKER + + unmarkedInput.substring(startIndex, endIndex) + + MARKER + + unmarkedInput.substring(endIndex); + assertEquals(input, markedInput); + assertEquals(expectedMessage, e.getMessage()); + } + } + + private int endIndex(String unmarkedInput, ValueParseException e) { + int i = e.getEndIndex(); + if (i>=0) { + return i; + } else { + return unmarkedInput.length(); + } + } + + private int startIndex(String unarkedInput, ValueParseException e) { + int i = e.getStartIndex(); + if (i>=0) { + return i; + } else { + return 0; + } + } + +}