CloudConfig 'manual network' schema

This commit is contained in:
Kris De Volder
2017-08-09 12:25:57 -07:00
parent 8c060d7980
commit faa45b76c6
15 changed files with 307 additions and 28 deletions

View File

@@ -15,7 +15,9 @@ import java.util.Collection;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.ide.vscode.bosh.models.BoshModels;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.Lazy;
import org.springframework.ide.vscode.commons.util.PartialCollection;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
@@ -24,40 +26,63 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
import com.google.common.collect.ImmutableList;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
public class BoshCloudConfigSchema extends SchemaSupport implements YamlSchema {
private final YBeanType toplevelType;
private final YType t_az_ref;
private final YType t_az_def;
private final YType t_vm_type_ref;
private final YType t_vm_type_def;
private final YType t_network_ref;
private AbstractType t_network_def;
private Collection<YType> definitionTypes;
private Collection<Pair<YType, YType>> defAndRefTypes;
private final Lazy<Collection<Pair<YType, YType>>> defAndRefTypes = new Lazy<>();
private AbstractType t_any;
private AbstractType t_ne_string;
private YAtomicType t_boolean;
private AbstractType t_pos_integer;
private YType t_params;
private YType t_ip_address;
private YType t_ip_range;
private YType t_ip_address_or_range;
public BoshCloudConfigSchema(YTypeFactory f, BoshModels models) {
super(f);
AbstractType t_any = f.yany("Object");
AbstractType t_ne_string = f.yatomic("String")
t_any = f.yany("Object");
t_ne_string = f.yatomic("String")
.parseWith(ValueParsers.NE_STRING);
YAtomicType t_boolean = f.yenum("boolean", "true", "false");
AbstractType t_pos_integer = f.yatomic("Positive Integer")
t_boolean = f.yenum("boolean", "true", "false");
t_pos_integer = f.yatomic("Positive Integer")
.parseWith(ValueParsers.POS_INTEGER);
YType t_params = f.ymap(t_ne_string, t_any);
t_params = f.ymap(t_ne_string, t_any);
t_az_ref = t_ne_string;
t_az_def = f.yatomic("AZName").parseWith(ValueParsers.NE_STRING);
t_az_ref = f.yenumFromDynamicValues("AZName", (dc) -> PartialCollection.compute(() ->
models.astTypes.getDefinedNames(dc, t_az_def)
));
t_vm_type_def = f.yatomic("VMTypeName").parseWith(ValueParsers.NE_STRING);
t_vm_type_ref = f.yenumFromDynamicValues("VMTypeName", (dc) -> PartialCollection.compute(() ->
models.astTypes.getDefinedNames(dc, t_vm_type_def)
));
t_network_def = f.yatomic("NetworkName").parseWith(ValueParsers.NE_STRING);
t_network_ref = t_ne_string;
t_ip_address = t_ne_string; //TODO? Syntax for ip addresses like 192.168.1.22 ?
t_ip_range = t_ne_string; //TODO? Syntax for ip ranges like 10.10.0.0/22 ?
t_ip_address_or_range = t_ne_string; //TODO?
YType t_network = createNetworkBlockSchema(models);
YBeanType t_az = f.ybean("AZ");
addProp(t_az, "name", t_az_def).isRequired(true);
addProp(t_az, "cloud_properties", t_params);
YBeanType t_compilation = f.ybean("Compilation");
addProp(t_compilation, "workers", t_pos_integer).isRequired(true);
@@ -71,8 +96,8 @@ public class BoshCloudConfigSchema extends SchemaSupport implements YamlSchema {
addProp(t_vm_type, "cloud_properties", t_params);
this.toplevelType = f.ybean("CloudConfig");
addProp(toplevelType, "azs", t_any).isRequired(true);
addProp(toplevelType, "networks", t_any).isRequired(true);
addProp(toplevelType, "azs", f.yseq(t_az)).isRequired(true);
addProp(toplevelType, "networks", f.yseq(t_network)).isRequired(true);
addProp(toplevelType, "vm_types", f.yseq(t_vm_type)).isRequired(true);
addProp(toplevelType, "vm_extensions", t_any);
addProp(toplevelType, "disk_types", t_any).isRequired(true);
@@ -85,18 +110,73 @@ public class BoshCloudConfigSchema extends SchemaSupport implements YamlSchema {
}
private YType createNetworkBlockSchema(BoshModels models) {
AbstractType t_manual_nw = f.ybean("ManualNetwork");
{
YBeanType t_subnet = f.ybean("Subnet[Manual]");
addProp(t_subnet, "range", t_ip_range).isRequired(true);
addProp(t_subnet, "gateway", t_ip_address).isRequired(true);
addProp(t_subnet, "dns", f.yseq(t_ip_address));
addProp(t_subnet, "reserved", f.yseq(t_ip_address_or_range));
addProp(t_subnet, "static", f.yseq(t_ip_address_or_range));
addProp(t_subnet, "az", t_az_ref);
addProp(t_subnet, "azs", f.yseq(t_az_ref));
addProp(t_subnet, "cloud_properties", t_params);
addProp(t_manual_nw, "subnets", f.yseq(t_subnet)).isRequired(true);
}
AbstractType t_vip_nw = f.ybean("VipNetwork");
addProp(t_vip_nw, "cloud_properties", t_params);
AbstractType t_dynamic_nw = f.ybean("DynamicNetwork");
addProp(t_dynamic_nw, "dns", f.yseq(t_ip_address));
addProp(t_dynamic_nw, "cloud_properties", t_params);
t_dynamic_nw.require(Constraints.mutuallyExclusive("dns", "subnets"));
t_dynamic_nw.require(Constraints.mutuallyExclusive("cloud_properties", "subnets"));
AbstractType t_network = f.contextAware("Network", dc -> {
String type = models.getTypeTag(dc);
if (StringUtil.hasText(type)) {
switch (type) {
case "manual":
return t_manual_nw;
case "dynamic":
return t_dynamic_nw;
case "vip":
return t_vip_nw;
default:
}
}
return null;
})
.treatAsBean();
//Add shared properties to the 'super' type.
addProp(t_network, "name", t_network_def).isPrimary(true);
addProp(t_network, "type", f.yenum("NetworkType", "manual", "dynamic", "vip")).isRequired(true);
//Add shared properties to all 'sub' types.
for (AbstractType subtype : ImmutableList.of(t_dynamic_nw, t_manual_nw, t_vip_nw)) {
for (YTypedProperty sharedProp : t_network.getProperties()) {
subtype.addProperty(sharedProp);
}
}
return t_network;
}
/**
* @return Pairs of types. Each pair contains a 'def' type and a 'ref' type. Nodes with the ref-type
* shall be interpreted as reference to a corresponding node with the 'def' type if the def and ref node
* contain the same scalar value.
*/
public Collection<Pair<YType, YType>> getDefAndRefTypes() {
if (defAndRefTypes==null) {
defAndRefTypes = ImmutableList.of(
Pair.of(t_vm_type_def, t_vm_type_ref)
);
}
return defAndRefTypes;
return defAndRefTypes.load(() -> ImmutableList.of(
Pair.of(t_vm_type_def, t_vm_type_ref),
Pair.of(t_network_def, t_network_ref),
Pair.of(t_az_def, t_az_ref)
));
}
public Collection<YType> getDefinitionTypes() {

View File

@@ -10,8 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.bosh.models;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
public class BoshModels {
public final YamlAstCache asts = new YamlAstCache();
@@ -28,4 +31,12 @@ public class BoshModels {
this.releasesProvider = releasesProvider;
}
public String getTypeTag(DynamicSchemaContext dc) {
YamlFileAST ast = asts.getSafeAst(dc.getDocument(), true);
if (ast!=null) {
return NodeUtil.asScalar(dc.getPath().thenValAt("type").traverseToNode(ast));
}
return null;
}
}

View File

@@ -0,0 +1 @@
*Required*. Lists subnets in this network.

View File

@@ -0,0 +1 @@
*Required*. Name used to reference this network configuration.

View File

@@ -0,0 +1 @@
*Required*. The type of configuration. Should be one of `manual`, `dynamic` or `vip`.

View File

@@ -0,0 +1 @@
AZ associated with this subnet (should only be used when using [first class AZs](https://bosh.io/docs/azs.html)). Example: `z1`. Available in v241+.

View File

@@ -0,0 +1 @@
List of AZs associated with this subnet (should only be used when using [first class AZs](https://bosh.io/docs/azs.html)). Example: `[z1, z2]`. Available in v241+.

View File

@@ -0,0 +1,11 @@
Describes any IaaS-specific properties for the subnet. Default is {} (empty Hash).
See:
- [AWS CPI network cloud properties](https://bosh.io/docs/aws-cpi.html#networks)
- [Azure CPI network cloud properties](https://bosh.io/docs/azure-cpi.html#networks)
- [OpenStack CPI network cloud properties](https://bosh.io/docs/openstack-cpi.html#networks)
- [Softlayer CPI network cloud properties](https://bosh.io/docs/softlayer-cpi.html#networks)
- [Google Cloud Platform CPI network cloud properties](https://bosh.io/docs/google-cpi.html#networks)
- [vSphere CPI network cloud properties](https://bosh.io/docs/vsphere-cpi.html#networks)
- [vCloud CPI network cloud properties](https://bosh.io/docs/vcloud-cpi.html#networks)

View File

@@ -0,0 +1 @@
DNS IP addresses for this subnet.

View File

@@ -0,0 +1 @@
*Required*. Subnet gateway IP.

View File

@@ -0,0 +1 @@
*Required*. Subnet IP range that includes all IPs from this subnet.

View File

@@ -0,0 +1 @@
Array of reserved IPs and/or IP ranges. BOSH does not assign IPs from this range to any VM.

View File

@@ -0,0 +1 @@
Array of static IPs and/or IP ranges. BOSH assigns IPs from this range to jobs requesting static IPs. Only IPs specified here can be used for static IP reservations.

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.bosh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -1915,13 +1916,8 @@ public class BoshEditorTest {
"compilation:\n" +
" <*>"
);
editor.assertContextualCompletions(PLAIN_COMPLETION.or(SNIPPET_COMPLETION),
editor.assertContextualCompletions(PLAIN_COMPLETION.and(SNIPPET_COMPLETION.negate()),
"<*>"
, // ==>
"workers: $1\n" +
" az: $2\n" +
" vm_type: $3\n" +
" network: $4<*>"
,
"az: <*>"
,
@@ -1933,6 +1929,14 @@ public class BoshEditorTest {
,
"workers: <*>"
);
editor.assertContextualCompletions(PLAIN_COMPLETION.and(SNIPPET_COMPLETION),
"<*>"
, // ==>
"workers: $1\n" +
" az: $2\n" +
" vm_type: $3\n" +
" network: $4<*>"
);
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"compilation:\n" +
@@ -1946,6 +1950,7 @@ public class BoshEditorTest {
editor.assertProblems(
"not-int|NumberFormatException",
"not-bool|unknown 'boolean'",
"z1|unknown 'AZName'",
"default|unknown 'VMTypeName'"
);
editor.assertHoverContains("workers", "The maximum number of compilation VMs");
@@ -2037,7 +2042,9 @@ public class BoshEditorTest {
" network: private"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
editor.assertProblems(/*NONE*/);
editor.assertProblems(
"z1|unknown 'AZName'"
);
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"vm_types:\n" +
@@ -2057,7 +2064,167 @@ public class BoshEditorTest {
" network: private"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
editor.assertProblems("woot-vm|unknown 'VMTypeName'");
editor.assertProblems(
"z1|unknown 'AZName'",
"woot-vm|unknown 'VMTypeName'"
);
}
@Test public void cloudconfig_network_ca() throws Exception {
Editor editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"networks:\n" +
"- <*>"
);
editor.assertCompletions(SNIPPET_COMPLETION.negate(),
"networks:\n" +
"- name: <*>"
);
editor.assertCompletions(SNIPPET_COMPLETION,
"networks:\n" +
"- name: $1\n" +
" type: $2<*>"
);
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"networks:\n" +
"- name: foo\n" +
" <*>"
);
editor.assertCompletions(PLAIN_COMPLETION,
"networks:\n" +
"- name: foo\n" +
" type: <*>"
);
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"networks:\n" +
"- name: foo\n" +
" type: <*>"
);
editor.assertContextualCompletions(PLAIN_COMPLETION,
"<*>"
, // =>
"dynamic<*>",
"manual<*>",
"vip<*>"
);
}
@Test public void cloudconfig_manual_network_ca() throws Exception {
Editor editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"networks:\n" +
"- name: foo\n" +
" type: manual\n" +
" <*>"
);
editor.assertContextualCompletions(PLAIN_COMPLETION,
"<*>"
, // =>
"subnets:\n" + //non-snippet
" - <*>"
,
"subnets:\n" + //snippet
" - range: $1\n" +
" gateway: $2<*>"
);
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"networks:\n" +
"- name: foo\n" +
" type: manual\n" +
" subnets:\n" +
" - <*>"
);
editor.assertContextualCompletions(PLAIN_COMPLETION.and(SNIPPET_COMPLETION.negate()),
"<*>"
, // ==>
"az: <*>"
,
"azs:\n" +
" - <*>"
,
"cloud_properties:\n" +
" <*>"
,
"dns:\n" +
" - <*>"
,
"gateway: <*>"
,
"range: <*>"
,
"reserved:\n" +
" - <*>"
,
"static:\n" +
" - <*>"
);
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"azs:\n" +
"- name: zone-1\n" +
"- name: zone-2\n" +
"- name: zone-3\n" +
"networks:\n" +
"- name: foo\n" +
" type: manual\n" +
" subnets:\n" +
" - az: <*>"
);
editor.assertContextualCompletions("<*>", "zone-1<*>", "zone-2<*>", "zone-3<*>");
editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"azs:\n" +
"- name: zone-1\n" +
"- name: zone-2\n" +
"- name: zone-3\n" +
"networks:\n" +
"- name: foo\n" +
" type: manual\n" +
" subnets:\n" +
" - azs:\n" +
" - <*>"
);
editor.assertContextualCompletions("<*>", "zone-1<*>", "zone-2<*>", "zone-3<*>");
}
@Test public void cloudconfig_manual_network_hovers() throws Exception {
Editor editor = harness.newEditor(LanguageId.BOSH_CLOUD_CONFIG,
"networks:\n" +
"- name: my-network\n" +
" type: manual\n" +
"\n" +
" subnets:\n" +
" - range: 10.10.0.0/24\n" +
" gateway: 10.10.0.1\n" +
" dns: [10.10.0.2]\n" +
"\n" +
" # IPs that will not be used for anything\n" +
" reserved: [10.10.0.2-10.10.0.10]\n" +
" az: z1\n" +
"\n" +
" cloud_properties: {subnet: subnet-9be6c3f7}\n" +
"\n" +
" - range: 10.10.1.0/24\n" +
" gateway: 10.10.1.1\n" +
" dns: [10.10.1.2]\n" +
"\n" +
" static: [10.10.1.11-10.10.1.20]\n" +
"\n" +
" azs: [z2, z3]\n" +
" cloud_properties: {subnet: subnet-9be6c6gh}"
);
editor.assertHoverContains("name", "Name used to reference this network configuration");
editor.assertHoverContains("type", "The type of configuration. Should be one of `manual`, `dynamic` or `vip`");
editor.assertHoverContains("subnets", "Lists subnets in this network");
editor.assertHoverContains("range", "Subnet IP range that includes all IPs from this subnet");
editor.assertHoverContains("gateway", "Subnet gateway IP");
editor.assertHoverContains("dns", "DNS IP addresses for this subnet");
editor.assertHoverContains("reserved", "Array of reserved IPs and/or IP ranges. BOSH does not assign IPs from this range to any VM");
editor.assertHoverContains("static", "Array of static IPs and/or IP ranges.");
editor.assertHoverContains("az", "AZ associated with this subnet");
editor.assertHoverContains("azs", "List of AZs associated with this subnet");
editor.assertHoverContains("cloud_properties", "Describes any IaaS-specific properties for the subnet");
}
}

View File

@@ -61,10 +61,10 @@ public class Editor {
|| c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ")
|| c.getLabel().startsWith(Unicodes.RIGHT_ARROW+" ")
;
public static final Predicate<CompletionItem> PLAIN_COMPLETION = c -> !RELAXED_COMPLETION.test(c);
public static final Predicate<CompletionItem> SNIPPET_COMPLETION = c -> c.getLabel().endsWith("Snippet");
public static final Predicate<CompletionItem> PLAIN_COMPLETION = RELAXED_COMPLETION.negate();
public static final Predicate<CompletionItem> DEDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.LEFT_ARROW+" ");
public static final Predicate<CompletionItem> INDENTED_COMPLETION = c -> c.getLabel().startsWith(Unicodes.RIGHT_ARROW+" ");
public static final Predicate<CompletionItem> SNIPPET_COMPLETION = c -> c.getLabel().endsWith("Snippet");
static class EditorState {
String documentContents;