Skip to content

Commit 6a55b73

Browse files
authored
IFC-1530 Add weighted pool resource generic (#6511)
This change adds weight to resources of pools in order to have a more precise control over the order of resources during the allocation process.
1 parent c441445 commit 6a55b73

File tree

11 files changed

+117
-7
lines changed

11 files changed

+117
-7
lines changed

backend/infrahub/core/constants/infrahubkind.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@
7070
USERVALIDATOR = "CoreUserValidator"
7171
VALIDATOR = "CoreValidator"
7272
WEBHOOK = "CoreWebhook"
73+
WEIGHTED_POOL_RESOURCE = "CoreWeightedPoolResource"

backend/infrahub/core/node/resource_manager/ip_address_pool.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,15 @@ async def get_resource(
8181
return node
8282

8383
async def get_next(self, db: InfrahubDatabase, prefixlen: int | None = None) -> IPAddressType:
84-
# Measure utilization of all prefixes identified as resources
8584
resources = await self.resources.get_peers(db=db) # type: ignore[attr-defined]
8685
ip_namespace = await self.ip_namespace.get_peer(db=db) # type: ignore[attr-defined]
8786

88-
for resource in resources.values():
87+
try:
88+
weighted_resources = sorted(resources.values(), key=lambda r: r.allocation_weight.value or 0, reverse=True)
89+
except AttributeError:
90+
weighted_resources = list(resources.values())
91+
92+
for resource in weighted_resources:
8993
ip_prefix = ipaddress.ip_network(resource.prefix.value) # type: ignore[attr-defined]
9094
prefix_length = prefixlen or ip_prefix.prefixlen
9195

backend/infrahub/core/node/resource_manager/ip_prefix_pool.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,15 @@ async def get_resource(
8888
return node
8989

9090
async def get_next(self, db: InfrahubDatabase, prefixlen: int) -> IPNetworkType:
91-
# Measure utilization of all prefixes identified as resources
9291
resources = await self.resources.get_peers(db=db) # type: ignore[attr-defined]
9392
ip_namespace = await self.ip_namespace.get_peer(db=db) # type: ignore[attr-defined]
9493

95-
for resource in resources.values():
94+
try:
95+
weighted_resources = sorted(resources.values(), key=lambda r: r.allocation_weight.value or 0, reverse=True)
96+
except AttributeError:
97+
weighted_resources = list(resources.values())
98+
99+
for resource in weighted_resources:
96100
subnets = await get_subnets(
97101
db=db,
98102
ip_prefix=ipaddress.ip_network(resource.prefix.value), # type: ignore[attr-defined]

backend/infrahub/core/protocols.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ class CoreWebhook(CoreNode):
209209
validate_certificates: BooleanOptional
210210

211211

212+
class CoreWeightedPoolResource(CoreNode):
213+
allocation_weight: IntegerOptional
214+
215+
212216
class LineageOwner(CoreNode):
213217
pass
214218

backend/infrahub/core/schema/definitions/core/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@
5656
core_user_validator,
5757
)
5858
from .repository import core_generic_repository, core_read_only_repository, core_repository
59-
from .resource_pool import core_ip_address_pool, core_ip_prefix_pool, core_number_pool, core_resource_pool
59+
from .resource_pool import (
60+
core_ip_address_pool,
61+
core_ip_prefix_pool,
62+
core_number_pool,
63+
core_resource_pool,
64+
core_weighted_pool_resource,
65+
)
6066
from .template import core_object_component_template, core_object_template
6167
from .transform import core_transform, core_transform_jinja2, core_transform_python
6268
from .webhook import core_custom_webhook, core_standard_webhook, core_webhook
@@ -81,6 +87,7 @@
8187
builtin_ip_prefix,
8288
builtin_ip_address,
8389
core_resource_pool,
90+
core_weighted_pool_resource,
8491
core_generic_account,
8592
core_base_permission,
8693
core_credential,

backend/infrahub/core/schema/definitions/core/resource_pool.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@
3232
],
3333
)
3434

35+
core_weighted_pool_resource = GenericSchema(
36+
name="WeightedPoolResource",
37+
namespace="Core",
38+
label="Weighted Pool Resource",
39+
description="Resource to be used in a pool, its weight is used to determine its priority on allocation.",
40+
include_in_menu=False,
41+
branch=BranchSupportType.AWARE,
42+
generate_profile=False,
43+
attributes=[Attr(name="allocation_weight", label="Weight", kind="Number", optional=True, order_weight=10000)],
44+
)
45+
3546
core_ip_prefix_pool = NodeSchema(
3647
name="IPPrefixPool",
3748
namespace="Core",

backend/tests/unit/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2505,7 +2505,7 @@ async def ipam_schema() -> SchemaRoot:
25052505
"order_by": ["prefix__value"],
25062506
"display_labels": ["prefix__value"],
25072507
"branch": BranchSupportType.AWARE.value,
2508-
"inherit_from": [InfrahubKind.IPPREFIX],
2508+
"inherit_from": [InfrahubKind.IPPREFIX, InfrahubKind.WEIGHTED_POOL_RESOURCE],
25092509
},
25102510
{
25112511
"name": "IPAddress",

backend/tests/unit/core/resource_manager/test_ipaddress_pool.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,46 @@ async def test_get_next(
4242
assert next_prefix.id == next_prefix2.id
4343

4444

45+
async def test_get_next_weighted(
46+
db: InfrahubDatabase,
47+
default_branch: Branch,
48+
default_ipnamespace: Node,
49+
register_ipam_schema: SchemaBranch,
50+
ip_dataset_prefix_v4,
51+
):
52+
ns1 = ip_dataset_prefix_v4["ns1"]
53+
net144 = ip_dataset_prefix_v4["net144"]
54+
net145 = ip_dataset_prefix_v4["net145"]
55+
56+
net144.allocation_weight.value = 100
57+
await net144.save(db=db)
58+
net145.allocation_weight.value = 200
59+
await net145.save(db=db)
60+
61+
adress_pool_schema = registry.schema.get_node_schema(name=InfrahubKind.IPADDRESSPOOL, branch=default_branch)
62+
63+
pool = await CoreIPAddressPool.init(schema=adress_pool_schema, db=db)
64+
await pool.new(
65+
db=db, name="pool1", resources=[net144, net145], ip_namespace=ns1, default_address_type="IpamIPAddress"
66+
)
67+
await pool.save(db=db)
68+
69+
assert pool
70+
71+
next_address = await pool.get_next(db=db, prefixlen=30)
72+
assert str(next_address) == "10.10.3.2/30"
73+
74+
next_prefix = await pool.get_resource(
75+
db=db, address_type="IpamIPAddress", identifier="item1", branch=default_branch
76+
)
77+
assert next_prefix
78+
79+
next_prefix2 = await pool.get_resource(
80+
db=db, address_type="IpamIPAddress", identifier="item1", branch=default_branch
81+
)
82+
assert next_prefix.id == next_prefix2.id
83+
84+
4585
async def test_get_next_full(
4686
db: InfrahubDatabase,
4787
default_branch: Branch,

backend/tests/unit/core/resource_manager/test_prefix_pool.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,44 @@ async def test_get_next(
4343
assert next_prefix.id == next_prefix2.id
4444

4545

46+
async def test_get_next_weighted(
47+
db: InfrahubDatabase,
48+
default_branch: Branch,
49+
default_ipnamespace: Node,
50+
register_ipam_schema: SchemaBranch,
51+
ip_dataset_prefix_v4,
52+
):
53+
ns1 = ip_dataset_prefix_v4["ns1"]
54+
net140 = ip_dataset_prefix_v4["net140"]
55+
net141 = ip_dataset_prefix_v4["net141"]
56+
57+
net140.allocation_weight.value = 100
58+
await net140.save(db=db)
59+
net141.allocation_weight.value = 200
60+
await net141.save(db=db)
61+
62+
prefix_pool_schema = registry.schema.get_node_schema(name=InfrahubKind.IPPREFIXPOOL, branch=default_branch)
63+
64+
pool = await CoreIPPrefixPool.init(schema=prefix_pool_schema, db=db)
65+
await pool.new(db=db, name="pool1", resources=[net140, net141], ip_namespace=ns1)
66+
await pool.save(db=db)
67+
68+
assert pool
69+
70+
next_subnet = await pool.get_next(db=db, prefixlen=17)
71+
assert str(next_subnet) == "10.11.0.0/17"
72+
73+
next_prefix = await pool.get_resource(
74+
db=db, prefixlen=17, prefix_type="IpamIPPrefix", member_type="prefix", identifier="item1", branch=default_branch
75+
)
76+
assert next_prefix
77+
78+
next_prefix2 = await pool.get_resource(
79+
db=db, prefixlen=17, prefix_type="IpamIPPrefix", member_type="prefix", identifier="item1", branch=default_branch
80+
)
81+
assert next_prefix.id == next_prefix2.id
82+
83+
4684
async def test_get_one(
4785
db: InfrahubDatabase,
4886
default_branch: Branch,

backend/tests/unit/message_bus/operations/requests/test_proposed_change.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async def test_get_proposed_change_schema_integrity_constraints(
140140
)
141141
non_generate_profile_constraints = [c for c in constraints if c.constraint_name != "node.generate_profile.update"]
142142
# should be updated/removed when ConstraintValidatorDeterminer is updated (#2592)
143-
assert len(constraints) == 184
143+
assert len(constraints) == 185
144144
assert len(non_generate_profile_constraints) == 110
145145
dumped_constraints = [c.model_dump() for c in non_generate_profile_constraints]
146146
assert {

0 commit comments

Comments
 (0)