Allocation V3¶
Allocation V3 is a new framework that changes significantly compared to Allocation V2. The purpose is the same as V2: filling up the read-only values of a service instance during the first validation compile of the lifecycle. Allocation is now performed via a plugin call.
The advantage of this approach is that it simplifies greatly the process: you don’t need anymore to write
allocator classes and all the required functions (needs_allocation
, allocate
, etc.). You also don’t need to instantiate many
AllocationSpecV2
with your allocators inside. Instead, you just need to write one plugin per attribute
you want to allocate and register it as an allocator
, it is less verbose and a much more straightforward approach.
LSM comes with build-in allocators that can be used out of the box, e.g. get_first_free_integer
.
Create an allocator¶
In the allocation V3 framework, an allocator is a python function returning the value to be set
for a specific read-only attribute on a specific service instance. To register this function as
an allocator, use the allocation_helpers.allocator()
decorator:
1from inmanta_plugins.lsm.allocation_helpers import allocator
2
3@allocator()
4def get_service_id(
5 service: "lsm::ServiceEntity",
6 attribute_path: "string",
7) -> "int":
8 return 5
- An allocator must accept exactly two positional arguments:
service
, the service instance for which the value is being allocated.
2.
attribute_path
, the attribute of the service instance in which the allocated value should be saved, as aDictPath
expression. The decorated function can define a default value.
After those two positional arguments, the function is free of accepting any keyword argument it needs from the model and they will be passed transparently. The function can also define default values, that will be passed transparently as well.
Once an allocator is registered, it can be reused for other instances and attributes that require the same type of allocation by passing the appropriate parameters to the plugin call.
It is also possible to enforce an order in the allocators call by passing values that are returned by other plugins in the model:
1"""
2 Inmanta LSM
3 :copyright: 2024 Inmanta
4 :contact: code@inmanta.com
5 :license: Inmanta EULA
6"""
7
8
9import lsm
10import lsm::fsm
11
12entity ServiceWithOrderedAllocation extends lsm::ServiceEntity:
13 """
14 This service entity demonstrates how to enforce a specific order during
15 the allocation process. Here we want to allocate some attributes in a
16 specific order: value_allocated_first and then value_allocated_last.
17
18 :attr name: The name identifying the service instance.
19 :attr value_allocated_first: A read-only value, automatically assigned by the api
20 before value_allocated_last.
21 :attr value_allocated_last: A read-only value, automatically assigned by the api
22 after value_allocated_first.
23 """
24
25 string name
26 lsm::attribute_modifier name__modifier="rw"
27 string? value_allocated_first=null
28 lsm::attribute_modifier value_allocated_first__modifier="r"
29 string? value_allocated_last=null
30 lsm::attribute_modifier value_allocated_last__modifier="r"
31end
32
33# Inherit parent entity's implementations
34implement ServiceWithOrderedAllocation using parents
35
36
37# Create a binding to enable service creation through the service catalog
38ordered_allocation_binding = lsm::ServiceEntityBindingV2(
39 service_entity="allocatorv3_demo::ServiceWithOrderedAllocation",
40 lifecycle=lsm::fsm::simple,
41 service_entity_name="allocation_order_enforcement",
42)
43
44# Collect all service instances
45for assignment in lsm::all(ordered_allocation_binding):
46 service = ServiceWithOrderedAllocation(
47 instance_id=assignment["id"],
48 entity_binding=ordered_allocation_binding,
49 name=assignment["attributes"]["name"],
50
51 # Regular allocation:
52 value_allocated_first=ordered_allocation(
53 service,
54 "value_allocated_first"
55 ),
56
57 # Passing value_allocated_first as a parameter to this allocator
58 # will enforce the ordering:
59 value_allocated_last = ordered_allocation(
60 service,
61 "value_allocated_last",
62 requires=[service.value_allocated_first]
63 )
64
65 )
66end
On the plugin side, add an optional argument to enforce ordering:
1"""
2 Copyright 2024 Inmanta
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15
16 Contact: code@inmanta.com
17"""
18
19from datetime import datetime
20
21from inmanta_plugins.lsm.allocation_helpers import allocator
22
23
24@allocator()
25def ordered_allocation(
26 service: "lsm::ServiceEntity",
27 attribute_path: "string",
28 *,
29 requires: "list?" = None
30) -> "string":
31 """
32 For demonstration purposes, this allocator returns the current time.
33
34 :param service: The service instance for which the attribute value
35 is being allocated.
36 :param attribute_path: DictPath to the attribute of the service
37 instance in which the allocated value will be stored.
38 :param requires: Optional list containing the results of allocator calls
39 that should happen before the current call.
40 """
41 return str(datetime.now())
V2 to V3 migration¶
Moving from allocation V2 to allocation V3 boils down to the following steps:
In the plugins directory:
Create a specific allocator for each property of the service that requires allocation.
Make sure to register these allocators by decorating them with the
@allocator()
decorator.
In the model:
Call the relevant allocator plugin for each value requiring allocation in the
lsm::all
unwrapping.
Basic example¶
Here is an example of a V2 to V3 migration. For both the model and the plugin, first the old V2 version is shown and then the new version using V3 framework:
Plugin¶
Baseline V2 allocation in the plugins directory:
1"""
2 Copyright 2024 Inmanta
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15
16 Contact: code@inmanta.com
17"""
18
19from inmanta.util import dict_path
20from inmanta_plugins.lsm.allocation import AllocationSpecV2
21from inmanta_plugins.lsm.allocation_v2.framework import AllocatorV2, ContextV2, ForEach
22
23
24class IntegerAllocator(AllocatorV2):
25 """
26 Custom allocator class to set an integer value for an attribute.
27 """
28
29 def __init__(self, value: int, attribute: str) -> None:
30 """
31 :param value: The value to store for this attribute of this service.
32 :param attribute: Attribute of the service instance in which the
33 value will be stored.
34 """
35 self.value = value
36 self.attribute = dict_path.to_path(attribute)
37
38 def needs_allocation(self, context: ContextV2) -> bool:
39 """
40 Determine if this allocator has any work to do or if all
41 values have already been allocated correctly for the instance
42 exposed through the context object.
43
44 :param context: Interface with the current instance
45 being unwrapped in an lsm::all call.
46 """
47 try:
48 if not context.get_instance().get(self.attribute):
49 # Attribute not present
50 return True
51 except IndexError:
52 return True
53
54 return False
55
56 def allocate(self, context: ContextV2) -> None:
57 """
58 Allocate the value for the attribute via the context object.
59
60 :param context: Interface with the current instance
61 being unwrapped in an lsm::all call.
62 """
63 context.set_value(self.attribute, self.value)
64
65
66# In the allocation V2 framework, AllocationSpecV2 objects
67# are used to configure the allocation process:
68AllocationSpecV2(
69 "value_allocation",
70 IntegerAllocator(value=1, attribute="top_level_value"),
71 ForEach(
72 item="item",
73 in_list="embedded_services",
74 identified_by="id",
75 apply=[
76 IntegerAllocator(
77 value=3,
78 attribute="embedded_value",
79 ),
80 ],
81 ),
82)
When moving to V3, register an allocator in the plugin:
1"""
2 Copyright 2024 Inmanta
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15
16 Contact: code@inmanta.com
17"""
18
19from inmanta_plugins.lsm.allocation_helpers import allocator
20
21
22@allocator()
23def get_value(
24 service: "lsm::ServiceEntity",
25 attribute_path: "string",
26 *,
27 value: "any",
28) -> "any":
29 """
30 Store a given value in the attributes of a service.
31
32 :param service: The service instance for which the attribute value
33 is being allocated.
34 :param attribute_path: DictPath to the attribute of the service
35 instance in which the allocated value will be stored.
36 :param value: The value to store for this attribute of this service.
37 """
38
39 return value
Model¶
Baseline V2 allocation in the model:
1"""
2 Inmanta LSM
3 :copyright: 2024 Inmanta
4 :contact: code@inmanta.com
5 :license: Inmanta EULA
6"""
7
8import lsm
9import lsm::fsm
10
11entity TopLevelService extends lsm::ServiceEntity:
12 """
13 Top-level service to demonstrate V2 allocation.
14
15 :attr name: The name identifying the service instance.
16 :attr top_level_value: A read-only value, automatically assigned by the api.
17 """
18 string name
19 lsm::attribute_modifier name__modifier="rw"
20 int? top_level_value=null
21 lsm::attribute_modifier top_level_value__modifier="r"
22end
23
24# Uniquely identify top level services through their name attribute
25index TopLevelService(name)
26
27# Each top level service may have zero or more embedded services attached to it
28TopLevelService.embedded_services [0:] -- EmbeddedService
29
30entity EmbeddedService extends lsm::EmbeddedEntity:
31 """
32 An embedded service, attached to a TopLevelService instance.
33
34 :attr id: Identifier for this embedded service instance.
35 :attr embedded_value: A read-only value, automatically assigned by the api.
36 """
37 string id
38 lsm::attribute_modifier id__modifier="rw"
39 int? embedded_value=null
40 lsm::attribute_modifier embedded_value__modifier="r"
41 string[]? __lsm_key_attributes = ["id"]
42end
43
44# Uniquely identify embedded services through their id attribute
45index EmbeddedService(id)
46
47# Inherit parent entity's implementations
48implement TopLevelService using parents
49
50implement EmbeddedService using parents
51
52# Create a binding to enable service creation through the service catalog
53value_binding = lsm::ServiceEntityBindingV2(
54 service_entity="allocatorv3_demo::TopLevelService",
55 lifecycle=lsm::fsm::simple,
56 service_entity_name="value-service",
57 # V2 allocation requires passing the allocation_spec argument.
58 # The value_allocation is defined in the plugin:
59 allocation_spec="value_allocation",
60 service_identity="name",
61 service_identity_display_name="Name",
62)
63
64# Collect all service instances
65for assignment in lsm::all(value_binding):
66 attributes = assignment["attributes"]
67
68 service = TopLevelService(
69 instance_id=assignment["id"],
70 entity_binding=value_binding,
71 name=attributes["name"],
72 top_level_value=attributes["top_level_value"],
73 embedded_services=[
74 EmbeddedService(
75 **embedded_service
76 )
77 for embedded_service in attributes["embedded_services"]
78 ],
79 )
80end
When moving to V3 allocation, on the model side, call the allocators for the values requiring allocation:
1"""
2 Inmanta LSM
3 :copyright: 2024 Inmanta
4 :contact: code@inmanta.com
5 :license: Inmanta EULA
6"""
7
8import lsm
9import lsm::fsm
10
11entity TopLevelService extends lsm::ServiceEntity:
12 """
13 This service entity demonstrates how a single allocator
14 can be used for both a service entity and its embedded
15 entities.
16
17 :attr name: The name identifying the service instance.
18 :attr top_level_value: A read-only value, automatically assigned by the api.
19 """
20
21 string name
22 lsm::attribute_modifier name__modifier="rw"
23 int? top_level_value=null
24 lsm::attribute_modifier top_level_value__modifier="r"
25end
26
27# Uniquely identify top level services through their name attribute
28index TopLevelService(name)
29
30# Each top level service may have zero or more embedded services attached to it
31TopLevelService.embedded_services [0:] -- EmbeddedService
32
33
34entity EmbeddedService extends lsm::EmbeddedEntity:
35 """
36 An embedded service, attached to a TopLevelService instance.
37
38 :attr id: Identifier for this embedded service instance.
39 :attr embedded_value: A read-only value, automatically assigned by the api.
40 """
41 string id
42 lsm::attribute_modifier id__modifier="rw"
43 int? embedded_value=null
44 lsm::attribute_modifier embedded_value__modifier="r"
45 string[]? __lsm_key_attributes = ["id"]
46end
47
48# Uniquely identify embedded services through their id attribute
49index EmbeddedService(id)
50
51# Inherit parent entity's implementations
52implement TopLevelService using parents
53
54implement EmbeddedService using parents
55
56
57# Create a binding to enable service creation through the service catalog
58top_level_service_binding = lsm::ServiceEntityBindingV2(
59 service_entity="allocatorv3_demo::TopLevelService",
60 lifecycle=lsm::fsm::simple,
61 service_entity_name="top-level-service",
62 service_identity="name",
63 service_identity_display_name="Name",
64)
65
66
67# Collect all service instances
68for assignment in lsm::all(top_level_service_binding):
69 attributes = assignment["attributes"]
70 service = TopLevelService(
71 instance_id=assignment["id"],
72 entity_binding=top_level_service_binding,
73 name=attributes["name"],
74 # Allocator call
75 top_level_value=get_value(service, "top_level_value", value=1),
76 embedded_services=[
77 EmbeddedService(
78 id=embedded_service["id"],
79 # Allocator call
80 embedded_value=get_value(
81 service,
82 lsm::format(
83 "embedded_services[id={id}].embedded_value",
84 args=[],
85 kwargs=embedded_service,
86 ),
87 value=3,
88 ),
89 )
90 for embedded_service in attributes["embedded_services"]
91 ],
92 )
93end
In-depth example¶
This is a more complex example ensuring uniqueness for an attribute across instances within a given range of values:
Plugin¶
Baseline V2 allocation in the plugins directory:
1"""
2 Copyright 2024 Inmanta
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15
16 Contact: code@inmanta.com
17"""
18
19from inmanta_plugins.lsm.allocation import AllocationSpec, AnyUniqueInt, LSM_Allocator
20
21# Define an AllocationSpec using the build-in LSM_Allocator allocator:
22AllocationSpec(
23 "allocate_vlan",
24 LSM_Allocator(attribute="vlan_id", strategy=AnyUniqueInt(lower=50000, upper=70000)),
25)
This example will demonstrate how to use the get_first_free_integer
allocator from the lsm
module. Since we are using a plugin that is already defined, no extra plugin code
is required. We will simply call this plugin from the model with the appropriate arguments.
Model¶
Baseline V2 allocation in the model:
1"""
2 Inmanta LSM
3 :copyright: 2024 Inmanta
4 :contact: code@inmanta.com
5 :license: Inmanta EULA
6"""
7
8import lsm
9import lsm::fsm
10
11entity VlanAssignment extends lsm::ServiceEntity:
12 """
13 This service entity demonstrates allocation using the LSM_Allocator
14 build in lsm.
15
16 :attr name: The name identifying the service instance.
17 :attr vlan_id: A read-only value, automatically assigned by the api.
18 """
19 string name
20 int? vlan_id=null
21 lsm::attribute_modifier vlan_id__modifier="r"
22end
23
24# Inherit parent entity's implementations
25implement VlanAssignment using parents
26
27# Create a binding to enable service creation through the service catalog
28vlan_binding = lsm::ServiceEntityBinding(
29 service_entity="allocatorv3_demo::VlanAssignment",
30 lifecycle=lsm::fsm::simple,
31 service_entity_name="vlan-assignment",
32 # V2 allocation requires passing the allocation_spec argument.
33 # The allocate_vlan is defined in the plugin:
34 allocation_spec="allocate_vlan",
35)
36
37# Collect all service instances
38for assignment in lsm::all(vlan_binding):
39 VlanAssignment(
40 instance_id=assignment["id"],
41 entity_binding=vlan_binding,
42 **assignment["attributes"]
43 )
44end
When moving to V3 allocation, on the model side, call the allocators for the values requiring allocation:
1"""
2 Inmanta LSM
3 :copyright: 2024 Inmanta
4 :contact: code@inmanta.com
5 :license: Inmanta EULA
6"""
7
8
9import lsm
10import lsm::fsm
11import lsm::allocators
12
13
14entity VlanAssignment extends lsm::ServiceEntity:
15 """
16 This service entity demonstrates allocation using the get_first_free_integer
17 allocator build in lsm.
18
19 :attr name: The name identifying the service instance.
20 :attr vlan_id: A read-only value, automatically assigned by the api.
21 """
22
23 string name
24 int? vlan_id=null
25 lsm::attribute_modifier vlan_id__modifier="r"
26end
27
28
29# Inherit parent entity's implementations
30implement VlanAssignment using parents
31
32# Create a binding to enable service creation through the service catalog
33vlan_binding = lsm::ServiceEntityBindingV2(
34 service_entity="allocatorv3_demo::VlanAssignment",
35 lifecycle=lsm::fsm::simple,
36 service_entity_name="vlan-assignment",
37)
38
39# Collect all service instances
40for assignment in lsm::all(vlan_binding):
41 service = VlanAssignment(
42 instance_id=assignment["id"],
43 entity_binding=vlan_binding,
44 name=assignment["attributes"]["name"],
45 # Allocator call
46 vlan_id=lsm::allocators::get_first_free_integer(
47 service,
48 "vlan_id",
49 range_start=50000,
50 range_end=70000,
51 # Retrieve the values already in use across services in the binding
52 # and pass them as a parameter to the allocator call
53 used_values=lsm::allocators::get_used_values(vlan_binding, "vlan_id"),
54 )
55 )
56end