# ConfDB Mutations (v2) Механизм [ConfDB](https://docs.getnoc.com/master/en/dev/reference/confdb/) предусматривает синтаксис конфигурации абстрактного устройства, предоставляет инструментарий для преобразования конфига устройства в формат ConfDB и определяет общий язык запросов. В то же время часто встречается задача изменения конфигурации устройства для активации/деактивации сервисов. В таком случае, синтаксис ConfDB может стать *lingua franca* для взаимодействия сторонних систем с оборудованием. То есть в сторонних системах настройки оборудования могут осуществляться в терминах ConfDB, а трансляция синтаксиса ConfDB в целевые команды может осуществляться NOC. Так как воздействие приводит к изменению конфига устройства, соответсвующие операции ConfDB называются *Мутации*. Мутации являются антогонистами номализаторов, так как нормализаторы преобразовывают целевую конфигурацию в синтаксис ConfDB, а мутации - наоборот, приводят синтаксис ConfDB к целевым командам. ## Анатомия мутации К каждому узлу ConfDB может быть применена одна из операций модификации: * Добавление/обновление (update) * Удаление (delete) При удалении узла ConfDB операция удаления применяется ко всем существующим узлам рекурсивно. ## Реализация мутаций Как и нормализаторы, мутации ConfDB реализуются на уровне профиля устройства. Профиль может определять несколько мутаций, которые могут стекироваться. Мутации, обычно, находятся в модуле `noc.sa.profile.XXX.YYY.confdb.mutations` В профиль объекта добавляется: ``` py class BaseProfile(object): confdb_mutations: Optional[List[Tuple[str, Dict[str, Any]]]] def iter_config_mutations(self, object: ManagedObject) -> Iterable[Tuple[str, Optional[Dict[str, Any]]]]: if self.confdb_mutations: yield from self.confdb_mutations ``` Первый возвращаемые параметр - имя класса (для loader). Второй - конфиг. Мутации наследуются от класса `BaseConfDBMutation`. Аналогично нормализаторам, метакласс `BaseConfDBMutation` анализирует синтаксис, задаваемый оператором `DEF` на предмет аттрибутов `setter` и `delete`. По аналогии с аттрибутом `gen` они задают функции, которые добавляются к базовому классу через метакласс. При этом переменные из пути становятся keyword-only переменными. @todo: Функции должны поднимать NotImplementedError, или просто ничего не делать? Мутации реализуются путем перегрузки методов. Пример: ``` py class MyMutation(BaseConfDBMutation): def set_interface_admin_status(self, *, interface, status): yield "interface {interface}" if status == "True": yield "no shutdown" else: yield "shutdown" yield "end" ``` ## Контексты В командах важен контекст исполнения (например, настроки интерфейса). Если CLI контекстно-зависим, то после выполнения команды необходимо вернуться в исходное состояние. При этом, формируемый конфиг может быть излишне болтлив. Например, при выполнении двух команд на установку interface admin-status и description итог может получиться таким: ``` iterface Gi 0/1 description "My Port" end interface Gi 0/1 no shutdown end ``` Для того, чтобы сгруппировать команды можно объявить контекст мутации, которые наследуются из базового класса `BaseMutationContext`: ``` py class BaseMutationContext(object): """ Args: prefix: Опциональный префикс для добавления команды """ prefix: str = "" def __init__(self, path: Tuple[str, ...]): ... def iter_enter(self) -> Iterable[str]: """ Список команд на входе в контекст """ def iter_exit(self) -> Iterable[str]: """ Список команд на выходе из контекста """ def feed(self, cmd: Union[str, Iterable[str]]) -> None: """ Добавить команду в контекст """ def flush(self) -> Iterable[str]: """ Выдать все команды и очистить контекст """ def empty(self) -> bool: """ Проверить, что контекст пустой """ ``` В итоге мутацию можно переработать ``` py class IterfaceCtx(BaseMutationContext): prefix = " " def iter_enter(self): yield f"interface {self.path[0]}" def iter_exit(self): yield "end" class MyMutation(BaseConfDBMutation): def set_interface_admin_status(self, *, interface, status): ctx = self.ensure_context(InterfaceContext, interface) if status == "True": yield ctx.feed("no shutdown") else: yield ctx.feed("shutdown") ``` Работа с контекстами является ленивой, реальный конфиг формируется при завершении обработки мутаций в порядке их объявления. Если требуется принудительно выдать накопленную конфигурацию в контексте можно использовать конструкцию: ``` py ctx = self.ensure_context(InterfaceContext, ...) yield from ctx.flush() ``` Если необходимо очистить все контексты данного типа, то можно использовать конструкцию: ``` py yield from self.flush(InterfaceContext) ``` Если необходимо очистить все контексты, то используется конструкция ``` py yield from self.flush() ``` ## Применение мутаций ### JSON ``` json { "managed_object": <id> | <remote-id> "commands": [ ["set", "interfaces", "Gi 0/1", "admin-status", "on"], ["set", "interfaces", "Gi 0/1", "description", "My Interface"] ], "dry-run": false } ``` Ответ ``` json { "status": True, "commands": "interface Gi 0/1\n no shutdown\n description My Interface\nend" } ``` Ключ dry-run позволяет развернуть конфиг без реального запуска на оборудовании ### CLI Для удобства работы необходимо реализовать CLI-подобный язык вида: ``` set interfaces "Gi 0/1" admin-status on set interfaces "Gi 0/1 description "My interface" ``` и отправлять его в endpoint `/api/???/mutation/<mo id>/cli` с content-type text/plain