""" 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