feat: config_loader, mock_state, state_machine (v0.1.0)

This commit is contained in:
2026-07-31 20:21:11 +04:00
parent 77f12d2f86
commit 1956fb7835
3 changed files with 355 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
"""
state_machine.py — apply_effect: применение результата операции к состоянию.
Вызывается после завершения операции (dtFinish проставлен).
Мутирует MockState в зависимости от kind и action операции.
Правила:
kind="instance":
create → статус "running", скопировать stateParams из service_def
modify → мерж params в state.params (реальный, не затираем старые)
delete → удалить инстанс
suspend → статус "suspended"
resume → статус "running"
redeploy→ статус "running"
другое → no-op (статус не меняется, например restart/recovery)
kind="subresource":
create → state.out[plural][name] = {}
delete → del state.out[plural][name]
"""
def _pluralize(word):
"""Простейшая плюрализация."""
if word.endswith("s"):
return word
if word.endswith("y") and len(word) > 2 and word[-2] not in "aeiou":
return word[:-1] + "ies"
return word + "s"
def apply_effect(op_uid, mock_state, services):
"""Применить эффект завершённой операции.
Args:
op_uid — UUID операции
mock_state — экземпляр MockState
services — {service_id: service_def} из config_loader
"""
op = mock_state.operations.get(op_uid)
if not op:
return
kind = op.get("kind", "instance")
action = op.get("action", "")
instance_uid = op.get("instanceUid")
inst = mock_state.instances.get(instance_uid)
op_params = mock_state.op_params.get(op_uid, {})
# ------------------------------------------------------------------
# instance-операции
# ------------------------------------------------------------------
if kind == "instance":
if action == "create":
if inst:
inst["status"] = "running"
inst["explainedStatus"] = "Выполнен"
# Скопировать stateParams из шаблона сервиса
svc_id = inst.get("serviceId")
svc_def = services.get(svc_id, {})
template = svc_def.get("stateParams", {})
inst["state"]["params"] = dict(template)
# stateOut из шаблона
state_out_template = svc_def.get("stateOut", {})
inst["state"]["out"] = {k: dict(v) for k, v in state_out_template.items()}
elif action == "modify":
if inst:
# Реальный мерж: параметры из op_params → state.params
_merge_params(inst, op_params, op.get("svcOperationId"), services)
elif action == "delete":
if instance_uid in mock_state.instances:
del mock_state.instances[instance_uid]
elif action == "suspend":
if inst:
inst["status"] = "suspended"
inst["explainedStatus"] = "Приостановлен"
elif action == "resume":
if inst:
inst["status"] = "running"
inst["explainedStatus"] = "Выполнен"
elif action == "redeploy":
if inst:
inst["status"] = "running"
inst["explainedStatus"] = "Выполнен"
# restart, recovery и любые другие — no-op (статус не меняем)
# ------------------------------------------------------------------
# subresource-операции
# ------------------------------------------------------------------
elif kind == "subresource":
if not inst:
return
operation_name = op.get("operation", "")
# Извлекаем имя subresource из operation: create_user → user
parts = operation_name.split("_", 1)
if len(parts) < 2:
return # невалидное имя операции
subresource_name = parts[1] # "user", "database", ...
plural = _pluralize(subresource_name)
if action == "create":
# Имя subresource-объекта — из параметров операции
obj_name = _extract_subresource_name(op_params, subresource_name)
if plural not in inst["state"]["out"]:
inst["state"]["out"][plural] = {}
inst["state"]["out"][plural][obj_name] = {}
elif action == "delete":
obj_name = _extract_subresource_name(op_params, subresource_name)
if plural in inst["state"]["out"]:
inst["state"]["out"][plural].pop(obj_name, None)
# ---------------------------------------------------------------------------
# Вспомогательные функции
# ---------------------------------------------------------------------------
def _merge_params(inst, op_params, svc_operation_id, services):
"""Мерж параметров операции в state.params инстанса.
op_params — {paramId(int): value(str)}.
Нужно смаппить paramId → code через cfsParamsByOp сервиса.
"""
svc_id = inst.get("serviceId")
svc_def = services.get(svc_id, {})
cfs_params_by_op = svc_def.get("cfsParamsByOp", {})
cfs_params = {p["svcOperationCfsParamId"]: p for p in svc_def.get("cfsParams", [])}
# ID параметров для этой операции
param_ids_for_op = cfs_params_by_op.get(svc_operation_id, [])
for pid in param_ids_for_op:
if pid in op_params:
code = cfs_params[pid]["svcOperationCfsParam"]
inst["state"]["params"][code] = op_params[pid]
def _extract_subresource_name(op_params, subresource_name):
"""Извлечь имя subresource-объекта из параметров операции.
Например для create_user: ищем параметр с кодом "username" или "user".
Если не нашли — используем "default".
"""
# Ищем по коду параметра (нужен доступ к cfsParams, но здесь у нас только op_params по id)
# Простой fallback: имя subresource
return subresource_name