Skip to content

Commit d4c8ade

Browse files
test data types
1 parent 27161cb commit d4c8ade

2 files changed

Lines changed: 114 additions & 106 deletions

File tree

plugins/iac/nimble/src/main/java/org/apache/cloudstack/tosca/parser/ToscaServiceTemplateParser.java

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.cloud.exception.InvalidParameterValueException;
2020
import com.cloud.user.Account;
2121
import org.apache.cloudstack.tosca.functions.ToscaFunction;
22-
import org.apache.cloudstack.tosca.model.ToscaDataTypeDefinition;
2322
import org.apache.cloudstack.tosca.model.ToscaFieldDefinition;
2423
import org.apache.cloudstack.tosca.model.ToscaInputDefinition;
2524
import org.apache.cloudstack.tosca.model.ToscaNodeTemplate;
@@ -207,7 +206,6 @@ private Map<String, ToscaProperty> parseNodeTemplateProperties(String nodeTempla
207206
return propertyDefinitions;
208207
}
209208

210-
// data types are missing here
211209
private ToscaProperty parseProperty(String propertyName, Object propertyBody, ToscaPropertyDefinition propertyDefinition, String nodeTemplateName, ToscaServiceTemplateParsingContext context) {
212210
Map<String, Object> body = ToscaYamlHelper.asMap(propertyBody);
213211
boolean isFunctionCall = body.size() == 1 && body.keySet().iterator().next().startsWith("$");
@@ -229,14 +227,6 @@ private ToscaProperty parsePropertyValue(String propertyName, Object propertyBod
229227
return null;
230228
}
231229

232-
if (type.getKind() == ToscaTypeDefinition.Kind.COLLECTION) {
233-
return parseCollectionPropertyValue(propertyName, propertyBody, propertyDefinition, nodeTemplateName, context);
234-
}
235-
236-
if (type.getKind() == ToscaTypeDefinition.Kind.DATA_TYPE) {
237-
return parseDataTypePropertyValue(propertyName, propertyBody, propertyDefinition, nodeTemplateName, context);
238-
}
239-
240230
ToscaFunction.ToscaBooleanFunction validationFunction = propertyDefinition.getValidation();
241231
if (validationFunction != null && !validationFunction.evaluate(propertyBody)) {
242232
context.addError(String.format("The provided value [%s] for the property [%s] of the [%s] node template is not valid according to the property validation function [%s].", propertyBody, propertyName, nodeTemplateName, validationFunction), nodeTemplateName + " declaration");
@@ -246,64 +236,6 @@ private ToscaProperty parsePropertyValue(String propertyName, Object propertyBod
246236
return new ToscaProperty(propertyDefinition, propertyBody, propertyBody);
247237
}
248238

249-
private ToscaProperty parseCollectionPropertyValue(String propertyName, Object propertyBody, ToscaPropertyDefinition propertyDefinition, String nodeTemplateName, ToscaServiceTemplateParsingContext context) {
250-
ToscaPropertyDefinition entryDefinition = ToscaPropertyDefinition.ofAnonymous(propertyDefinition.getType().getEntrySchema());
251-
boolean hasErrors = false;
252-
253-
List<?> propertyBodyAsList = ToscaYamlHelper.asList(propertyBody);
254-
if (propertyBodyAsList != null) {
255-
for (int i = 0; i < propertyBodyAsList.size(); i++) {
256-
if (parseProperty(String.format("%s[%d]", propertyName, i), propertyBodyAsList.get(i), entryDefinition, nodeTemplateName, context) == null) {
257-
hasErrors = true;
258-
}
259-
}
260-
} else {
261-
for (Map.Entry<String, Object> entry : ToscaYamlHelper.asMap(propertyBody).entrySet()) {
262-
if (parseProperty(String.format("%s[%s]", propertyName, entry.getKey()), entry.getValue(), entryDefinition, nodeTemplateName, context) == null) {
263-
hasErrors = true;
264-
}
265-
}
266-
}
267-
268-
return hasErrors ? null : new ToscaProperty(propertyDefinition, propertyBody, propertyBody);
269-
}
270-
271-
private ToscaProperty parseDataTypePropertyValue(String propertyName, Object propertyBody, ToscaPropertyDefinition propertyDefinition, String nodeTemplateName, ToscaServiceTemplateParsingContext context) {
272-
ToscaDataTypeDefinition dataTypeDefinition = propertyDefinition.getType().getDataType();
273-
Map<String, Object> dataType = ToscaYamlHelper.asMap(propertyBody);
274-
275-
validateKnownToscaKeys(dataType, dataTypeDefinition.getProperties().keySet(), nodeTemplateName + " declaration", context);
276-
Set<String> dataTypeRequiredProperties = dataTypeDefinition.getRequiredPropertyNames();
277-
boolean missingRequiredProperties = checkMissingRequiredToscaKeys(dataType, dataTypeRequiredProperties, nodeTemplateName + " declaration", context);
278-
if (missingRequiredProperties) {
279-
return null;
280-
}
281-
282-
boolean hasErrors = false;
283-
for (Map.Entry<String, ToscaPropertyDefinition> entry : dataTypeDefinition.getProperties().entrySet()) {
284-
String fieldName = entry.getKey();
285-
String fieldPropertyName = String.format("%s.%s", propertyName, fieldName);
286-
ToscaPropertyDefinition fieldDefinition = entry.getValue();
287-
Object fieldValue = dataType.get(fieldName);
288-
hasErrors = hasErrors || checkErrorsInDataTypeFieldValue(fieldValue, fieldDefinition, fieldPropertyName, nodeTemplateName, context);
289-
}
290-
291-
return hasErrors ? null : new ToscaProperty(propertyDefinition, dataType, dataType);
292-
}
293-
294-
private boolean checkErrorsInDataTypeFieldValue(Object fieldValue, ToscaPropertyDefinition fieldDefinition, String fieldPropertyName, String nodeTemplateName, ToscaServiceTemplateParsingContext context) {
295-
if (fieldValue == null && !fieldDefinition.isRequired()) {
296-
return false;
297-
}
298-
299-
if (fieldValue == null && fieldDefinition.isRequired()) {
300-
context.addError(String.format("Required field [%s] of property [%s] is missing.", fieldDefinition.getName(), fieldPropertyName), nodeTemplateName + " declaration");
301-
return true;
302-
}
303-
304-
return parseProperty(fieldPropertyName, fieldValue, fieldDefinition, nodeTemplateName, context) == null;
305-
}
306-
307239
private ToscaProperty parseGetInputPropertyValue(String propertyName, Map<String, Object> propertyBody, ToscaPropertyDefinition propertyDefinition, String nodeTemplateName, ToscaServiceTemplateParsingContext context) {
308240
String targetInputName = ToscaYamlHelper.asString(propertyBody.get("$get_input"));
309241
if (!context.getInputs().containsKey(targetInputName)) {

plugins/iac/nimble/src/test/java/org/apache/cloudstack/tosca/parser/ToscaServiceTemplateParserTest.java

Lines changed: 114 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
// under the License.
1717
package org.apache.cloudstack.tosca.parser;
1818

19-
import bsh.StringUtil;
2019
import com.cloud.exception.InvalidParameterValueException;
2120
import org.apache.cloudstack.tosca.functions.ToscaBooleanFunctions;
2221
import org.apache.cloudstack.tosca.functions.ToscaFunction;
2322
import org.apache.cloudstack.tosca.model.ToscaAttributeDefinition;
23+
import org.apache.cloudstack.tosca.model.ToscaCollectionType;
2424
import org.apache.cloudstack.tosca.model.ToscaDataTypeDefinition;
2525
import org.apache.cloudstack.tosca.model.ToscaInputDefinition;
2626
import org.apache.cloudstack.tosca.model.ToscaNodeTemplate;
@@ -71,16 +71,18 @@ private ToscaNodeType getVmNodeTypeForTests() {
7171
ToscaPropertyDefinition sshKeyPairName = new ToscaPropertyDefinition("ssh-key-pair-name", "SSH key pair name.", ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING), false, null);
7272

7373
ToscaPropertyDefinition nameDataTypeProperty = new ToscaPropertyDefinition("name", "Name.", ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING), true, null);
74-
ToscaPropertyDefinition valueDataTypeProperty = new ToscaPropertyDefinition("value", "Value.", ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING), true, null);
75-
ToscaDataTypeDefinition detailsDataTypeDefinition = new ToscaDataTypeDefinition("NameValueMapping", Map.of(nameDataTypeProperty.getName(), nameDataTypeProperty, valueDataTypeProperty.getName(), valueDataTypeProperty));
76-
ToscaPropertyDefinition details = new ToscaPropertyDefinition("offering-details", "Offering details.", ToscaTypeDefinition.ofDataType(detailsDataTypeDefinition), false, null);
74+
ToscaPropertyDefinition valueDataTypeProperty = new ToscaPropertyDefinition("value", "Value.", ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING), false, null);
75+
ToscaDataTypeDefinition nameValueMappingDataType = new ToscaDataTypeDefinition("NameValueMapping", Map.of(nameDataTypeProperty.getName(), nameDataTypeProperty, valueDataTypeProperty.getName(), valueDataTypeProperty));
76+
ToscaPropertyDefinition nameValueMappingList = new ToscaPropertyDefinition("name-value-mapping", "Name value mapping.", ToscaTypeDefinition.ofCollection(ToscaCollectionType.LIST, ToscaTypeDefinition.ofDataType(nameValueMappingDataType)), false, null);
7777

78+
ToscaPropertyDefinition ipAddresses = new ToscaPropertyDefinition("ip-addresses", "IPs.", ToscaTypeDefinition.ofCollection(ToscaCollectionType.LIST, ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING)), false, null);
79+
ToscaPropertyDefinition details = new ToscaPropertyDefinition("offering-details", "Offering details.", ToscaTypeDefinition.ofCollection(ToscaCollectionType.MAP, ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING)), false, null);
7880
ToscaAttributeDefinition uuid = new ToscaAttributeDefinition("uuid", "UUID of the system VM.", ToscaTypeDefinition.ofPrimitive(ToscaPrimitiveType.STRING));
7981

8082
return new ToscaNodeType("Vm",
8183
Map.of(type.getName(), type, vcpus.getName(), vcpus, startVm.getName(), startVm,
8284
maxUsage.getName(), maxUsage, sshKeyPairId.getName(), sshKeyPairId, sshKeyPairName.getName(), sshKeyPairName,
83-
details.getName(), details),
85+
nameValueMappingList.getName(), nameValueMappingList, ipAddresses.getName(), ipAddresses, details.getName(), details),
8486
Map.of(uuid.getName(), uuid));
8587
}
8688

@@ -247,23 +249,6 @@ public void parseNodeTemplatesTestHandleMissingRequiredKeys() {
247249
Mockito.verify(parsingContextMock, Mockito.times(1)).addErrors(Mockito.anyList(), Mockito.anyString());
248250
}
249251

250-
//
251-
// instance:
252-
// type: Vm
253-
// properties:
254-
// type: SSVM
255-
// vcpus: 2
256-
// start-vm: false
257-
// max-usage: 10.572
258-
// ssh-key-pair-id: { $get_attribute: [pair, uuid] }
259-
// ssh-key-pair-name: { $get_property: [pair, name] }
260-
// pair:
261-
// type: SshPair
262-
// properties:
263-
// name: Pair
264-
// public-key: { $get_input: key }
265-
266-
267252
@Test
268253
public void parseNodeTemplatesTestEnsureAllPropertyTypesAreParsedCorrectly() {
269254
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, start-vm: false, max-usage: 10.572, ssh-key-pair-id: {$get_attribute: [pair, uuid]}, ssh-key-pair-name: {$get_property: [pair, name]}}}, pair: {type: SshPair, properties: {name: Pair, public-key: {$get_input: public-key}}}}";
@@ -358,22 +343,113 @@ public void parseNodeTemplatesTestHandleHandleIncorrectGetAttributeAndGetPropert
358343
Mockito.verify(parsingContextMock, Mockito.never()).addUnresolvedByGetAttribute(Mockito.any(), Mockito.any());
359344
}
360345

361-
// instance:
362-
// type: Vm
363-
// properties:
364-
// type: SSVM
365-
// vcpus: 2
366-
// start-vm: false
367-
// max-usage: 10.572
368-
// ssh-key-pair-id: { $get_attribute: [pair, uuid] }
369-
// ssh-key-pair-name: { $get_property: [pair, name] }
370-
// requirements:
371-
// - dependency: pair
372-
// pair:
373-
// type: SshPair
374-
// properties:
375-
// name: Pair
376-
// public-key: { $get_input: key }
346+
@Test
347+
public void parseNodeTemplatesTestEnsureCollectionTypesAreParsedCorrectly() {
348+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, offering-details: {memory: '1024', speed: '1000'}, ip-addresses: [10.0.0.2, 172.16.30.2]}}}";
349+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
350+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
351+
352+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
353+
Mockito.verify(parsingContextMock, Mockito.never()).addError(Mockito.anyString(), Mockito.anyString());
354+
Mockito.verify(parsingContextMock, Mockito.never()).addErrors(Mockito.anyList(), Mockito.anyString());
355+
356+
Map<String, Object> details = ToscaYamlHelper.asMap(nodeTemplates.get("instance").getProperty("offering-details").getRawValue());
357+
Assert.assertEquals(2, details.size());
358+
Assert.assertTrue(details.containsKey("memory"));
359+
Assert.assertTrue(details.containsKey("speed"));
360+
361+
List<?> ipAddresses = ToscaYamlHelper.asList(nodeTemplates.get("instance").getProperty("ip-addresses").getRawValue());
362+
Assert.assertEquals(2, ipAddresses.size());
363+
Assert.assertTrue(ipAddresses.contains("10.0.0.2"));
364+
Assert.assertTrue(ipAddresses.contains("172.16.30.2"));
365+
}
366+
367+
@Test
368+
public void parseNodeTemplatesTestHandleIncorrectCollectionTypesValues() {
369+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, offering-details: [10.0.0.2, 172.16.30.2], ip-addresses: {memory: '1024', speed: '1000'}}}}";
370+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
371+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
372+
373+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
374+
Mockito.verify(parsingContextMock, Mockito.times(2)).addError(Mockito.anyString(), Mockito.anyString());
375+
Assert.assertNull(nodeTemplates.get("instance").getProperty("offering-details"));
376+
Assert.assertNull(nodeTemplates.get("instance").getProperty("ip-addresses"));
377+
}
378+
379+
@Test
380+
public void parseNodeTemplatesTestHandleIncorrectEntrySchemaValues() {
381+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, offering-details: {memory: 1024, speed: false}, ip-addresses: [{name: name, value: value}]}}}";
382+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
383+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
384+
385+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
386+
Mockito.verify(parsingContextMock, Mockito.times(2)).addError(Mockito.anyString(), Mockito.anyString());
387+
Assert.assertNull(nodeTemplates.get("instance").getProperty("offering-details"));
388+
Assert.assertNull(nodeTemplates.get("instance").getProperty("ip-addresses"));
389+
}
390+
391+
@Test
392+
public void parseNodeTemplatesTestEnsureDataTypesAreParsedCorrectly() {
393+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, name-value-mapping: [{name: name1, value: value1}, {name: name2}]}}}";
394+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
395+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
396+
397+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
398+
Mockito.verify(parsingContextMock, Mockito.times(0)).addError(Mockito.anyString(), Mockito.anyString());
399+
List<?> nameValues = ToscaYamlHelper.asList(nodeTemplates.get("instance").getProperty("name-value-mapping").getRawValue());
400+
Map<String, Object> firstNameValue = ToscaYamlHelper.asMap(nameValues.get(0));
401+
Map<String, Object> secondNameValue = ToscaYamlHelper.asMap(nameValues.get(1));
402+
Assert.assertEquals(2, nameValues.size());
403+
Assert.assertEquals(2, firstNameValue.size());
404+
Assert.assertTrue(firstNameValue.containsKey("name"));
405+
Assert.assertTrue(firstNameValue.containsKey("value"));
406+
Assert.assertEquals(1, secondNameValue.size());
407+
Assert.assertTrue(secondNameValue.containsKey("name"));
408+
}
409+
410+
@Test
411+
public void parseNodeTemplatesTestHandleUnknownKeyInDataType() {
412+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, name-value-mapping: [{name: name1, unknown-key: value1}]}}}";
413+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
414+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
415+
416+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
417+
Mockito.verify(parsingContextMock, Mockito.times(1)).addError(Mockito.anyString(), Mockito.anyString());
418+
Assert.assertNull(nodeTemplates.get("instance").getProperty("name-value-mapping"));
419+
}
420+
421+
@Test
422+
public void parseNodeTemplatesTestHandleMissingRequiredKeyInDataType() {
423+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, name-value-mapping: [{value: value2}]}}}";
424+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
425+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
426+
427+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
428+
Mockito.verify(parsingContextMock, Mockito.times(1)).addError(Mockito.anyString(), Mockito.anyString());
429+
Assert.assertNull(nodeTemplates.get("instance").getProperty("name-value-mapping"));
430+
}
431+
432+
@Test
433+
public void parseNodeTemplatesTestHandleMismatchingPropertyTypesInDataType() {
434+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, name-value-mapping: [{name: 100, value: false}]}}}";
435+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
436+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
437+
438+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
439+
Mockito.verify(parsingContextMock, Mockito.times(1)).addError(Mockito.anyString(), Mockito.anyString());
440+
Assert.assertNull(nodeTemplates.get("instance").getProperty("name-value-mapping"));
441+
}
442+
443+
@Test
444+
public void parseNodeTemplatesTestHandleMismatchingTypesInDataType() {
445+
String serviceTemplateYaml = "{instance: {type: Vm, properties: {type: SSVM, vcpus: 2, name-value-mapping: [[firstitem]]}}}";
446+
Map<String, Object> serviceTemplate = ToscaYamlHelper.asMap(ToscaYamlHelper.loadYaml(serviceTemplateYaml));
447+
Mockito.when(parsingContextMock.getProfile()).thenReturn(getToscaProfileForTests());
448+
449+
Map<String, ToscaNodeTemplate> nodeTemplates = toscaServiceTemplateParserSpy.parseNodeTemplates(serviceTemplate, parsingContextMock);
450+
Mockito.verify(parsingContextMock, Mockito.times(1)).addError(Mockito.anyString(), Mockito.anyString());
451+
Assert.assertNull(nodeTemplates.get("instance").getProperty("name-value-mapping"));
452+
}
377453

378454
@Test
379455
public void parseNodeTemplatesTestEnsureRequirementsDependenciesAreAddedToTheParsingContext() {

0 commit comments

Comments
 (0)