Embedded entities#
In some situations, the attributes of a ServiceEntity contain a lot of duplication. Consider the following example:
1import lsm
2import lsm::fsm
3import ip
4
5entity ServiceX extends lsm::ServiceEntity:
6 """
7 The API of ServiceX.
8
9 :attr service_id: A unique ID for this service.
10
11 :attr customer_router_name: The name of the router on the customer side.
12 :attr customer_router_system_ip: The system ip of the router on the customer side.
13 :attr customer_router_vendor: The vendor of the router on the customer side.
14 :attr customer_router_chassis: The chassis of the router on the customer side.
15
16 :attr provider_router_name: The name of the router on the provider side.
17 :attr provider_router_system_ip: The system ip of the router on the provider side.
18 :attr provider_router_vendor: The vendor of the router on the provider side.
19 :attr provider_router_chassis: The chassis of the router on the provider side.
20 """
21 string service_id
22
23 string customer_router_name
24 ip::ip customer_router_system_ip
25 lsm::attribute_modifier customer_router_system_ip__modifier="rw+"
26 string customer_router_vendor
27 string customer_router_chassis
28
29 string provider_router_name
30 ip::ip provider_router_system_ip
31 lsm::attribute_modifier provider_router_system_ip__modifier="rw+"
32 string provider_router_vendor
33 string provider_router_chassis
34end
35
36index ServiceX(service_id)
37
38implement ServiceX using parents
39
40binding = lsm::ServiceEntityBindingV2(
41 service_entity="__config__::ServiceX",
42 lifecycle=lsm::fsm::service,
43 service_entity_name="service_x",
44)
45
46for instance in lsm::all(binding):
47 ServiceX(
48 instance_id=instance["id"],
49 entity_binding=binding,
50 **instance["attributes"],
51 )
52end
Specifying the router details multiple times, results in code that is hard to read and hard to maintain. Embedded entities provide a mechanism to define a set of attributes in a separate entity. These attributes can be included in a ServiceEntity or in another embedded entity via an entity relationship. The code snippet below rewrite the above-mentioned example using the embedded entity Router:
1import lsm
2import lsm::fsm
3import ip
4
5entity ServiceX extends lsm::ServiceEntity:
6 """
7 The API of ServiceX.
8
9 :attr service_id: A unique ID for this service.
10 """
11 string service_id
12end
13
14index ServiceX(service_id)
15
16ServiceX.customer_router [1] -- Router
17ServiceX.provider_router [1] -- Router
18
19entity Router extends lsm::EmbeddedEntity:
20 """
21 Router details.
22
23 :attr name: The name of the router.
24 :attr system_ip: The system ip of the router.
25 :attr vendor: The vendor of the router.
26 :attr chassis: The chassis of the router.
27 """
28 string name
29 ip::ip system_ip
30 lsm::attribute_modifier system_ip__modifier="rw+"
31 string vendor
32 string chassis
33end
34
35index Router(name)
36
37implement ServiceX using parents
38implement Router using parents
39
40binding = lsm::ServiceEntityBindingV2(
41 service_entity="__config__::ServiceX",
42 lifecycle=lsm::fsm::service,
43 service_entity_name="service_x",
44)
45
46for instance in lsm::all(binding):
47 ServiceX(
48 instance_id=instance["id"],
49 entity_binding=binding,
50 service_id=instance["attributes"]["service_id"],
51 customer_router=Router(**instance["attributes"]["customer_router"]),
52 provider_router=Router(**instance["attributes"]["provider_router"]),
53 )
54end
Note, that the Router entity also defines an index on the name attribute.
Modelling embedded entities#
This section describes the different parts of the model that are relevant when modelling an embedded entity.
Strict modifier enforcement#
Each entity binding (lsm::ServiceEntityBinding
and lsm::ServiceEntityBindingV2
) has a feature flag
called strict_modifier_enforcement
. This flag indicates whether attribute modifiers should be enforced recursively
on embedded entities or not. For new projects, it’s recommended to enable this flag. Enabling it can be done in two
different ways:
Create a service binding using the
lsm::ServiceEntityBinding
entity and set the value of the attributestrict_modifier_enforcement
explicitly to true.Or, create a service binding using the
lsm::ServiceEntityBindingV2
entity (recommended approach). This entity has thestrict_modifier_enforcement
flag enabled by default.
The remainder of this section assumes the strict_modifier_enforcement
flag is enabled. If your project has
strict_modifier_enforcement
disabled for legacy reasons, consult the Section
Legacy: Embedded entities without strict_modifier_enforcement for more
information.
Defining an embedded entity#
The following constraints should be satisfied for each embedded entity defined in a model:
The embedded entity must inherit from
lsm::EmbeddedEntity
.When a bidirectional relationship is used between the embedding entity and the embedded entity, the variable name referencing the embedding entity should start with an underscore (See code snippet below).
When a bidirectional relationship is used, the arity of the relationship towards the embedding entity should be 0 or 1.
Relation attributes, where the other side is an embedded entity, should be prefixed with an underscore when the relation should not be included in the service definition.
An index must be defined on an embedded entity if the relationship towards that embedded entity has an upper arity larger than one. This index is used to uniquely identify an embedded entity in a relationship. More information regarding this is available in section Attribute modifiers on a relationship.
When an embedded entity is defined with the attribute modifier
__r__
, all sub-attributes of that embedded entity need to have the attribute modifier set to read-only as well. More information regarding attribute modifiers on embedded entities is available in section Attribute modifiers on a relationship.
The following code snippet gives an example of a bidirectional relationship to an embedded entity. Note that the name of the relationship to the embedding entity starts with an underscore as required by the above-mentioned constraints:
1import lsm
2import lsm::fsm
3import ip
4
5entity ServiceX extends lsm::ServiceEntity:
6 """
7 The API of ServiceX.
8
9 :attr service_id: A unique ID for this service.
10 """
11 string service_id
12end
13
14index ServiceX(service_id)
15
16ServiceX.router [1] -- Router._service [1]
17
18entity Router extends lsm::EmbeddedEntity:
19 """
20 Router details.
21
22 :attr name: The name of the router.
23 :attr system_ip: The system ip of the router.
24 :attr vendor: The vendor of the router.
25 :attr chassis: The chassis of the router.
26 """
27 string name
28 ip::ip system_ip
29 lsm::attribute_modifier system_ip__modifier="rw+"
30 string vendor
31 string chassis
32end
33
34index Router(name)
35
36implement ServiceX using parents
37implement Router using parents
38
39binding = lsm::ServiceEntityBindingV2(
40 service_entity="__config__::ServiceX",
41 lifecycle=lsm::fsm::service,
42 service_entity_name="service_x",
43)
44
45for instance in lsm::all(binding):
46 ServiceX(
47 instance_id=instance["id"],
48 entity_binding=binding,
49 service_id=instance["attributes"]["service_id"],
50 router=Router(**instance["attributes"]["router"]),
51 )
52end
Attribute modifiers on a relationship#
Attribute modifiers can also be specified on relational attributes. The --
part of the relationship definition can be
replaced with either lsm::__r__
, lsm::__rw__
or lsm::__rwplus__
. These attribute modifiers have the following
semantics when set on a relationship:
__r__: The embedded entity/entities can only be set by an allocator. If an embedded entity has this attribute modifier, all its sub-attributes should have the read-only modifier as well.
__rw__: The embedded entities, part of the relationship, should be set on service instantiation. After creation, no embedded entities can be added or removed from the relationship anymore. Note that this doesn’t mean that the attributes of the embedded entity cannot be updated. The latter is determined by the attribute modifiers defined on the attributes of the embedded entity.
__rwplus__: After service instantiation, embedded entities can be added or removed from the relationship.
When the relationship definition contains a --
instead of one of the above-mentioned keywords, the default attribute
modifier __rw__
is applied on the relationship. The code snippet below gives an example on the usage of attribute
modifiers on relationships:
1import lsm
2import lsm::fsm
3import ip
4
5entity ServiceX extends lsm::ServiceEntity:
6 """
7 The API of ServiceX.
8
9 :attr service_id: A unique ID for this service.
10 """
11 string service_id
12end
13
14index ServiceX(service_id)
15
16ServiceX.primary [1] -- SubService
17ServiceX.secondary [0:1] lsm::__rwplus__ SubService
18
19entity SubService extends lsm::EmbeddedEntity:
20 """
21 :attr ip: The IP address of the service
22 """
23 ip::ip ip
24end
25
26index SubService(ip)
27
28implement ServiceX using parents
29implement SubService using parents
30
31binding = lsm::ServiceEntityBindingV2(
32 service_entity="__config__::ServiceX",
33 lifecycle=lsm::fsm::service,
34 service_entity_name="service_x",
35)
36
37for instance in lsm::all(binding):
38 service_x = ServiceX(
39 instance_id=instance["id"],
40 entity_binding=binding,
41 service_id=instance["attributes"]["service_id"],
42 primary=SubService(**instance["attributes"]["primary"]),
43 )
44 if instance["attributes"]["secondary"] != null:
45 service_x.secondary=SubService(**instance["attributes"]["secondary"])
46 end
47end
In order to enforce the above-mentioned attribute modifiers, the inmanta server needs to be able to determine whether the embedded entities, provided in an attribute update, are an update of an existing embedded entity or a new embedded entity is being created. For that reason, each embedded entity needs to define the set of attributes that uniquely identify the embedded entity if the upper arity of the relationship is larger than one. This set of attributes is defined via an index on the embedded entity. The index should satisfy the following constraints:
At least one non-relational attribute should be included in the index.
Each non-relational attribute, part of the index, is exposed via the north-bound API (i.e. the name of the attribute doesn’t start with an underscore).
The index can include no other relational attributes except for the relation to the embedding entity.
The attributes that uniquely identify an embedded entity can never be updated. As such, they cannot have the
attribute modifier __rwplus__
.
If multiple indices are defined on the embedded entity that satisfy the above-mentioned constraints, one index needs
to be selected explicitly by defining the string[]? __lsm_key_attributes
attribute in the embedded entity. The
default value of this attribute should contain all the attributes of the index that should be used to uniquely identify
the embedded entity.
The example below defines an embedded entity SubService
with two indices that satisfy the above-mentioned
constraints. The __lsm_key_attributes
attribute is used to indicate that the name
attribute should be used
to uniquely identify the embedded entity.
1import lsm
2import lsm::fsm
3import ip
4
5entity ServiceX extends lsm::ServiceEntity:
6 """
7 The API of ServiceX.
8
9 :attr service_id: A unique ID for this service.
10 """
11 string service_id
12end
13
14index ServiceX(service_id)
15
16ServiceX.primary [1] -- SubService
17ServiceX.secondary [0:1] lsm::__rwplus__ SubService
18
19entity SubService extends lsm::EmbeddedEntity:
20 """
21 :attr name: The name of the sub-service
22 :attr ip: The IP address of the service
23 """
24 string name
25 ip::ip ip
26 string[]? __lsm_key_attributes = ["name"]
27end
28
29index SubService(name)
30index SubService(ip)
31
32implement ServiceX using parents
33implement SubService using parents
34
35binding = lsm::ServiceEntityBindingV2(
36 service_entity="__config__::ServiceX",
37 lifecycle=lsm::fsm::service,
38 service_entity_name="service_x",
39)
40
41for instance in lsm::all(binding):
42 service_x = ServiceX(
43 instance_id=instance["id"],
44 entity_binding=binding,
45 service_id=instance["attributes"]["service_id"],
46 primary=SubService(**instance["attributes"]["primary"]),
47 )
48 if instance["attributes"]["secondary"] != null:
49 service_x.secondary=SubService(**instance["attributes"]["secondary"])
50 end
51end
If the upper arity of the relationship towards an embedded entity is one, it’s not required to define an
index on the embedded entity. In that case, the embedded entity will always have the same identity, no matter what the
values of its attributes are. This means that there will be no difference in behavior whether the attribute modifier is
set to rw
or rw+
. If an index is defined on the embedded entity, the attribute modifiers will be enforced in
the same way as for relationships with an upper arity larger than one.
Legacy: Embedded entities without strict modifier enforcement#
When the strict_modifier_enforcement
flag is disabled on a service entity binding, the attribute modifiers defined
on embedded entities are not enforced recursively. In that case, only the attribute modifiers defined on top-level
service attributes are enforced. The following meaning applies to attribute modifiers associated with top-level
relational attributes to embedded entities:
__r__: The embedded entity/entities can only be set by an allocator.
__rw__: The embedded entity/entities should be set on service instantiation. Afterwards the relationship object cannot be altered anymore. This means it will be impossible to add/remove entities from the relationship as well as modify any of the attributes of the embedded entity in the relationship.
__rwplus__: After service instantiation, embedded entities can be updated and embedded entities can be added/removed from the relationship.
The modelling rules that apply when the strict_modifier_enforcement
flag is disabled are less strict compared to the
rules defined in Defining an embedded entity. The following changes apply:
No index should be defined on an embedded entity to indicate the set of attributes that uniquely identify that embedded entity. There is also no need to set the
__lsm_key_attributes
attribute either.When the attribute modifier on an embedded entity is set to
__r__
, it’s not required to set the attribute modifiers of all sub-attribute to read-only as well.