Skip to content

Commit b2ee738

Browse files
finish resolving properties by get attribute and get property
1 parent 8aae91f commit b2ee738

7 files changed

Lines changed: 104 additions & 6 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,7 @@ public class ApiConstants {
13291329
public static final String OBJECT_STORAGE_TOTAL = "objectstoragetotal";
13301330

13311331
// NIMBLE related
1332+
public static final String INPUTS = "inputs";
13321333
public static final String IAC_RESOURCE_TYPE_CONTENT = "iacresourcetypecontent";
13331334
public static final String SHOW_IAC_RESOURCE_TYPE_CONTENT = "showiacresourcetypecontent";
13341335

plugins/iac/nimble/src/main/java/org/apache/cloudstack/api/command/DeployIacTemplateCmd.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command;
1818

19+
import com.cloud.exception.InvalidParameterValueException;
1920
import org.apache.cloudstack.api.APICommand;
2021
import org.apache.cloudstack.api.ApiConstants;
2122
import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -25,6 +26,7 @@
2526
import org.apache.cloudstack.service.NimbleService;
2627

2728
import javax.inject.Inject;
29+
import java.util.Map;
2830

2931
@APICommand(name = "deployIacTemplate", description = "", responseObject = SuccessResponse.class, entityType = {IacResourceType.class},
3032
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -35,9 +37,20 @@ public class DeployIacTemplateCmd extends BaseAsyncCmd {
3537
@Parameter(name = ApiConstants.IAC_RESOURCE_TYPE_CONTENT, type = CommandType.STRING, length = 65535, description = "")
3638
private String iacTemplateContent;
3739

40+
@Parameter(name = ApiConstants.INPUTS, type = CommandType.MAP, description = "Input variables of the IaC template. They must be specified as key-pairs, for instance: 'inputs[0].first-input=\"First input value\" inputs[0].second-input=\"Second input value\"'")
41+
private Map<String, Map<String, String>> inputs;
42+
43+
public Map<String, String> getInputs() {
44+
if (inputs.size() > 1) {
45+
throw new InvalidParameterValueException("Please, specify the inputs as key-pairs, indexed with [0]. For instance: 'inputs[0].first-input=\"First input value\" inputs[0].second-input=\"Second input value\"'");
46+
}
47+
48+
return inputs.get(0);
49+
}
50+
3851
@Override
3952
public void execute() {
40-
nimbleService.deployIacTemplate(iacTemplateContent);
53+
nimbleService.deployIacTemplate(iacTemplateContent, getInputs());
4154
}
4255

4356
@Override

plugins/iac/nimble/src/main/java/org/apache/cloudstack/service/NimbleManagerImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public ListResponse<IacResourceTypeResponse> listIacResourceTypes(ListIacResourc
5959
}
6060

6161
@Override
62-
public void deployIacTemplate(String iacTemplateContent) {
63-
toscaOrchestrator.deployIacTemplate(iacTemplateContent);
62+
public void deployIacTemplate(String iacTemplateContent, Map<String, String> inputs) {
63+
toscaOrchestrator.deployIacTemplate(iacTemplateContent, inputs);
6464
}
6565

6666
@Override

plugins/iac/nimble/src/main/java/org/apache/cloudstack/service/NimbleService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.apache.cloudstack.framework.config.ConfigKey;
2424
import org.apache.cloudstack.framework.config.Configurable;
2525

26+
import java.util.Map;
27+
2628
public interface NimbleService extends PluggableService, Configurable {
2729
ConfigKey<Boolean> NimbleServiceEnabled = new ConfigKey<>("Advanced", Boolean.class,
2830
"nimble.service.enabled", "false",
@@ -35,5 +37,5 @@ public interface NimbleService extends PluggableService, Configurable {
3537
false, NimbleServiceEnabled.key());
3638

3739
ListResponse<IacResourceTypeResponse> listIacResourceTypes(ListIacResourceTypesCmd cmd);
38-
void deployIacTemplate(String iacTemplateContent);
40+
void deployIacTemplate(String iacTemplateContent, Map<String, String> inputs);
3941
}

plugins/iac/nimble/src/main/java/org/apache/cloudstack/tosca/model/ToscaNodeTemplate.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ public void addAttribute(String name, Object value) {
8787
public Object getAttribute(String name) {
8888
return attributes.get(name);
8989
}
90+
91+
public Map<String, Object> getAttributes() {
92+
return attributes;
93+
}
9094
}

plugins/iac/nimble/src/main/java/org/apache/cloudstack/tosca/orchestrator/ToscaOrchestrator.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,24 @@ public class ToscaOrchestrator {
9393

9494
private ExecutorService executorPool;
9595

96-
public void deployIacTemplate(String iacTemplateContent) {
96+
public void deployIacTemplate(String iacTemplateContent, Map<String, String> inputs) {
9797
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(iacTemplateContent, toscaProfile, null);
98+
resolveServiceTemplateInputs(serviceTemplate, inputs);
9899
Map<String, CompletableFuture<String>> provisioningTasksFutures = createProvisioningTasksFutures(serviceTemplate);
99100
logger.debug("Awaiting for all the provisioning tasks of the node template to complete.");
100101
CompletableFuture<Void> serviceTemplateFeature = CompletableFuture.allOf(provisioningTasksFutures.values().toArray(new CompletableFuture[0]));
101102
serviceTemplateFeature.join();
102103
logger.info("All provisioning tasks have completed successfully.");
103104
}
104105

106+
protected void resolveServiceTemplateInputs(ToscaServiceTemplate serviceTemplate, Map<String, String> inputs) {
107+
serviceTemplate.getInputs();
108+
109+
// como que tu vai fazer com collections? o tosca permite passar collections como inputs? e, o tosca permite definir apenas 1 item da collection como o que vem do input?
110+
111+
112+
}
113+
105114
private Map<String, CompletableFuture<String>> createProvisioningTasksFutures(ToscaServiceTemplate serviceTemplate) {
106115
logger.debug("Building provisioning tasks for the service template based on its graph topological sort.");
107116
Map<String, CompletableFuture<String>> futures = new HashMap<>();
@@ -274,6 +283,13 @@ protected String retrieve() {
274283
}
275284
}
276285

286+
/**
287+
* Populates the attributes of a node template based on the result of its provisioning task.
288+
* Each attribute declared in the node template's type will be searched in the <code>potentialAttributes</code>
289+
* map. If its corresponding value is found, it will be used to populate the node template's attribute.
290+
* @param nodeTemplate The node template whose attributes will be resolved.
291+
* @param potentialAttributes The return of the node template's provisioning task from which the values of the attributes will be retrieved.
292+
*/
277293
protected void populateNodeTemplateAttributes(ToscaNodeTemplate nodeTemplate, Map<String, Object> potentialAttributes) {
278294
ToscaNodeType nodeType = nodeTemplate.getType();
279295
if (nodeType.getAttributes().isEmpty()) {
@@ -294,6 +310,14 @@ protected void populateNodeTemplateAttributes(ToscaNodeTemplate nodeTemplate, Ma
294310
});
295311
}
296312

313+
/**
314+
* Resolve the unresolved/pending properties of a node template by the <code>$get_attribute</code> and <code>$get_property</code> TOSCA functions.
315+
* @param nodeTemplate The node template from which the pending dependencies will be resolved.
316+
* @param serviceTemplate The TOSCA service template the node template belongs to.
317+
* @param toscaFunction The TOSCA function used to resolve the pending dependencies. Current supported functions are: <code>$get_attribute</code> and <code>$get_property</code>.
318+
* @throws InvalidParameterValueException When the return value of the <code>$get_attribute</code> and <code>$get_property</code> function calls is null
319+
* or when the return value does not passes the validation function.
320+
*/
297321
protected void resolveUnresolvedPropertiesByToscaFunction(ToscaNodeTemplate nodeTemplate, ToscaServiceTemplate serviceTemplate, String toscaFunction) {
298322
Set<ToscaProperty> unresolvedProperties = ToscaConstants.GET_PROPERTY_FUNCTION.equals(toscaFunction) ? nodeTemplate.getUnresolvedPropertiesByGetProperty() : nodeTemplate.getUnresolvedPropertiesByGetAttribute();
299323
if (CollectionUtils.isEmpty(unresolvedProperties)) {
@@ -308,7 +332,13 @@ protected void resolveUnresolvedPropertiesByToscaFunction(ToscaNodeTemplate node
308332
String targetNodeName = ToscaYamlHelper.asString(functionCallArgs.get(0));
309333
ToscaNodeTemplate targetNode = serviceTemplate.getNodeTemplates().get(targetNodeName);
310334
String targetField = ToscaYamlHelper.asString(functionCallArgs.get(1));
335+
311336
Object valueToBeResolved = ToscaConstants.GET_PROPERTY_FUNCTION.equals(toscaFunction) ? targetNode.getProperty(targetField).getEvaluatedValue() : targetNode.getAttribute(targetField);
337+
if (valueToBeResolved == null) {
338+
logger.error("The field [{}] of the target node [{}] has not been defined. Thus, it is not possible to resolve the property [{}] of the [{}] node template.", targetField, targetNode.getName(), unresolvedProperty.getDefinition().getName(), nodeTemplate.getName());
339+
throw new InvalidParameterValueException(String.format("The field [%s] of the target node [%s] has not been defined. Unable to deploy [%s].", targetField, targetNode.getName(), nodeTemplate.getName()));
340+
}
341+
312342
if (unresolvedProperty.getDefinition().getValidation() != null) {
313343
logger.debug("The unresolved property [{}] has a validation clause. Executing it.", unresolvedProperty.getDefinition().getName());
314344
boolean validationResult = unresolvedProperty.getDefinition().getValidation().evaluate(valueToBeResolved);

plugins/iac/nimble/src/test/java/org/apache/cloudstack/tosca/orchestrator/ToscaOrchestratorTest.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
// under the License.
1717
package org.apache.cloudstack.tosca.orchestrator;
1818

19+
import com.cloud.exception.InvalidParameterValueException;
1920
import org.apache.cloudstack.fixtures.ToscaFixtures;
2021
import org.apache.cloudstack.persistence.iactemplatesprofile.IacResourceTypeVO;
22+
import org.apache.cloudstack.tosca.model.ToscaNodeTemplate;
2123
import org.apache.cloudstack.tosca.model.ToscaNodeType;
2224
import org.apache.cloudstack.tosca.model.ToscaServiceTemplate;
2325
import org.apache.cloudstack.tosca.parser.ToscaFieldParser;
@@ -59,7 +61,31 @@ public void setUp() {
5961
}
6062

6163
@Test
62-
public void resolveUnresolvedPropertiesByToscaFunctionTest() {
64+
public void populateNodeTemplateAttributesTestSuccessfullyPopulateNodeAttributes() {
65+
String serviceTemplateYaml = "{service_template: {node_templates: {instance: {type: Vm, properties: {type: VR, ssh-key-pair-name: {$get_property: [pair, name]}, vcpus: 2, ip-addresses: [10.0.0.1, 10.0.0.2]}}, other-instance: {type: Vm, properties: {type: SSVM, ssh-key-pair-name: {$get_attribute: [pair, uuid]}, vcpus: 1, ip-addresses: [10.0.0.1]}}, pair: {type: SshPair, properties: {name: Pair, public-key: Public Key}}}}}";
66+
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(serviceTemplateYaml, ToscaFixtures.getToscaProfileForTests(), null);
67+
ToscaNodeTemplate instanceNodeTemplate = serviceTemplate.getNodeTemplates().get("instance");
68+
Map<String, Object> potentialAttributes = Map.of("uuid", "UUID Value", "name", "VM Name", "vCPUs", 2, "systemVm", false);
69+
toscaOrchestratorSpy.populateNodeTemplateAttributes(instanceNodeTemplate, potentialAttributes);
70+
Map<String, Object> nodeTemplateAttributes = instanceNodeTemplate.getAttributes();
71+
Assert.assertEquals(instanceNodeTemplate.getType().getAttributes().size(), nodeTemplateAttributes.size());
72+
Assert.assertEquals(potentialAttributes.get("uuid"), nodeTemplateAttributes.get("uuid"));
73+
}
74+
75+
@Test
76+
public void populateNodeTemplateAttributesTestNotPopulateAttributeWhenItsValueIsNotAvailable() {
77+
String serviceTemplateYaml = "{service_template: {node_templates: {instance: {type: Vm, properties: {type: VR, ssh-key-pair-name: {$get_property: [pair, name]}, vcpus: 2, ip-addresses: [10.0.0.1, 10.0.0.2]}}, other-instance: {type: Vm, properties: {type: SSVM, ssh-key-pair-name: {$get_attribute: [pair, uuid]}, vcpus: 1, ip-addresses: [10.0.0.1]}}, pair: {type: SshPair, properties: {name: Pair, public-key: Public Key}}}}}";
78+
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(serviceTemplateYaml, ToscaFixtures.getToscaProfileForTests(), null);
79+
ToscaNodeTemplate instanceNodeTemplate = serviceTemplate.getNodeTemplates().get("instance");
80+
Map<String, Object> potentialAttributes = Map.of("name", "VM Name", "vCPUs", 2, "systemVm", false);
81+
toscaOrchestratorSpy.populateNodeTemplateAttributes(instanceNodeTemplate, potentialAttributes);
82+
Map<String, Object> nodeTemplateAttributes = instanceNodeTemplate.getAttributes();
83+
Assert.assertEquals(0, nodeTemplateAttributes.size());
84+
Assert.assertFalse(nodeTemplateAttributes.containsKey("uuid"));
85+
}
86+
87+
@Test
88+
public void resolveUnresolvedPropertiesByToscaFunctionTestExecuteGetAttributeAndGetPropertyFunctions() {
6389
String serviceTemplateYaml = "{service_template: {node_templates: {instance: {type: Vm, properties: {type: VR, ssh-key-pair-name: {$get_property: [pair, name]}, vcpus: 2, ip-addresses: [10.0.0.1, 10.0.0.2]}}, other-instance: {type: Vm, properties: {type: SSVM, ssh-key-pair-name: {$get_attribute: [pair, uuid]}, vcpus: 1, ip-addresses: [10.0.0.1]}}, pair: {type: SshPair, properties: {name: Pair, public-key: Public Key}}}}}";
6490
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(serviceTemplateYaml, ToscaFixtures.getToscaProfileForTests(), null);
6591
serviceTemplate.getNodeTemplates().get("pair").addAttribute("uuid", "UUID");
@@ -71,6 +97,28 @@ public void resolveUnresolvedPropertiesByToscaFunctionTest() {
7197
Assert.assertEquals(serviceTemplate.getNodeTemplates().get("pair").getAttribute("uuid"), serviceTemplate.getNodeTemplates().get("other-instance").getProperty("ssh-key-pair-name").getEvaluatedValue());
7298
}
7399

100+
@Test(expected = InvalidParameterValueException.class)
101+
public void resolveUnresolvedPropertiesByToscaFunctionTestThrowExceptionWhenTargetAttributeIsNotAvailable() {
102+
String serviceTemplateYaml = "{service_template: {node_templates: {instance: {type: Vm, properties: {type: {$get_attribute: [pair, uuid]}, ssh-key-pair-name: Name, vcpus: 2, ip-addresses: [10.0.0.1, 10.0.0.2]}}, pair: {type: SshPair, properties: {name: SSVM, public-key: Public Key}}}}}";
103+
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(serviceTemplateYaml, ToscaFixtures.getToscaProfileForTests(), null);
104+
toscaOrchestratorSpy.resolveUnresolvedPropertiesByToscaFunction(serviceTemplate.getNodeTemplates().get("instance"), serviceTemplate, "$get_attribute");
105+
}
106+
107+
@Test(expected = InvalidParameterValueException.class)
108+
public void resolveUnresolvedPropertiesByToscaFunctionTestThrowExceptionWhenValidationFunctionFailsAfterResolvingAProperty() {
109+
String serviceTemplateYaml = "{service_template: {node_templates: {instance: {type: Vm, properties: {type: {$get_property: [pair, name]}, ssh-key-pair-name: Name, vcpus: 2, ip-addresses: [10.0.0.1, 10.0.0.2]}}, pair: {type: SshPair, properties: {name: Pair, public-key: Public Key}}}}}";
110+
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(serviceTemplateYaml, ToscaFixtures.getToscaProfileForTests(), null);
111+
toscaOrchestratorSpy.resolveUnresolvedPropertiesByToscaFunction(serviceTemplate.getNodeTemplates().get("instance"), serviceTemplate, "$get_property");
112+
}
113+
114+
@Test
115+
public void resolveUnresolvedPropertiesByToscaFunctionTestResolvePropertyWhenValidationFunctionSucceeds() {
116+
String serviceTemplateYaml = "{service_template: {node_templates: {instance: {type: Vm, properties: {type: {$get_property: [pair, name]}, ssh-key-pair-name: Name, vcpus: 2, ip-addresses: [10.0.0.1, 10.0.0.2]}}, pair: {type: SshPair, properties: {name: SSVM, public-key: Public Key}}}}}";
117+
ToscaServiceTemplate serviceTemplate = toscaParser.parseServiceTemplate(serviceTemplateYaml, ToscaFixtures.getToscaProfileForTests(), null);
118+
toscaOrchestratorSpy.resolveUnresolvedPropertiesByToscaFunction(serviceTemplate.getNodeTemplates().get("instance"), serviceTemplate, "$get_property");
119+
Assert.assertEquals(serviceTemplate.getNodeTemplates().get("pair").getProperty("name").getEvaluatedValue(), serviceTemplate.getNodeTemplates().get("instance").getProperty("type").getEvaluatedValue());
120+
}
121+
74122
@Test
75123
public void loadToscaProfileTestEachIacResourceTypeShouldBeParsed() {
76124
String firstResourceTypeContent = "{description: First resource type content}";

0 commit comments

Comments
 (0)