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:
Entitle:
App6 v1.0
Upgrade:
App1: from v1.0 to v2.0
App4: from v1.5 to v1.6
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.
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
API Request (EntitlementController)
The process starts when an API Client sends the PUT request. TheEntitlementController'sapplyState()method receives it. Its first responsibility is to create a standardizedEntitlementRequestobject, 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".Service Layer Orchestration (EntitlementService)
The controller then callsEntitlementService.performRequest(). This method acts as a high-level orchestrator and does not need to be modified. Its logic remains generic:get a factory for the request type,
use the factory to create a flow,
and execute the flow.
Factory Selection (FlowProvider)
TheEntitlementServiceasks theFlowProviderfor the appropriate factory based on theEntitlementRequest. TheFlowProvider, upon seeing the request type is"state", returns the newly implementedDesiredStateFlowFactory. This step cleanly decouples the service layer from the specific logic of any given flow.Flow Creation (DesiredStateFlowFactory - new class)
This is the core of the new functionality. TheEntitlementServicecallscreateFlow()on theDesiredStateFlowFactory. 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.
Flow Execution (FlowEngine)
Once the Flow is created, theEntitlementServicehands it over to theFlowEnginefor execution. The FlowEngine processes each stage to realize the desired state.Response Handling
After theFlowEnginecompletes theEntitlementServicepopulate the result into theExtendedEntitlementsresponse object, which is then passed back to theEntitlementControllerand 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.
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.
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.
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
UpgradeRequestValidatoris applied to ensure the upgrade paths are valid.If there are applications to revoke, the
ExistingEntitlementValidatorconfirms 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.
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
ApplicationsFlowProvideris 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
ApplicationsFlowProvideris 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.
Finalization:
Finally, the
FinishedFlowFinalizeris 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 theEntitlementService.
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, andExistingEntitlementValidator, 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
Flowobject will be constructed by assembling the revoke stages followed by the sorted entitle/upgrade stages, creating a complete and executable plan.