.. This work is licensed under a Creative Commons Attribution 4.0 International License.

.. _actors-overview-label:

#####################
Actor Design Overview
#####################

.. contents::

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.

.. image:: topview.png

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