# ConfDB Mutations
Механизм [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[tr, 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`.
Функции мутации помечаются декораторами
`@mutation_set` и `@mutation_delete`, декораторы принимают путь в дереве confdb и используются аналогично `@match` в нормализаторах.
Пример:
``` py
class MyMutation(BaseConfDBMutation):
@mutation_set("interfaces", ANY, "admin-status", ANY)
def set_interface_status(self, tokens: List[str]):
yield "interface {tokens[1]}"
if tokens[3] == "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):
@mutation_set("interfaces", ANY, "admin-status", ANY)
def set_interface_status(self, tokens: List[str]):
ctx = self.ensure_context(InterfaceContext, tokens[1])
if tokens[3] == "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