Transfer optimization

By default, the Inmanta server performs a new compile every time the state of a service instance changes. However, in practice it often happens that a transition between two states doesn’t result in a new desired state for the service instance. To prevent unnecessary compiles, the LSM module has support to indicate which transfers in a lifecycle preserve the desired state. The Inmanta server can then use this information to improve the performance of state transitions. This page describes how to mark transfers in a lifecycle as state-preserving and how to enable the transfer optimization feature on the server.

Annotating desired state-preserving transfers

The lsm::StateTransfer entity has two attributes to indicate that a transfer preserves the desired state:

  • bool target_same_desired_state (default=false): True iff following the success edge doesn’t change the desired state.

  • bool error_same_desired_state (default=false): True iff following the error edge doesn’t change the desired state.

The code snippet below models a lifecycle that contains state-preserving transfers:

 1import lsm
 2import lsm::fsm
 3import std::testing
 4
 5start = lsm::State(
 6    name="start",
 7    export_resources=false,
 8    validate_self="candidate",
 9)
10creating = lsm::State(
11    name="creating",
12    export_resources=true,
13    validate_self="active",
14    validate_others="active",
15)
16up = lsm::State(
17    name="up",
18    label="success",
19    export_resources=true,
20    validate_self="active",
21    validate_others="active",
22)
23failed = lsm::State(
24    name="failed",
25    label="danger",
26    export_resources=true,
27    validate_self="active",
28    validate_others="active",
29)
30rejected = lsm::State(
31    name="rejected",
32    label="danger",
33    export_resources=false,
34    deleted=true,
35)
36deleting = lsm::State(
37    name="deleting",
38    export_resources=true,
39    validate_self="active",
40    validate_others="active",
41    purge_resources=true,
42)
43terminated = lsm::State(
44    name="terminated",
45    export_resources=false,
46    deleted=true,
47)
48
49basic_lifecycle = lsm::LifecycleStateMachine(
50    name="testing::basic_lifecycle",
51    initial_state=start,
52    transfers=[
53        lsm::StateTransfer(
54            source=start,
55            target=creating,
56            error=rejected,
57            validate=true,
58            auto=true,
59            target_operation="promote",
60            error_same_desired_state=true,
61        ),
62        lsm::StateTransfer(
63            source=creating,
64            target=up,
65            error=creating,
66            resource_based=true,
67            target_same_desired_state=true,
68            error_same_desired_state=true,
69        ),
70        lsm::StateTransfer(
71            source=up,
72            target=up,
73            error=failed,
74            resource_based=true,
75            target_same_desired_state=true,
76            error_same_desired_state=true,
77        ),
78        lsm::StateTransfer(
79            source=failed,
80            target=up,
81            error=failed,
82            resource_based=true,
83            target_same_desired_state=true,
84            error_same_desired_state=true,
85        ),
86        lsm::StateTransfer(
87            source=up,
88            target=deleting,
89            on_delete=true,
90        ),
91        lsm::StateTransfer(
92            source=deleting,
93            target=terminated,
94            error=deleting,
95            resource_based=true,
96            error_same_desired_state=true,
97        ),
98    ]
99)

Let’s discuss the transfers that are not marked as state-preserving and why:

  • start -> creating: Not a state-preserving transfer because it moves the instances from a non-exporting state to an exporting state.

  • up -> deleting: Not a state-preserving transfer because this state transfer flips the purge_resources flag, which will have an effect on the desired state being deployed.

  • deleting -> terminated: Not a state-preserving transfer because it moves the instance from an exporting to a non-exporting state.

All other transfers were marked as state-preserving transfers. This decision was based on the assumption that not changing the high-level intent (active attribute set) doesn’t change the low-level intent (what is deployed to the infrastructure). This assumption doesn’t hold in all situations. The service model could for example change the low-level intent based on the current state of the service instance. Caution is advised when modelling a lifecycle with state-preserving transfer, as incorrectly marking a transfer as state-preserving will cause the orchestrator to behave incorrectly.

Enabling the transfer optimization feature

The environment setting enable_lsm_transfer_optimization can be used to enable the transfer optimization feature. When enabled, the LSM extension will perform a compile only when transitioning between two states that don’t preserve the desired state. When disabled, a recompile will be done for each state transition. Validation compiles are not impacted by this setting. They are always executed.

Testing

The Inmanta server validates every lifecycle that is exported to the server and it will reject any lifecycle that is indisputably wrong. The server will for example reject lifecycles with state-preserving transfers that connect a state that exports resources with a state that doesn’t export resources. However, the server cannot exhaustively detect all cases where transfers are incorrectly marked as state-preserving. As such, it’s important to add tests that validate whether the service model behaves consistently, whether or not the enable_lsm_transfer_optimization environment setting is enabled.