1 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
2 .. http://creativecommons.org/licenses/by/4.0
3 .. Copyright (c) Nordix Foundation. All rights reserved.
5 .. _acm-participant-guide-label:
7 Participant developer guide
8 ###########################
13 The ACM runtime delegates the user requests to the participants for performing the actual operations.
14 Hence the participant module in ACM is implemented adhering to a list of ACM protocols along with their own functional logic.
15 It works in a contract with the Participant Intermediary module for communicating with ACM-R.
16 This guide explains the design considerations for a new participant implementation in ACM.
18 Please refer the following section for a detailed understanding of Inbound and outbound messages a participant interacts with.
23 design-impl/participants/participants
25 Design considerations for a participant
26 ---------------------------------------
28 In ONAP, the ACM-runtime and participant modules are implemented in Java spring boot. The participant Intermediary module
29 which is added as a maven dependency to the participants has the default implementations available for listening the kafka
30 events coming in from the ACM-runtime, process them and delegate them to the appropriate handler class. Similarly the
31 Intermediary module also has the publisher class implementations for publishing events back from the participants to the ACM-runtime.
33 Hence the new participants has to have this Participant Intermediary module as a dependency and should:
35 * Configure SpringBoot to scan the components located into the package "org.onap.policy.clamp.acm.participant.intermediary".
36 * Implement the following interfaces from the Participant Intermediary.
37 * Provide the following mandatory properties in order to make the participant work in synchronisation with ACM-runtime.
39 The participant application should be provided with the following Intermediary parameter values in the application properties
40 and the same is configured for the 'ParticipantIntermediaryParameters' object in the code.
42 1. participantId - A unique participant UUID that is used by the runtime to identify the participant.
43 2. ReportingTimeIntervalMs - Time inertval the participant should report the status/heartbeat to the runtime.
44 3. clampAutomationCompositionTopics - This property takes in the kafka topic names and servers for the intermediary module to use.
45 These values should be provided for both source and sink configs. The following example shows the topic parameters set for using DMaap.
49 clampAutomationCompositionTopics:
52 topic: POLICY-ACRUNTIME-PARTICIPANT
54 - ${topicServer:localhost}
55 topicCommInfrastructure: dmaap
59 topic: POLICY-ACRUNTIME-PARTICIPANT
61 - ${topicServer:localhost}
62 topicCommInfrastructure: dmaap
64 4. participantSupportedElementTypes - This property takes a list of typeName and typeVersion fields to define the types of AC elements the participant deals with.
65 These are user defined name and version and the same should be defined for the AC elements that are included in the TOSCA based AC definitions.
69 participantSupportedElementTypes:
71 typeName: org.onap.policy.clamp.acm.PolicyAutomationCompositionElement
74 Interfaces to Implement
75 -----------------------
76 AutomationCompositionElementListener:
77 Every participant should implement a handler class that implements the AutomationCompositionElementListener interface
78 from the Participant Intermediary. The intermediary listener class listens for the incoming events from the ACM-runtime
79 and invoke the handler class implementations for various operations. This class implements the methods for deploying,
80 undeploying, locking, unlocking , deleting, updating, priming, depriming requests that are coming from the ACM-runtime.
81 The methods are as follows.
85 1. void undeploy(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException;
86 2. void deploy(UUID automationCompositionId, AcElementDeploy element, Map<String, Object> inProperties) throws PfModelException;
87 3. void lock(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException;
88 4. void unlock(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException;
89 5. void delete(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException;
90 6. void update(UUID automationCompositionId, AcElementDeploy element, Map<String, Object> inProperties) throws PfModelException;
91 7. void prime(UUID compositionId, List<AutomationCompositionElementDefinition> elementDefinitionList) throws PfModelException;
92 8. void deprime(UUID compositionId) throws PfModelException;
93 9. void handleRestartComposition(UUID compositionId, List<AutomationCompositionElementDefinition> elementDefinitionList, AcTypeState state) throws PfModelException;
94 10. void handleRestartInstance(UUID automationCompositionId, AcElementDeploy element, Map<String, Object> properties, DeployState deployState, LockState lockState) throws PfModelException;
95 11. void migrate(UUID instanceId, AcElementDeploy element, UUID compositionTargetId, Map<String, Object> properties) throws PfModelException;
97 These method from the interface are implemented independently as per the user requirement. These methods after handling the
98 appropriate requests should also invoke the intermediary's publisher apis to notify the ACM-runtime with the acknowledgement events.
100 ParticipantParameters:
101 Every participant should implement a properties class that contains the values of all Intermediary parameter properties.
102 This class implements the method getIntermediaryParameters that returns 'ParticipantIntermediaryParameters' object. The method is as follows.
106 ParticipantIntermediaryParameters getIntermediaryParameters()
111 ParticipantIntermediaryApi:
112 The participant intermediary api has the following methods that can be invoked from the participant for the following purposes.
114 #. The requested operations are completed in the handler class and the ACM-runtime needs to be notified.
115 #. Collect all instances data.
116 #. Send out Properties to ACM-runtime.
118 The methods are as follows:
120 This following method is invoked to update the AC element state after each operation is completed in the participant.
124 1. void updateAutomationCompositionElementState(UUID automationCompositionId, UUID elementId, DeployState deployState, LockState lockState, StateChangeResult stateChangeResult, String message);
125 2. Map<UUID, AutomationComposition> getAutomationCompositions();
126 3. AutomationComposition getAutomationComposition(UUID automationCompositionId);
127 4. AutomationCompositionElement getAutomationCompositionElement(UUID automationCompositionId, UUID elementId);
128 5. Map<UUID, Map<ToscaConceptIdentifier, AutomationCompositionElementDefinition>> getAcElementsDefinitions();
129 6. Map<ToscaConceptIdentifier, AutomationCompositionElementDefinition> getAcElementsDefinitions(UUID compositionId);
130 7. AutomationCompositionElementDefinition getAcElementDefinition(UUID compositionId, ToscaConceptIdentifier elementId);
131 8. void sendAcDefinitionInfo(UUID compositionId, ToscaConceptIdentifier elementId, Map<String, Object> outProperties);
132 9. void updateCompositionState(UUID compositionId, AcTypeState state, StateChangeResult stateChangeResult, String message);
133 10. void sendAcElementInfo(UUID automationCompositionId, UUID elementId, String useState, String operationalState, Map<String, Object> outProperties);
135 In/Out composition Properties
136 -----------------------------
137 The 'Common Properties' could be created or updated by ACM-runtime.
138 Participants will receive that Properties during priming events.
143 public void prime(UUID compositionId, List<AutomationCompositionElementDefinition> elementDefinitionList) throws PfModelException {
144 for (var acElementDefinition : elementDefinitionList) {
145 var inProperties = acElementDefinition.getAutomationCompositionElementToscaNodeTemplate().getProperties();
151 The 'Common Properties' could be fetched during depriming events.
156 public void deprime(UUID compositionId) throws PfModelException {
157 var elementDefinitionList = intermediaryApi.getAcElementsDefinitions(compositionId);
158 for (var acElementDefinition : elementDefinitionList.values()) {
159 var inProperties = acElementDefinition.getAutomationCompositionElementToscaNodeTemplate().getProperties();
165 The 'Out Properties' could be created or updated by participants. ACM-runtime will receive that Properties during ParticipantStatus event.
166 The participant can trigger this event using the method sendAcDefinitionInfo.
168 Is allowed to the participant to read all In/Out Properties of all compositions handled by the participant using the method getAcElementsDefinitions.
169 The following code is an example how to update the property 'myProperty' and send to ACM-runtime:
173 var acElement = intermediaryApi.getAcElementDefinition(compositionId, elementId);
174 var outProperties = acElement.getOutProperties();
175 outProperties.put("myProperty", myProperty);
176 intermediaryApi.sendAcDefinitionInfo(compositionId, elementId, outProperties);
178 In/Out instance Properties
179 --------------------------
180 The 'In/Out Properties' are stored into the instance elements, so each element has its own In/Out Properties.
182 The 'In Properties' could be created or updated by ACM-runtime. Participants will receive that Properties during deploy and update events.
184 The 'Out Properties' could be created or updated by participants. ACM-runtime will receive that Properties during ParticipantStatus event.
185 The participant can trigger this event using the method sendAcElementInfo. The 'useState' and 'operationalState' can be used as well.
186 The 'Out Properties' could be **cleaned**:
188 * by the participant using the method sendAcElementInfo
189 * by intermediary automatically during deleting of the instance
190 * by an update when the instance is in UNDEPLOYED state (changing the elementId)
192 The 'Out Properties' will be **not cleaned** by intermediary:
194 * during DEPLOIYNG (Out Properties will be take from last changes matching by elementId)
196 * during LOCKING/UNLOCKING
197 * during UPDATING/MIGRATING
199 Is allowed to the participant to read all In/Out Properties and state of all instances handled by the participant using the method getAutomationCompositions.
200 The following code is an example how to update the property 'myProperty' and send to ACM-runtime:
204 var acElement = intermediaryApi.getAutomationCompositionElement(automationCompositionId, elementId);
205 var outProperties = acElement.getOutProperties();
206 outProperties.put("myProperty", myProperty);
207 intermediaryApi.sendAcElementInfo(automationCompositionId, elementId, acElement.getUseState(), acElement.getOperationalState(), outProperties);
211 Restart methods handle the scenario when participant shut down and restart.
212 During RESTARTING, compositions and instances will be stored in participant memory with In/Out Properties, 'useState' and 'operationalState'.
213 The method handleRestartComposition will be called for each composition and will be present the 'state' at the time the participant shut down.
214 The method handleRestartInstance will be called for each instance element and will be present the 'deployState' and the 'lockState' at the time the participant shut down.
216 In ONAP, the following participants are already implemented in java spring boot for various requirements. The maven modules
217 can be referred here:
219 * `HTTP participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-http>`_.
220 * `Kubernetes participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-kubernetes>`_.
221 * `Policy participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-policy>`_.
222 * `A1PMS participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-a1pms>`_.
223 * `Kserve participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-kserve>`_.
225 Example of Implementation
226 -------------------------
228 This following code is an example of My First Participant:
233 The Application class is configured to add the "org.onap.policy.clamp.acm.participant.intermediary" package in SpringBoot component scanning.
237 @SpringBootApplication
239 "org.onap.policy.clamp.acm.participant.myfirstparticipant",
240 "org.onap.policy.clamp.acm.participant.intermediary"
242 @ConfigurationPropertiesScan("org.onap.policy.clamp.acm.participant.myfirstparticipant.parameters")
243 public class MyFirstParticipantApplication {
245 public static void main(String[] args) {
246 SpringApplication.run(Application.class, args);
250 The Participant Parameters class implements the mandatory interface ParticipantParameters.
251 It could contains additional parameters.
258 @ConfigurationProperties(prefix = "participant")
259 public class ParticipantSimParameters implements ParticipantParameters {
262 private String myparameter;
266 private ParticipantIntermediaryParameters intermediaryParameters;
269 The following example shows the topic parameters and the additional 'myparameter'.
274 myparameter: my parameter
275 intermediaryParameters:
276 reportingTimeIntervalMs: 120000
277 description: Participant Description
278 participantId: 101c62b3-8918-41b9-a747-d21eb79c6c90
279 clampAutomationCompositionTopics:
281 - topic: POLICY-ACRUNTIME-PARTICIPANT
283 - ${topicServer:localhost}
284 topicCommInfrastructure: dmaap
287 - topic: POLICY-ACRUNTIME-PARTICIPANT
289 - ${topicServer:localhost}
290 topicCommInfrastructure: dmaap
291 participantSupportedElementTypes:
293 typeName: org.onap.policy.clamp.acm.MyFirstAutomationCompositionElement
297 The following example shows the Handler implementation and how could be the implemented the mandatory notifications.
302 @RequiredArgsConstructor
303 public class MyFirstAcElementHandler implements AutomationCompositionElementListener {
305 private final ParticipantIntermediaryApi intermediaryApi;
308 public void deploy(UUID automationCompositionId, AcElementDeploy element, Map<String, Object> properties)
309 throws PfModelException {
311 // TODO deploy process
313 if (isDeploySuccess()) {
314 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
315 DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Deployed");
317 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
318 DeployState.UNDEPLOYED, null, StateChangeResult.FAILED, "Deploy failed!");
323 public void undeploy(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException {
324 LOGGER.debug("undeploy call");
326 // TODO undeploy process
328 if (isUndeploySuccess()) {
329 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
330 automationCompositionElementId, DeployState.UNDEPLOYED, null, StateChangeResult.NO_ERROR,
333 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
334 automationCompositionElementId, DeployState.DEPLOYED, null, StateChangeResult.FAILED,
340 public void lock(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException {
344 if (isLockSuccess()) {
345 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
346 automationCompositionElementId, null, LockState.LOCKED, StateChangeResult.NO_ERROR, "Locked");
348 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
349 automationCompositionElementId, null, LockState.UNLOCKED, StateChangeResult.FAILED, "Lock failed!");
354 public void unlock(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException {
356 // TODO unlock process
358 if (isUnlockSuccess()) {
359 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
360 automationCompositionElementId, null, LockState.UNLOCKED, StateChangeResult.NO_ERROR, "Unlocked");
362 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
363 automationCompositionElementId, null, LockState.LOCKED, StateChangeResult.FAILED, "Unlock failed!");
368 public void delete(UUID automationCompositionId, UUID automationCompositionElementId) throws PfModelException {
370 // TODO delete process
372 if (isDeleteSuccess()) {
373 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
374 automationCompositionElementId, DeployState.DELETED, null, StateChangeResult.NO_ERROR, "Deleted");
376 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId,
377 automationCompositionElementId, DeployState.UNDEPLOYED, null, StateChangeResult.FAILED,
383 public void update(UUID automationCompositionId, AcElementDeploy element, Map<String, Object> properties)
384 throws PfModelException {
386 // TODO update process
388 if (isUpdateSuccess()) {
389 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
390 DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Updated");
392 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
393 DeployState.DEPLOYED, null, StateChangeResult.FAILED, "Update failed!");
398 public void migrate(UUID automationCompositionId, AcElementDeploy element, UUID compositionTargetId,
399 Map<String, Object> properties) throws PfModelException {
401 // TODO migrate process
403 if (isMigrateSuccess()) {
404 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
405 DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Migrated");
407 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
408 DeployState.DEPLOYED, null, StateChangeResult.FAILED, "Migrate failed!");
413 public void prime(UUID compositionId, List<AutomationCompositionElementDefinition> elementDefinitionList)
414 throws PfModelException {
416 // TODO prime process
418 if (isPrimeSuccess()) {
419 intermediaryApi.updateCompositionState(compositionId, AcTypeState.PRIMED, StateChangeResult.NO_ERROR,
422 intermediaryApi.updateCompositionState(compositionId, AcTypeState.COMMISSIONED, StateChangeResult.FAILED,
428 public void deprime(UUID compositionId) throws PfModelException {
430 // TODO deprime process
432 if (isDeprimeSuccess()) {
433 intermediaryApi.updateCompositionState(compositionId, AcTypeState.COMMISSIONED, StateChangeResult.NO_ERROR,
436 intermediaryApi.updateCompositionState(compositionId, AcTypeState.PRIMED, StateChangeResult.FAILED,
443 public void handleRestartComposition(UUID compositionId,
444 List<AutomationCompositionElementDefinition> elementDefinitionList, AcTypeState state)
445 throws PfModelException {
449 prime(compositionId, elementDefinitionList);
453 // TODO restart process
455 deprime(compositionId);
459 // TODO restart process
461 intermediaryApi.updateCompositionState(compositionId, state, StateChangeResult.NO_ERROR, "Restarted");
466 public void handleRestartInstance(UUID automationCompositionId, AcElementDeploy element,
467 Map<String, Object> properties, DeployState deployState, LockState lockState) throws PfModelException {
469 // TODO restart process
471 if (DeployState.DEPLOYING.equals(deployState)) {
472 deploy(automationCompositionId, element, properties);
475 if (DeployState.UNDEPLOYING.equals(deployState)) {
476 undeploy(automationCompositionId, element.getId());
479 if (DeployState.UPDATING.equals(deployState)) {
480 update(automationCompositionId, element, properties);
483 if (DeployState.DELETING.equals(deployState)) {
484 delete(automationCompositionId, element.getId());
487 if (LockState.LOCKING.equals(lockState)) {
488 lock(automationCompositionId, element.getId());
491 if (LockState.UNLOCKING.equals(lockState)) {
492 unlock(automationCompositionId, element.getId());
495 intermediaryApi.updateAutomationCompositionElementState(automationCompositionId, element.getId(),
496 deployState, lockState, StateChangeResult.NO_ERROR, "Restarted");
500 AC Element states in failure scenarios
501 --------------------------------------
503 During the execution of any state change order, there is always a possibility of failures or exceptions that can occur in the participant.
504 This can be tackled by the followed approaches.
506 The participant implementation can handle the exception and revert back the appropriate AC element state, by invoking the
507 'updateAutomationCompositionElementState' api from the participant intermediary.
509 Alternatively, the participant can simply throw a PfModelException from its implementation which will be handled by the participant intermediary.
510 The intermediary handles this exception and rolls back the AC element to its previous state with the appropriate stateChange Result.
511 Please refer the following table for the state change reversion that happens in the participant intermediary for the AC elements.
513 ================== ==================
514 **Error Scenario** **State Reverted**
515 ================== ==================
516 Prime fails Commissoned
520 Deploy fails Undeployed
522 Undeploy fails Deployed
524 Update fails Deployed
526 Delete fails Undeployed
531 ================== ==================
533 Considering the above mentioned behavior of the participant Intermediary, it is the responsibility of the developer to tackle the
534 error scenarios in the participant with the suitable approach.
537 If the participant tries to undeploy an element which doesn’t exist in the system any more (due to various other external factors),
538 it could update the element state to ‘undeployed’ using the Intermediary api.