EUREKA-761 Design solution for "desired state application entitlement"

EUREKA-761 Design solution for "desired state application entitlement"

Problem Statement

Currently, management of application entitlements requires explicit operation specification (POST/PUT/DELETE) and handling various error scenarios manually. There is need to implement a more intelligent, desired-state approach similar to Okapi's model.

Current Behavior:

  • Users must specify explicit operations (POST/PUT/DELETE) for application management

  • Manual handling of multiple error scenarios:

    • Already entitled applications

    • Conflicting application versions

    • Invalid upgrade operations for non-entitled applications

  • Process is error-prone and requires detailed knowledge of current state

Proposed Solution: Implement a desired-state management system where:

  • Users specify only the target state (which applications should be entitled)

  • System automatically determines required operations: “entitle”, “upgrade”, “revoke”

  • mgr-tenant-entitlements (MTE) handles state transitions automatically

Scope

https://folio-org.atlassian.net/browse/EUREKA-757

  • Capture low-level design/implementation details

  • Create JIRAs for the implementation (or update/augment EUREKA-757)

  • Find particular places in the codebase where changes should be made, and what those changes/adjustments should be.

  • Describe the new workflow (e.g. w/ flow or sequence diagrams), e.g. how does MTE determine which actions are required by looking at the desired and present states.

  • Revisit validators and describe if they are still relevant, if some new ones need to be provided, or existing ones must be removed.

  • This work is introduced as a new API. Existing APIs for entitlement, revocation, upgrade are not changed.

    • The logic for the new API can/should reuse existing code if/where possible.

Design

What is Applications Desired-State by example

The following diagram shows the initial set of applications enabled for a tenant and the desired final set. The "apply state" action represents the orchestration process that mgr-tenant-entitlements would need to perform.

To move from the Initial State to the Desired State, the system must calculate and execute the following operations:

  1. Entitle:

    • App6 v1.0

  2. Upgrade:

    • App1: from v1.0 to v2.0

    • App4: from v1.5 to v1.6

  3. Revoke:

    • App2 v2.1

    • App3 v1.1

    • App5 v1.1

This calculated set of actions – Upgrades, Revokes, and Entitlements – forms the execution plan that the new flow in mgr-tenant-entitlements will orchestrate to achieve the desired state.

Application Entitlement States

In scope of Entitlement process an application can be in three different states:

  • Entitled

  • Upgraded

  • Revoked

The following diagram models the lifecycle of a single application (e.g., App1) as it is managed for a specific tenant.

app-states.png

 

  • Entitle (Initial): The lifecycle begins when a new application is installed. An Entitle action transitions it from a non-existent state [*] to the entitled state.

    • Example: App6 is introduced and installed as v1.0, moving it to the entitled state.

  • Upgrade from entitled: An application in the entitled state can be upgraded to a newer version. This Upgrade action moves it to the upgraded state.

    • Example: App1 transitions from v1.0 (entitled) to v2.0 (upgraded).

  • Upgrade from upgraded: An application can be upgraded multiple times. A subsequent Upgrade action on an application already in the upgraded state causes a self-transition.

    • Example: If App1 were later moved from v2.0 to v2.1, it would transition from upgraded back to upgraded.

  • Revoke: An active application (in either entitled or upgraded state) can be disabled. The Revoke action moves it to the revoked state, making it unavailable to the tenant.

    • Example: App2 v2.1, App3 v1.1, and App5 v1.1 are all moved to the revoked state.

  • Re-entitle from revoked: A disabled application can be re-activated. An Entitle action transitions a revoked application back to the entitled state.

    • Example: If we decided to re-install App2 v2.1, it would move from revoked back to entitled.

  • Forget from revoked: This is a final, destructive action. A Forget action permanently removes the tenant's history of a revoked application, transitioning it to the end state [*].

    • Example: After revoking App2, a Forget action would erase its entitlement record completely.

Applications Desired-State API

To introduce applications desired-state flow, we will add a new endpoint:

PUT /entitlements/state

This new endpoint will be idempotent.

The client provides the complete, final list of applications that should be entitled for a tenant—the "desired state." The mgr-tenant-entitlements is then responsible for calculating the difference between the current state and the desired state and executing the necessary sequence of Entitle, Upgrade, and Revoke operations to achieve it.

Using PUT is idiomatic for this kind of "create-or-replace" operation. Calling this endpoint multiple times with the same request body will result in the same final state for the tenant, with no further changes made after the first successful call.

Handling the Desired-State Request

The following diagram illustrates the end-to-end process of handling a PUT /entitlements/state request within the mgr-tenant-entitlements component

desired-state-handler.png
  1. API Request (EntitlementController)
    The process starts when an API Client sends the PUT request. The EntitlementController's applyState() method receives it. Its first responsibility is to create a standardized EntitlementRequest object, populating it with the tenantId, the list of desired applications, and query parameters. Crucially, it sets the type of this request to a new value: "state".

  2. Service Layer Orchestration (EntitlementService)
    The controller then calls EntitlementService.performRequest(). This method acts as a high-level orchestrator and does not need to be modified. Its logic remains generic:

    1. get a factory for the request type,

    2. use the factory to create a flow,

    3. and execute the flow.

  3. Factory Selection (FlowProvider)
    The EntitlementService asks the FlowProvider for the appropriate factory based on the EntitlementRequest. The FlowProvider, upon seeing the request type is "state", returns the newly implemented DesiredStateFlowFactory. This step cleanly decouples the service layer from the specific logic of any given flow.

  4. Flow Creation (DesiredStateFlowFactory - new class)
    This is the core of the new functionality. The EntitlementService calls createFlow() on the DesiredStateFlowFactory. This factory encapsulates all the logic for the desired state operation, which will be described in details later, but briefly it does the next:

    • It fetches the tenant's currently entitled applications.

    • It gets the desired state from the EntitlementRequest.

    • It performs a "diff" calculation to produce three lists of operations: applications to entitle, applications to revoke, and applications to upgrade.

    • It constructs a Flow object containing the necessary stages (e.g., RevokeApplication, UpgradeApplication, EntitleApplication) in the correct execution order.

  5. Flow Execution (FlowEngine)
    Once the Flow is created, the EntitlementService hands it over to the FlowEngine for execution. The FlowEngine processes each stage to realize the desired state.

  6. Response Handling
    After the FlowEngine completes the EntitlementService populate the result into the ExtendedEntitlements response object, which is then passed back to the EntitlementController and returned to the API client as a 200 OK response.

Desired State Flow

The following activity diagram illustrates the high-level process undertaken by the DesiredStateFlowFactory to create an executable flow. This factory is responsible for transforming a declarative "desired state" request into an imperative sequence of ordered stages (revoke, upgrade, entitle) that can be executed by the FlowEngine. The diagram shows the critical phases of calculation, validation, and dynamic flow construction.

desired-state-flow.png
  1. Initialization:

    • The process begins with standard flow initialization steps.

    • The Tenant object is loaded and key information, such as the tenantName, is added to the flow's context.

    • The descriptors for the applications specified in the incoming request (the "desired state") are fetched and stored in the context for later use.

  2. Calculation of Operations (The "Diff"):

    • This is the core logic where the imperative plan is created.

    • First, the factory queries the system to get the list of all applications currently entitled for the tenant.

    • It then performs a "diff" by comparing the current list against the desired list, categorizing each necessary change into one of three operation sets:

      • Revoke: Any currently entitled application whose name does not appear in the desired list.

      • Upgrade: Any application whose name appears in both lists but with a different version.

      • Entitle: Any application from the desired list whose name does not appear in the current entitled list.

    • These three sets of operations are stored in the flow context.

  3. Validation:

    • Before building the execution stages, a series of critical, fail-fast validations are performed.

    • InterfaceIntegrityValidator: A general check to ensure that the required and provided interfaces for all applications the request are compatible.

    • ApplicationFlowValidator: A validator that checks each calculated operation set for internal consistency.

    • Conditional Validators:

      • If there are applications to upgrade, the UpgradeRequestValidator is applied to ensure the upgrade paths are valid.

      • If there are applications to revoke, the ExistingEntitlementValidator confirms that these entitlements actually exist before attempting to remove them.

    • If any validator throws an exception, the entire flow creation fails immediately, preventing a partially-valid plan from being executed.

  4. Dynamic Flow Construction:

    • If all validations pass, the factory proceeds to build the executable stages of the flow.

    • Revoke Stages: If the "revoke" operation set is not empty, the ApplicationsFlowProvider is used to generate and add the necessary RevokeApplication stages to the flow. Revoking is typically done first to free up dependencies.

    • Combined Entitle/Upgrade Stages: This is a key design point. The "entitle" and "upgrade" sets are merged. A modified or new ApplicationsFlowProvider is then used to generate the stages for both sets together. This is essential because the installation and upgrade order must be calculated based on the combined dependency hierarchy of all new and upgraded applications to ensure, for example, that a new dependency is entitled before an existing application is upgraded to a version that requires it.

    • The generated stages are added to the flow.

  5. Finalization:

    • Finally, the FinishedFlowFinalizer is run. This stage typically handles any cleanup or final state logging required before the flow object is considered complete and ready for execution. The fully constructed flow is then returned to the EntitlementService.

Summary of Implementation Tasks

1. API Layer: Expose the New Endpoint

A new declarative endpoint, PUT /entitlements/state, will be added to the EntitlementController. This endpoint will accept the complete desired list of applications for a tenant, making the management process idempotent.

2. Controller Logic: Delegate to the Service Layer

The controller method will translate the incoming API request into an internal EntitlementRequest, setting its type to "state". It will then pass this request to the existing EntitlementService without requiring any changes to the service layer's primary entry point.

3. Flow Selection: Introduce the New Flow Factory

The FlowProvider component will be updated to recognize the "state" request type. Upon receiving such a request, it will select and return the new DesiredStateFlowFactory, cleanly integrating the new logic path into the existing architecture.

4. Core Logic: Implement the DesiredStateFlowFactory

This new factory will encapsulate the core desired-state logic by calculating an execution plan and ensuring its validity before proceeding.

  • Calculate Operations ("Diff"): It will fetch the tenant's current entitlements and compare them against the desired list to produce three distinct operation sets: applications to revoke, upgrade, and entitle.

  • Validate the Plan: The entire transition plan will be validated by applying a sequence of validators, such as InterfaceIntegrityValidator, UpgradeRequestValidator, and ExistingEntitlementValidator, to fail fast on invalid or inconsistent requests.

5. Stage Generation: Enhance Flow Construction

The factory will dynamically construct the execution flow by generating and ordering stages based on the calculated plan.

  • Sequence Revoke Operations First: Stages for all "revoke" operations will be generated and placed at the beginning of the flow, ensuring that old applications are uninstalled before new or upgraded ones are introduced.

  • Combine and Sort Entitle/Upgrade Operations: The "entitle" and "upgrade" operation sets will be merged into a single list. This combined list will be dependency-sorted to produce a valid, ordered sequence of installation and upgrade stages.

  • Assemble the Final Flow: The final Flow object will be constructed by assembling the revoke stages followed by the sorted entitle/upgrade stages, creating a complete and executable plan.