Allocation V2#
Allocation V2 is a new framework, similar to allocation (v1). It happens in the same lifecycle stage and serves the same purpose: filling up read-only values of a service instance.
It comes to fill some gaps in the functionalities of allocation (v1) and takes advantages of the experience and learnings that using allocation (v1) taught us. It is a more complete, functional, and elegant framework.
Example#
The example below show you the use case where a single allocator is used the same way on both the service instance and an embedded entity.
1import lsm
2import lsm::fsm
3
4
5entity ValueService extends lsm::ServiceEntity:
6 string name
7 lsm::attribute_modifier name__modifier="rw"
8
9 int? first_value
10 lsm::attribute_modifier first_value__modifier="r"
11end
12ValueService.embedded_values [0:] -- EmbeddedValue
13
14entity EmbeddedValue extends lsm::EmbeddedEntity:
15 string id
16 lsm::attribute_modifier id__modifier="rw"
17
18 int? third_value
19 lsm::attribute_modifier third_value__modifier="r"
20
21 string[]? __lsm_key_attributes = ["id"]
22end
23
24index EmbeddedValue(id)
25
26implement ValueService using parents
27implement EmbeddedValue using std::none
28
29binding = lsm::ServiceEntityBinding(
30 service_entity="__config__::ValueService",
31 lifecycle=lsm::fsm::simple,
32 service_entity_name="value-service",
33 allocation_spec="value_allocation",
34 strict_modifier_enforcement=true,
35)
36
37for assignment in lsm::all(binding):
38 attributes = assignment["attributes"]
39
40 service = ValueService(
41 instance_id=assignment["id"],
42 entity_binding=binding,
43 name=attributes["name"],
44 first_value=attributes["first_value"],
45 )
46
47 for embedded_value in attributes["embedded_values"]:
48 service.embedded_values += EmbeddedValue(
49 **embedded_value
50 )
51 end
52end
1"""
2 Inmanta LSM
3
4 :copyright: 2022 Inmanta
5 :contact: [email protected]
6 :license: Inmanta EULA
7"""
8from inmanta_lsm import dict_path
9from inmanta_plugins.lsm.allocation import AllocationSpecV2
10from inmanta_plugins.lsm.allocation_v2.framework import AllocatorV2, ContextV2, ForEach
11
12
13class IntegerAllocator(AllocatorV2):
14 def __init__(self, value: int, attribute: str) -> None:
15 self.value = value
16 self.attribute = dict_path.to_path(attribute)
17
18 def needs_allocation(self, context: ContextV2) -> bool:
19 try:
20 if not context.get_instance().get(self.attribute):
21 # Attribute not present
22 return True
23 except IndexError:
24 return True
25 return False
26
27 def allocate(self, context: ContextV2) -> None:
28 context.set_value(self.attribute, self.value)
29
30
31AllocationSpecV2(
32 "value_allocation",
33 IntegerAllocator(value=1, attribute="first_value"),
34 ForEach(
35 item="item",
36 in_list="embedded_values",
37 identified_by="id",
38 apply=[
39 IntegerAllocator(
40 value=3,
41 attribute="third_value",
42 ),
43 ],
44 ),
45)
Allocation V2 features#
- The two main additions to allocation v2 when compared to v1 are:
The new ContextV2 object (replacement for
AllocationContext
object), which goes in pair with AllocatorV2 and AllocationSpecV2The support for allocating attributes in embedded entities.
Setting a read-only attribute on an embedded entity, like done in the above-mentioned example, is only possible when strict_modifier_enforcement
is enabled. On legacy services, where strict_modifier_enforcement
is not enabled, read-only attributes can be set on embedded entities using the workaround mentioned the Section Legacy: Set attributes on embedded entities.
Warning
To use allocation safely, allocators should not keep any state between invocations, but pass all state via the ContextV2 object.
ContextV2#
A context object that will be passed to each allocator and that should be used to set values. This context always shows the attributes the allocator should have access to, based on its level in the allocators tree. This means a top level allocator will see all the attributes, but an allocator used on embedded entities will only see the attributes of such embedded entity (as if it was a standalone entity). The context object can also be used to store values at each “level of allocation”, reachable by all allocators at the same level.
In the example at the beginning of this page, the same allocator can be used to set a value on the service entity and an
embedded entity. In needs_allocation
, when calling context.get_instance()
, we receive as dict
the full service entity when allocating first_value
and the embedded entity when allocating
third_value
.
AllocatorV2#
A base class for all v2 allocators, they are provided with a ContextV2
object for those two
methods: needs_allocation
and allocate
. The main difference with v1, is that the allocate
method doesn’t return any value to allocate, it sets them using the context object:
context.set_value(name, value)
.
AllocationSpecV2#
The collector for all AllocatorV2
.
Legacy: Set attributes on embedded entities#
The server doesn’t have support to set read-only attributes on embedded entities when strict_modifier_enforcement
is disabled. Thanks to the allocator ContextV2Wrapper
and the plugin lsm::context_v2_unwrapper
a workaround exists to do allocation on an embedded entity’s attributes with strict_modifier_enforcement
disabled. This workaround saves all the allocated values in a dict, in an attribute of the service instance (added to the instance for this single purpose). That way, the server accepts the update.
The ContextV2Wrapper
, which has to be used at the root of the allocation tree, will collect and save all the allocated value in a single dict. And when getting all the service instances in your model, with lsm::all
, you can simply wrap the call
to lsm::all
with a call to lsm::context_v2_unwrapper
, which will place all the allocated values
saved in the dict, directly where they belong, in the embedded entities.
When using the ContextV2Wrapper
and the lsm::context_v2_unwrapper
plugin, you will have to
specify in which attributes all the allocated values should be saved.
1import lsm
2import lsm::fsm
3
4
5entity ValueService extends lsm::ServiceEntity:
6 string name
7 lsm::attribute_modifier name__modifier="rw"
8
9 int? first_value
10 lsm::attribute_modifier first_value__modifier="r"
11
12 dict? allocated
13 lsm::attribute_modifier allocated__modifier="r"
14end
15ValueService.embedded_values [0:] -- EmbeddedValue
16
17entity EmbeddedValue extends lsm::EmbeddedEntity:
18 string id
19 lsm::attribute_modifier id__modifier="rw"
20
21 int? third_value
22 lsm::attribute_modifier third_value__modifier="r"
23end
24
25implement ValueService using parents
26implement EmbeddedValue using std::none
27
28binding = lsm::ServiceEntityBinding(
29 service_entity="__config__::ValueService",
30 lifecycle=lsm::fsm::simple,
31 service_entity_name="value-service",
32 allocation_spec="value_allocation",
33)
34
35for assignment in lsm::context_v2_unwrapper(
36 assignments=lsm::all(binding),
37 fallback_attribute="allocated",
38):
39 attributes = assignment["attributes"]
40
41 service = ValueService(
42 instance_id=assignment["id"],
43 entity_binding=binding,
44 name=attributes["name"],
45 first_value=attributes["first_value"],
46 allocated=attributes["allocated"],
47 )
48
49 for embedded_value in attributes["embedded_values"]:
50 service.embedded_values += EmbeddedValue(
51 **embedded_value
52 )
53 end
54end
1"""
2 Inmanta LSM
3
4 :copyright: 2022 Inmanta
5 :contact: [email protected]
6 :license: Inmanta EULA
7"""
8from inmanta_lsm import dict_path
9from inmanta_plugins.lsm.allocation import AllocationSpecV2
10from inmanta_plugins.lsm.allocation_v2.framework import (
11 AllocatorV2,
12 ContextV2,
13 ContextV2Wrapper,
14 ForEach,
15)
16
17
18class IntegerAllocator(AllocatorV2):
19 def __init__(self, value: int, attribute: str) -> None:
20 self.value = value
21 self.attribute = dict_path.to_path(attribute)
22
23 def needs_allocation(self, context: ContextV2) -> bool:
24 try:
25 if not context.get_instance().get(self.attribute):
26 # Attribute not present
27 return True
28 except IndexError:
29 return True
30 return False
31
32 def allocate(self, context: ContextV2) -> None:
33 context.set_value(self.attribute, self.value)
34
35
36AllocationSpecV2(
37 "value_allocation",
38 IntegerAllocator(value=1, attribute="first_value"),
39 ContextV2Wrapper(
40 "allocated",
41 ForEach(
42 item="item",
43 in_list="embedded_values",
44 identified_by="id",
45 apply=[
46 IntegerAllocator(
47 value=3,
48 attribute="third_value",
49 ),
50 ],
51 ),
52 ),
53)
To facilitate allocation on embedded entities, the ForEach
allocator can be used.
Deleting of Embedded entities#
When you want to support deletion of embedded entities during updates, a slightly different configuration is needed. Because all allocated values are stored in a single attribute, the deleted entities will be recreated when unwrapping.
To prevent this, use track_deletes=true
on both the the allocator ContextV2Wrapper
and the plugin lsm::context_v2_unwrapper
Additionally, to re-trigger allocation when an item is deleted, use SetSensitiveForEach
instead of ForEach
.
1import lsm
2import lsm::fsm
3
4
5entity ValueService extends lsm::ServiceEntity:
6 string name
7 lsm::attribute_modifier name__modifier="rw"
8
9 int? first_value
10 lsm::attribute_modifier first_value__modifier="r"
11
12 dict? allocated
13 lsm::attribute_modifier allocated__modifier="r"
14end
15ValueService.embedded_values [0:] lsm::__rwplus__ EmbeddedValue
16
17entity EmbeddedValue extends lsm::EmbeddedEntity:
18 string id
19 lsm::attribute_modifier id__modifier="rw"
20
21 int? third_value
22 lsm::attribute_modifier third_value__modifier="r"
23
24 string other_value
25 lsm::attribute_modifier other_value__modifier="rw"
26end
27
28index EmbeddedValue(id)
29
30implement ValueService using parents
31implement EmbeddedValue using std::none
32
33binding = lsm::ServiceEntityBindingV2(
34 service_entity="__config__::ValueService",
35 lifecycle=lsm::fsm::simple,
36 service_entity_name="value-service",
37 allocation_spec="value_allocation",
38)
39
40for assignment in lsm::context_v2_unwrapper(
41 assignments=lsm::all(binding),
42 fallback_attribute="allocated",
43 track_deletes=true,
44):
45 attributes = assignment["attributes"]
46
47 service = ValueService(
48 instance_id=assignment["id"],
49 entity_binding=binding,
50 name=attributes["name"],
51 first_value=attributes["first_value"],
52 allocated=attributes["allocated"],
53 )
54
55 for embedded_value in attributes["embedded_values"]:
56 service.embedded_values += EmbeddedValue(
57 **embedded_value
58 )
59 end
60end
1"""
2 Inmanta LSM
3
4 :copyright: 2020 Inmanta
5 :contact: [email protected]
6 :license: Inmanta EULA
7"""
8from inmanta_lsm import dict_path
9from inmanta_plugins.lsm.allocation import AllocationSpecV2
10from inmanta_plugins.lsm.allocation_v2.framework import (
11 AllocatorV2,
12 ContextV2,
13 ContextV2Wrapper,
14 SetSensitiveForEach,
15)
16
17
18class IntegerAllocator(AllocatorV2):
19 def __init__(self, value: int, attribute: str) -> None:
20 self.value = value
21 self.attribute = dict_path.to_path(attribute)
22
23 def needs_allocation(self, context: ContextV2) -> bool:
24 try:
25 if not context.get_instance().get(self.attribute):
26 # Attribute not present
27 return True
28 except IndexError:
29 return True
30 return False
31
32 def allocate(self, context: ContextV2) -> None:
33 if self.needs_allocation(context):
34 context.set_value(self.attribute, self.value)
35
36
37AllocationSpecV2(
38 "value_allocation",
39 IntegerAllocator(value=1, attribute="first_value"),
40 ContextV2Wrapper(
41 "allocated",
42 SetSensitiveForEach(
43 item="item",
44 in_list="embedded_values",
45 identified_by="id",
46 apply=[
47 IntegerAllocator(
48 value=3,
49 attribute="third_value",
50 ),
51 ],
52 ),
53 track_deletes=True,
54 ),
55)