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.
46 (**Note**: In order to avoid a connection to Kafka when Unit Tests are running, set topicCommInfrastructure: NOOP in properties file for tests).
47 The following example shows the topic parameters set for using Kafka.
51 clampAutomationCompositionTopics:
54 topic: POLICY-ACRUNTIME-PARTICIPANT
56 - ${topicServer:localhost}:9092
57 topicCommInfrastructure: kafka
61 topic: POLICY-ACRUNTIME-PARTICIPANT
63 - ${topicServer:localhost}:9092
64 topicCommInfrastructure: kafka
66 4. participantSupportedElementTypes - This property takes a list of typeName and typeVersion fields to define the types of AC elements the participant deals with.
67 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.
71 participantSupportedElementTypes:
73 typeName: org.onap.policy.clamp.acm.PolicyAutomationCompositionElement
76 Interfaces to Implement
77 -----------------------
78 AutomationCompositionElementListener:
79 Every participant should implement a handler class that implements the AutomationCompositionElementListener interface
80 from the Participant Intermediary. The intermediary listener class listens for the incoming events from the ACM-runtime
81 and invoke the handler class implementations for various operations. This class implements the methods for deploying,
82 undeploying, locking, unlocking , deleting, updating, migrating, priming, depriming requests that are coming from the ACM-runtime.
83 The methods are as follows.
87 1. void deploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException;
88 2. void undeploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException;
89 3. void lock(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException;
90 4. void unlock(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException;
91 5. void delete(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException;
92 6. void update(CompositionElementDto compositionElement, InstanceElementDto instanceElement, InstanceElementDto instanceElementUpdated) throws PfModelException;
93 7. void prime(CompositionDto composition) throws PfModelException;
94 8. void deprime(CompositionDto composition) throws PfModelException;
95 9. void handleRestartComposition(CompositionDto composition, AcTypeState state) throws PfModelException;
96 10. void handleRestartInstance(CompositionElementDto compositionElement, InstanceElementDto instanceElement, DeployState deployState, LockState lockState) throws PfModelException;
97 11. void migrate(CompositionElementDto compositionElement, CompositionElementDto compositionElementTarget, InstanceElementDto instanceElement, InstanceElementDto instanceElementMigrate) throws PfModelException;
99 These method from the interface are implemented independently as per the user requirement. These methods after handling the
100 appropriate requests should also invoke the intermediary's publisher apis to notify the ACM-runtime with the acknowledgement events.
102 ParticipantParameters:
103 Every participant should implement a properties class that contains the values of all Intermediary parameter properties.
104 This class implements the method getIntermediaryParameters that returns 'ParticipantIntermediaryParameters' object. The method is as follows.
108 ParticipantIntermediaryParameters getIntermediaryParameters()
110 Abstract class AcElementListenerV1
111 ----------------------------------
112 This abstract class is introduced to help to maintain the java backward compatibility with AutomationCompositionElementListener implemented in 7.1.0 version.
113 So developers can decide to align to new functionality later. Any new functionality in the future will be wrapped by this class.
115 The Abstract class AcElementListenerV1 supports the follow methods.
119 1. void undeploy(UUID instanceId, UUID elementId) throws PfModelException;
120 2. void deploy(UUID instanceId, AcElementDeploy element, Map<String, Object> inProperties) throws PfModelException;
121 3. void lock(UUID instanceId, UUID elementId) throws PfModelException;
122 4. void unlock(UUID instanceId, UUID elementId) throws PfModelException;
123 5. void delete(UUID instanceId, UUID elementId) throws PfModelException;
124 6. void update(UUID instanceId, AcElementDeploy element, Map<String, Object> inProperties) throws PfModelException;
125 7. void prime(UUID compositionId, List<AutomationCompositionElementDefinition> elementDefinitionList) throws PfModelException;
126 8. void deprime(UUID compositionId) throws PfModelException;
127 9. void handleRestartComposition(UUID compositionId, List<AutomationCompositionElementDefinition> elementDefinitionList, AcTypeState state) throws PfModelException;
128 10. void handleRestartInstance(UUID instanceId, AcElementDeploy element, Map<String, Object> properties, DeployState deployState, LockState lockState) throws PfModelException;
129 11. void migrate(UUID instanceId, AcElementDeploy element, UUID compositionTargetId, Map<String, Object> properties) throws PfModelException;
131 **Note**: this class needs intermediaryApi and it should be passed by constructor. It is declared as protected and can be used.
132 Default implementation are supported for the methods: lock, unlock, update, migrate, delete, prime, deprime, handleRestartComposition and handleRestartInstance.
134 Un example of AutomationCompositionElementHandler implemented in 7.1.0 version and how to use AcElementListenerV1 abstract class:
139 @RequiredArgsConstructor
140 public class AutomationCompositionElementHandler implements AutomationCompositionElementListener {
142 private final ParticipantIntermediaryApi intermediaryApi;
143 private final otherService otherService;
144 ..............................
148 public class AutomationCompositionElementHandler extends AcElementListenerV1 {
150 private final OtherService otherService;
152 public AutomationCompositionElementHandler(ParticipantIntermediaryApi intermediaryApi, OtherService otherService) {
153 super(intermediaryApi);
154 this.otherService = otherService;
156 ..............................
166 public class AutomationCompositionElementHandler implements AutomationCompositionElementListener {
169 private ParticipantIntermediaryApi intermediaryApi;
172 private otherService otherService;
173 ..............................
177 public class AutomationCompositionElementHandler extends AcElementListenerV1 {
180 private otherService otherService;
182 public AutomationCompositionElementHandler(ParticipantIntermediaryApi intermediaryApi) {
183 super(intermediaryApi);
185 ..............................
188 Abstract class AcElementListenerV2
189 ----------------------------------
190 This abstract class is introduced to help to maintain the java backward compatibility with AutomationCompositionElementListener from new releases.
191 Any new functionality in the future will be wrapped by this class.
193 **Note**: this class needs intermediaryApi and it should be passed by constructor. It is declared as protected and can be used.
194 Default implementation are supported for the methods: lock, unlock, update, migrate, delete, prime, deprime, handleRestartComposition and handleRestartInstance.
199 ParticipantIntermediaryApi:
200 The participant intermediary api has the following methods that can be invoked from the participant for the following purposes.
202 #. The requested operations are completed in the handler class and the ACM-runtime needs to be notified.
203 #. Collect all instances data.
204 #. Send out Properties to ACM-runtime.
206 The methods are as follows:
208 This following methods could be invoked to fetch data during each operation in the participant.
212 1. Map<UUID, AutomationComposition> getAutomationCompositions();
213 2. AutomationComposition getAutomationComposition(UUID instanceId);
214 3. AutomationCompositionElement getAutomationCompositionElement(UUID instanceId, UUID elementId);
215 4. Map<UUID, Map<ToscaConceptIdentifier, AutomationCompositionElementDefinition>> getAcElementsDefinitions();
216 5. Map<ToscaConceptIdentifier, AutomationCompositionElementDefinition> getAcElementsDefinitions(UUID compositionId);
217 6. AutomationCompositionElementDefinition getAcElementDefinition(UUID compositionId, ToscaConceptIdentifier elementId);
219 This following methods are invoked to update the outProperties during each operation in the participant.
223 1. void sendAcDefinitionInfo(UUID compositionId, ToscaConceptIdentifier elementId, Map<String, Object> outProperties);
224 2. void sendAcElementInfo(UUID instanceId, UUID elementId, String useState, String operationalState, Map<String, Object> outProperties);
226 This following methods are invoked to update the AC element state or AC element definition state after each operation is completed in the participant.
230 1. void updateAutomationCompositionElementState(UUID instanceId, UUID elementId, DeployState deployState, LockState lockState, StateChangeResult stateChangeResult, String message);
231 2. void updateCompositionState(UUID compositionId, AcTypeState state, StateChangeResult stateChangeResult, String message);
233 In/Out composition Properties
234 -----------------------------
235 The 'Common Properties' could be created or updated by ACM-runtime.
236 Participants will receive that Properties during priming and deprime events by CompositionDto class.
241 public void prime(CompositionDto composition) throws PfModelException {
242 for (var entry : composition.inPropertiesMap().entrySet()) {
243 var elementDefinitionId = entry.getKey();
244 var inProperties = entry.getValue();
250 Participants will receive the Properties related to the element definition by CompositionElementDto class.
255 public void deploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException {
256 var inCompositionProperties = compositionElement.inProperties();
260 The 'Out Properties' could be created or updated by participants. ACM-runtime will receive that Properties during ParticipantStatus event.
261 The participant can trigger this event using the method sendAcDefinitionInfo.
263 Participants will receive that outProperties during priming and deprime events by CompositionDto class.
268 public void deprime(CompositionDto composition) throws PfModelException {
269 for (var entry : composition.outPropertiesMap().entrySet()) {
270 var elementDefinitionId = entry.getKey();
271 var outProperties = entry.getValue();
277 Participants will receive the outProperties related to the element definition by CompositionElementDto class.
282 public void deploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException {
283 var outCompositionProperties = compositionElement.outProperties();
287 Is allowed to the participant to read all In/Out Properties of all compositions handled by the participant using the method getAcElementsDefinitions.
288 The following code is an example how to update the property 'myProperty' and send to ACM-runtime:
292 var acElement = intermediaryApi.getAcElementDefinition(compositionId, elementDefinitionId);
293 var outProperties = acElement.getOutProperties();
294 outProperties.put("myProperty", myProperty);
295 intermediaryApi.sendAcDefinitionInfo(compositionId, elementDefinitionId, outProperties);
297 In/Out instance Properties
298 --------------------------
299 The 'In/Out Properties' are stored into the instance elements, so each element has its own In/Out Properties.
301 The 'In Properties' could be created or updated by ACM-runtime. Participants will receive that Properties during deploy and update events.
303 The 'Out Properties' could be created or updated by participants. ACM-runtime will receive that Properties during ParticipantStatus event.
304 The participant can trigger this event using the method sendAcElementInfo. The 'useState' and 'operationalState' can be used as well.
305 The 'Out Properties' could be **cleaned**:
307 * by the participant using the method sendAcElementInfo
308 * by intermediary automatically during deleting of the instance
309 * by an update when the instance is in UNDEPLOYED state (changing the elementId)
311 The 'Out Properties' will be **not cleaned** by intermediary:
313 * during DEPLOIYNG (Out Properties will be take from last changes matching by elementId)
315 * during LOCKING/UNLOCKING
316 * during UPDATING/MIGRATING
318 Participants will receive the in/out instance Properties related to the element by InstanceElementDto class.
323 public void deploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement) throws PfModelException {
324 var inProperties = instanceElement.inProperties();
325 var outProperties = instanceElement.outProperties();
329 Is allowed to the participant to read all In/Out Properties and state of all instances handled by the participant using the method getAutomationCompositions.
330 The following code is an example how to update the property 'myProperty' and send to ACM-runtime:
334 var acElement = intermediaryApi.getAutomationCompositionElement(instanceId, elementId);
335 var outProperties = acElement.getOutProperties();
336 outProperties.put("myProperty", myProperty);
337 intermediaryApi.sendAcElementInfo(instanceId, elementId, acElement.getUseState(), acElement.getOperationalState(), outProperties);
339 **Note**: In update and migrate Participants will receive the instance Properties before the merge (instanceElement) and the instance Properties merged (instanceElementUpdated / instanceElementMigrate).
343 Restart methods handle the scenario when participant shut down and restart.
344 During RESTARTING, compositions and instances will be stored in participant memory with In/Out Properties, 'useState' and 'operationalState'.
345 The method handleRestartComposition will be called for each composition and will be present the 'state' at the time the participant shut down.
346 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.
348 In ONAP, the following participants are already implemented in java spring boot for various requirements. The maven modules
349 can be referred here:
351 * `HTTP participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-http>`_.
352 * `Kubernetes participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-kubernetes>`_.
353 * `Policy participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-policy>`_.
354 * `A1PMS participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-a1pms>`_.
355 * `Kserve participant <https://github.com/onap/policy-clamp/tree/master/participant/participant-impl/participant-impl-kserve>`_.
357 Example of Implementation
358 -------------------------
360 This following code is an example of My First Participant:
365 The Application class is configured to add the "org.onap.policy.clamp.acm.participant.intermediary" package in SpringBoot component scanning.
369 @SpringBootApplication
371 "org.onap.policy.clamp.acm.participant.myfirstparticipant",
372 "org.onap.policy.clamp.acm.participant.intermediary"
374 @ConfigurationPropertiesScan("org.onap.policy.clamp.acm.participant.myfirstparticipant.parameters")
375 public class MyFirstParticipantApplication {
377 public static void main(String[] args) {
378 SpringApplication.run(Application.class, args);
382 The Participant Parameters class implements the mandatory interface ParticipantParameters.
383 It could contains additional parameters.
390 @ConfigurationProperties(prefix = "participant")
391 public class ParticipantSimParameters implements ParticipantParameters {
394 private String myparameter;
398 private ParticipantIntermediaryParameters intermediaryParameters;
401 The following example shows the topic parameters and the additional 'myparameter'.
406 myparameter: my parameter
407 intermediaryParameters:
408 reportingTimeIntervalMs: 120000
409 description: Participant Description
410 participantId: 101c62b3-8918-41b9-a747-d21eb79c6c90
411 clampAutomationCompositionTopics:
413 - topic: POLICY-ACRUNTIME-PARTICIPANT
415 - ${topicServer:localhost}:9092
416 topicCommInfrastructure: kafka
419 - topic: POLICY-ACRUNTIME-PARTICIPANT
421 - ${topicServer:localhost}:9092
422 topicCommInfrastructure: kafka
423 participantSupportedElementTypes:
425 typeName: org.onap.policy.clamp.acm.MyFirstAutomationCompositionElement
429 The following example shows the Handler implementation and how could be the implemented the mandatory notifications.
434 public class AutomationCompositionElementHandler extends AcElementListenerV2 {
437 public void deploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
438 throws PfModelException {
440 // TODO deploy process
442 if (isDeploySuccess()) {
443 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
444 instanceElement.elementId(), DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR,
447 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
448 instanceElement.elementId(), DeployState.UNDEPLOYED, null, StateChangeResult.FAILED,
454 public void undeploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
455 throws PfModelException {
457 // TODO undeploy process
459 if (isUndeploySuccess()) {
460 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
461 instanceElement.elementId(), DeployState.UNDEPLOYED, null, StateChangeResult.NO_ERROR,
464 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
465 instanceElement.elementId(), DeployState.DEPLOYED, null, StateChangeResult.FAILED,
471 public void lock(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
472 throws PfModelException {
476 if (isLockSuccess()) {
477 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
478 instanceElement.elementId(), null, LockState.LOCKED, StateChangeResult.NO_ERROR, "Locked");
480 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
481 instanceElement.elementId(), null, LockState.UNLOCKED, StateChangeResult.FAILED, "Lock failed!");
486 public void unlock(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
487 throws PfModelException {
489 // TODO unlock process
491 if (isUnlockSuccess()) {
492 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
493 instanceElement.elementId(), null, LockState.UNLOCKED, StateChangeResult.NO_ERROR, "Unlocked");
495 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
496 instanceElement.elementId(), null, LockState.LOCKED, StateChangeResult.FAILED, "Unlock failed!");
501 public void delete(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
502 throws PfModelException {
504 // TODO delete process
506 if (isDeleteSuccess()) {
507 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
508 instanceElement.elementId(), DeployState.DELETED, null, StateChangeResult.NO_ERROR, "Deleted");
510 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
511 instanceElement.elementId(), DeployState.UNDEPLOYED, null, StateChangeResult.FAILED,
517 public void update(CompositionElementDto compositionElement, InstanceElementDto instanceElement,
518 InstanceElementDto instanceElementUpdated) throws PfModelException {
520 // TODO update process
522 if (isUpdateSuccess()) {
523 intermediaryApi.updateAutomationCompositionElementState(
524 instanceElement.instanceId(), instanceElement.elementId(),
525 DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Updated");
527 intermediaryApi.updateAutomationCompositionElementState(
528 instanceElement.instanceId(), instanceElement.elementId(),
529 DeployState.DEPLOYED, null, StateChangeResult.FAILED, "Update failed!");
534 public void migrate(CompositionElementDto compositionElement, CompositionElementDto compositionElementTarget,
535 InstanceElementDto instanceElement, InstanceElementDto instanceElementMigrate)
536 throws PfModelException
538 // TODO migrate process
540 if (isMigrateSuccess()) {
541 intermediaryApi.updateAutomationCompositionElementState(
542 instanceElement.instanceId(), instanceElement.elementId(),
543 DeployState.DEPLOYED, null, StateChangeResult.NO_ERROR, "Migrated");
545 intermediaryApi.updateAutomationCompositionElementState(
546 instanceElement.instanceId(), instanceElement.elementId(),
547 DeployState.DEPLOYED, null, StateChangeResult.FAILED, "Migrate failed!");
552 public void prime(CompositionDto composition) throws PfModelException {
554 // TODO prime process
556 if (isPrimeSuccess()) {
557 intermediaryApi.updateCompositionState(composition.compositionId(),
558 AcTypeState.PRIMED, StateChangeResult.NO_ERROR, "Primed");
560 intermediaryApi.updateCompositionState(composition.compositionId(),
561 AcTypeState.COMMISSIONED, StateChangeResult.FAILED, "Prime failed!");
566 public void deprime(CompositionDto composition) throws PfModelException {
568 // TODO deprime process
570 if (isDeprimeSuccess()) {
571 intermediaryApi.updateCompositionState(composition.compositionId(), AcTypeState.COMMISSIONED,
572 StateChangeResult.NO_ERROR, "Deprimed");
574 intermediaryApi.updateCompositionState(composition.compositionId(), AcTypeState.PRIMED,
575 StateChangeResult.FAILED, "Deprime failed!");
581 public void handleRestartComposition(CompositionDto composition, AcTypeState state) throws PfModelException {
583 // TODO restart process
586 case PRIMING -> prime(composition);
587 case DEPRIMING -> deprime(composition);
588 default -> intermediaryApi
589 .updateCompositionState(composition.compositionId(), state, StateChangeResult.NO_ERROR, "Restarted");
594 public void handleRestartInstance(CompositionElementDto compositionElement, InstanceElementDto instanceElement,
595 DeployState deployState, LockState lockState) throws PfModelException {
597 // TODO restart process
599 if (DeployState.DEPLOYING.equals(deployState)) {
600 deploy(compositionElement, instanceElement);
603 if (DeployState.UNDEPLOYING.equals(deployState)) {
604 undeploy(compositionElement, instanceElement);
607 if (DeployState.UPDATING.equals(deployState)) {
608 update(compositionElement, instanceElement, instanceElement);
611 if (DeployState.DELETING.equals(deployState)) {
612 delete(compositionElement, instanceElement);
615 if (LockState.LOCKING.equals(lockState)) {
616 lock(compositionElement, instanceElement);
619 if (LockState.UNLOCKING.equals(lockState)) {
620 unlock(compositionElement, instanceElement);
623 intermediaryApi.updateAutomationCompositionElementState(instanceElement.instanceId(),
624 instanceElement.elementId(), deployState, lockState, StateChangeResult.NO_ERROR, "Restarted");
628 AC Element states in failure scenarios
629 --------------------------------------
631 During the execution of any state change order, there is always a possibility of failures or exceptions that can occur in the participant.
632 This can be tackled by the followed approaches.
634 The participant implementation can handle the exception and revert back the appropriate AC element state, by invoking the
635 'updateAutomationCompositionElementState' api from the participant intermediary.
637 Alternatively, the participant can simply throw a PfModelException from its implementation which will be handled by the participant intermediary.
638 The intermediary handles this exception and rolls back the AC element to its previous state with the appropriate stateChange Result.
639 Please refer the following table for the state change reversion that happens in the participant intermediary for the AC elements.
641 ================== ==================
642 **Error Scenario** **State Reverted**
643 ================== ==================
644 Prime fails Commissoned
648 Deploy fails Undeployed
650 Undeploy fails Deployed
652 Update fails Deployed
654 Delete fails Undeployed
660 Migrate fails Deployed
661 ================== ==================
663 Considering the above mentioned behavior of the participant Intermediary, it is the responsibility of the developer to tackle the
664 error scenarios in the participant with the suitable approach.
667 If the participant tries to undeploy an element which doesn’t exist in the system any more (due to various other external factors),
668 it could update the element state to ‘undeployed’ using the Intermediary api.