Actor Design Overview
Intro
An actor/operation is any ONAP component that an Operational Policy can use to control a VNF/VM/etc. during execution of a control loop operational policy when a Control Loop Event is triggered.
An Actor Service object contains one or more Actor objects, which are found and created using ServiceLoader. Each Actor object, in turn, creates one or more Operator objects. All of these components, the Actor Service, the Actor, and the Operator are typically singletons that are created once, at start-up (or on the first request). The Actor Service includes several methods, configure(), start(), and stop(), which are cascaded to the Actors and then to the Operators.
Operation objects, on the other hand, are not singletons; a new Operation object is created for each operation that an application wishes to perform. For instance, if an application wishes to use the “SO” Actor to add two new modules, then two separate Operation objects would be created, one for each module.
Actors are configured by invoking the Actor Service configure() method, passing it a set of properties. The configure() method extracts the properties that are relevant to each Actor and passes them to the Actor’s configure() method. Similarly, the Actor’s configure() method extracts the properties that are relevant to each Operator and passes them to the Operator’s configure() method. Note: Actors typically extract “default” properties from their respective property sets and include those when invoking each Operator’s configure() method.
Once the Actor Service has been configured, it can be started via start(). It will then continue to run until no longer needed, at which point stop() can be invoked.
Note: it is possible to create separate instances of an Actor Service, each with its own set of properties. In that case, each Actor Service will get its own instances of Actors and Operators.
Components
This section describes things to consider when creating a new Actor/Operator.
Actor
The constructor should use addOperator() to add operators
By convention, the name of the actor is specified by a static field, “NAME”
An actor is registered via the Java ServiceLoader by including its jar on the classpath and adding its class name to this file, typically contained within the jar:
onap.policy.controlloop.actorServiceProvider.spi
Actor loading is ordered, so that those having a lower (i.e., earlier) sequence number are loaded first. If a later actor has the same name as one that has already been loaded, a warning will be generated and the later actor discarded. This makes it possible for an organization to override an actor implementation
An implementation for a specific Actor will typically be derived from HttpActor or BidirectionalTopicActor, depending whether it is HTTP/REST-based or DMaaP-topic-based. These super classes provide most of the functionality needed to configure the operators, extracting operator-specific properties and adding default, actor-level properties
Operator
Typically, developers don’t have to implement any Operator classes; they just use HttpOperator or BidirectionalTopicOperator
Operation
Most operations require guard checks to be performed first. Thus, at a minimum, they should override startPreprocessorAsync() and have it invoke startGuardAsync()
In addition, if the operation depends on data being previously gathered and placed into the context, then it should override startPreprocessorAsync() and have it invoke obtain(). Note: obtain() and the guard can be performed in parallel by using the allOf() method. If the guard happens to depend on the same data, then it will block until the data is available, and then continue; the invoker need not deal with the dependency
Subclasses will typically derive from HttpOperation or BidirectionalTopicOperation, though if neither of those suffice, then they can extend OperationPartial, or even just implement a raw Operation. OperationPartial is the super class of HttpOperation and BidirectionalTopicOperation and provides most of the methods used by the Operation subclasses, including a number of utility methods (e.g., cancellable allOf)
Operation subclasses should be written in a way so-as to avoid any blocking I/O. If this proves too difficult, then the implementation should override doOperation() instead of startOperationAsync()
Operations return a “future” when start() is invoked. Typically, if the “future” is canceled, then any outstanding operation should be canceled. For instance, HTTP connections should be closed without waiting for a response
If an operation sets the outcome to “FAILURE”, it will be automatically retried; other failure types are not retried
ControlLoopParams
Identifies the operation to be performed
Includes timeout and retry information, though the actors typically provide default values if they are not specified in the parameters
Includes the event “context”
Includes “Policy” fields (e.g., “actor” and “operation”)
Context (aka, Event Context)
Includes:
the original onset event
enrichment data associated with the event
results of A&AI queries
XxxParams and XxxConfig
XxxParams objects are POJOs into which the property Maps are decoded when configuring actors or operators
XxxConfig objects contain a single Operator’s (or Actor’s) configuration information, based on what was in the XxxParams. For instance, the HttpConfig contains a reference to the HttpClient that is used to perform HTTP operations, while the associated HttpParams just contains the name of the HttpClient. XxxConfig objects are shared by all operations created by a single Operator. As a result, it should not contain any data associated with an individual operation; such data should be stored within the Operation object, itself
Junit tests
Operation Tests may choose to subclass from BasicHttpOperation, which provides some supporting utilities and mock objects
Should include a test to verify that the Actor, and possibly each Operator, can be retrieved via an Actor Service
Tests with an actual REST server are performed within HttpOperationTest, so need not be repeated in subclasses. Instead, they can catch the callback to the get(), post(), etc., methods and pass the rawResponse to it there. That being said, a number of actors spin up a simulator to verify end-to-end request/response processing
Clients (e.g., drools-applications)
When using callbacks, a client may want to use the isFor() method to verify that the outcome is for the desired operation, as callbacks are invoked with the outcome of all operations performed, including any preprocessor steps
Flow of operation
PDP:
Populates a ControlLoopParams using ControlLoopParams.builder()
Invokes start() on the ControlLoopParams
ControlLoopParams:
Finds the actor/operator
Uses it to invoke buildOperation()
Invokes start() on the Operation
Operation:
start() invokes startPreprocessorAsync() and then startOperationAsync()
Exceptions that occur while constructing the operation pipeline propagate back to the client that invoked start()
Exceptions that occur while executing the operation pipeline are caught and turned into an OperationOutcome whose result is FAILURE_EXCEPTION. In addition, the “start” callback (i.e., specified via the ControlLoopParams) will be invoked, if it hasn’t been invoked yet, and then the “complete” callback will be invoked
By default, startPreprocessorAsync() does nothing, thus most subclasses will override it to:
Do any A&AI query that is needed (beyond enrichment, which is already available in the Context)
Use Context obtain() to request the data asynchronously
Invoke startGuardAsync()
By default, startGuardAsync() will simply perform a guard check, passing it the “standard” payload
Subclasses may override makeGuardPayload() to add extra fields to the payload (e.g., some SO operations add the VF count)
If any preprocessing step fails, then the “start” and “complete” callbacks will be invoked to indicate a failure of the operation as a whole. Otherwise, the flow will continue on to startOperationAsync(), after the “start” callback is invoked
StartOperationAsync() will perform whatever needs to be done to start the operation
Once it completes, the “complete” callback will be invoked with the outcome of the operation. StartOperationAsync() should not invoke the callback, as that is handled automatically by OperationPartial, which is the superclass of most Operations