Skip to content

Commit 5670601

Browse files
committed
enums: deploy valkey from 2025.2
kolla replaced Redis with Valkey at OpenStack 2025.2: from that release kolla-redis.yml is not shipped and playbooks.yml has no redis entry. The three collections that listed Role("redis") -- nutshell, collection-infrastructure and cloudpod-infrastructure -- therefore carry a role that cannot resolve on 2025.2 and later, and valkey was never added, so nothing deploys the replacement either. On 2026.1 that aborts "osism apply nutshell" once common succeeds, and every coordination backend osism/defaults points at valkey has no backend at all. Give each of the three collections both backends under disjoint, adjacent bounds, so exactly one is selected on any release: never both, never neither. On 2025.1 both plays exist, and the bound rather than the playbook is what keeps valkey out, matching enable_valkey in osism/defaults. A second test pins that a bounded role has no dependencies. Excluding one promotes its dependents but not the prerequisite it was, and nothing at expansion time can supply a replacement, so the resulting graph can be well formed and still ordered wrongly. Forcing that decision in CI, in the change that introduces such a role, is the only place it is cheap to make: kolla 2026.1 also dissolved common into logs, kolla_toolbox, cron and fluentd, and common has six dependents, so that migration trips this test rather than silently reordering a deployment. Assisted-by: Claude:claude-opus-5 Signed-off-by: Roger Luethi <luethi@osism.tech>
1 parent 59776ab commit 5670601

2 files changed

Lines changed: 114 additions & 3 deletions

File tree

osism/data/enums.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,10 @@ def bounded_roles(roles):
228228
),
229229
Role("openvswitch", dependencies=[Role("ovn")]),
230230
Role("memcached"),
231-
Role("redis"),
231+
# kolla replaced redis with valkey at OpenStack 2025.2; on 2025.1
232+
# both plays exist but osism/defaults leaves enable_valkey off.
233+
Role("redis", until="2025.1"),
234+
Role("valkey", since="2025.2"),
232235
Role("rabbitmq"),
233236
],
234237
),
@@ -289,7 +292,8 @@ def bounded_roles(roles):
289292
),
290293
Role("openvswitch", dependencies=[Role("ovn")]),
291294
Role("memcached"),
292-
Role("redis"),
295+
Role("redis", until="2025.1"),
296+
Role("valkey", since="2025.2"),
293297
Role("rabbitmq"),
294298
],
295299
),
@@ -433,7 +437,8 @@ def bounded_roles(roles):
433437
),
434438
Role("openvswitch", dependencies=[Role("ovn")]),
435439
Role("memcached"),
436-
Role("redis"),
440+
Role("redis", until="2025.1"),
441+
Role("valkey", since="2025.2"),
437442
Role("rabbitmq"),
438443
],
439444
),

tests/unit/data/test_enums.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,109 @@ def test_bounded_roles_includes_a_bounded_parent_and_its_bounded_child():
391391
parent = Role("parent", until="2025.1", dependencies=[child])
392392

393393
assert list(bounded_roles([parent])) == [parent, child]
394+
395+
396+
# ---------------------------------------------------------------------------
397+
# The key-value store cut-over
398+
# ---------------------------------------------------------------------------
399+
400+
401+
KVS_COLLECTIONS = ["nutshell", "collection-infrastructure", "cloudpod-infrastructure"]
402+
403+
404+
@pytest.mark.parametrize("collection", KVS_COLLECTIONS)
405+
def test_kvs_collections_carry_both_backends(collection):
406+
roles = MAP_ROLE2ROLE[collection]
407+
408+
assert find_role(roles, "redis") is not None
409+
assert find_role(roles, "valkey") is not None
410+
411+
412+
@pytest.mark.parametrize("collection", KVS_COLLECTIONS)
413+
def test_kvs_backends_are_bounded_and_disjoint(collection):
414+
roles = MAP_ROLE2ROLE[collection]
415+
redis = find_role(roles, "redis")
416+
valkey = find_role(roles, "valkey")
417+
418+
assert redis.until == (2025, 1)
419+
assert redis.since is None
420+
assert valkey.since == (2025, 2)
421+
assert valkey.until is None
422+
423+
424+
@pytest.mark.parametrize("collection", KVS_COLLECTIONS)
425+
@pytest.mark.parametrize(
426+
"release,expected",
427+
[
428+
((2024, 2), "redis"),
429+
((2025, 1), "redis"),
430+
((2025, 2), "valkey"),
431+
((2026, 1), "valkey"),
432+
],
433+
)
434+
def test_exactly_one_backend_per_release(collection, release, expected):
435+
"""Never both, never neither -- on any release, past or future."""
436+
roles = MAP_ROLE2ROLE[collection]
437+
selected = [
438+
name
439+
for name in ("redis", "valkey")
440+
if find_role(roles, name).deployed_in(release)
441+
]
442+
443+
assert selected == [expected]
444+
445+
446+
def test_only_the_kvs_roles_carry_bounds():
447+
"""A bound anywhere else is new and wants its own test above."""
448+
bounded = {
449+
role.name for roles in MAP_ROLE2ROLE.values() for role in bounded_roles(roles)
450+
}
451+
452+
assert bounded == {"redis", "valkey"}
453+
454+
455+
def test_no_bounded_role_has_dependencies():
456+
"""A release-bounded role must be a leaf, until someone settles ordering.
457+
458+
Excluding a role promotes its dependencies into the surrounding group so
459+
the subtree survives. That keeps the subtree and its position among
460+
retained siblings, but not what the excluded role supplied to it:
461+
``chain(pt, st)`` runs a role BEFORE its ``dependencies``, so they are its
462+
dependents and it is their prerequisite. Promote them past an excluded
463+
parent and they run with no predecessor -- a well-formed task graph that
464+
may be ordered wrongly, which is worse than an error because it looks
465+
intentional. Nothing at expansion time can know which retained or
466+
replacement role belongs in that place.
467+
468+
So the decision is forced here, in CI, in the change that introduces such
469+
a role -- not at deploy time, and not only for whoever reads the design
470+
document. The case this exists for is the kolla 2026.1 split of ``common``
471+
into ``logs``, ``kolla_toolbox``, ``cron`` and ``fluentd``: ``common`` has
472+
six dependents, so bounding it trips this test.
473+
474+
If you are here because you added one: decide what runs in the excluded
475+
role's place on each release, encode that, and replace this test with one
476+
that pins the ordering you chose.
477+
"""
478+
offenders = {
479+
role.name: [dependency.name for dependency in role.dependencies]
480+
for roles in MAP_ROLE2ROLE.values()
481+
for role in bounded_roles(roles)
482+
if role.dependencies
483+
}
484+
485+
assert not offenders, (
486+
f"release-bounded roles with dependencies: {offenders}. "
487+
"Excluding one promotes its dependents but not the prerequisite it "
488+
"was; settle the ordering and replace this test. See the docstring."
489+
)
490+
491+
492+
def test_valkey_absent_from_collections_that_never_had_redis():
493+
"""The cut-over touches exactly the three collections that listed redis."""
494+
for name, roles in MAP_ROLE2ROLE.items():
495+
if name in KVS_COLLECTIONS:
496+
continue
497+
498+
assert find_role(roles, "valkey") is None, name
499+
assert find_role(roles, "redis") is None, name

0 commit comments

Comments
 (0)