---
# System prepended metadata

title: ConfDB Mutations (v2)

---

# 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