feat: config_loader, mock_state, state_machine (v0.1.0)
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user