156 lines
6.2 KiB
Python
156 lines
6.2 KiB
Python
"""
|
|
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:
|
|
p = cfs_params.get(pid)
|
|
if p:
|
|
inst["state"]["params"][p["svcOperationCfsParam"]] = op_params[pid]
|
|
|
|
|
|
def _extract_subresource_name(op_params, subresource_name):
|
|
"""Извлечь имя subresource-объекта из параметров операции.
|
|
|
|
Ищет первое непустое строковое значение в op_params.
|
|
Если не нашли — fallback на subresource_name.
|
|
"""
|
|
for val in op_params.values():
|
|
if isinstance(val, str) and val.strip():
|
|
return val
|
|
return subresource_name
|