Commit 1c3c4f03 authored by Andrey Vertiprahov's avatar Andrey Vertiprahov
Browse files

Add SA Profile Docs.

parent 17e3bab4
......@@ -230,6 +230,7 @@ build:Docs:en:
image: registry.getnoc.com/infrastructure/mkdocs:master
script:
- set -x
- pip3 install cachetools==4.2.1
- mkdocs build --config-file=docs/en/mkdocs.yml
tags:
- docker
......@@ -250,6 +251,7 @@ build:Docs:ru:
image: registry.getnoc.com/infrastructure/mkdocs:master
script:
- set -x
- pip3 install cachetools==4.2.1
- mkdocs build --config-file=docs/ru/mkdocs.yml
tags:
- docker
......
......@@ -12,7 +12,6 @@ import importlib
# NOC modules
from noc.config import config
from noc.core.mongo.connection import get_db
class ImportRouter(object):
......@@ -105,6 +104,7 @@ class NOCPyruleLoader(NOCLoader):
def _get_collection(self):
if not self.collection:
from noc.core.mongo.connection import get_db
self.collection = get_db()[self.COLLECTION_NAME]
return self.collection
......
......@@ -10,7 +10,6 @@ import re
from itertools import zip_longest
# Third-party modules
from numpy import array
from typing import List, Union, Iterable
rx_header_start = re.compile(r"^\s*[-=]+[\s\+]+[-=]+")
......@@ -606,6 +605,8 @@ def parse_table_header(v):
{10: 'Config Master', 18: 'Current Master', 26: 'Agg Control', 33: 'Min Active',
43: 'Ld Share Algorithm', 49: 'Flags ', 59: 'Ld Share Group', 63: 'Agg Mbr', 69: 'Link State'}
"""
from numpy import array
head = []
empty_header = None
header = {}
......
# -*- coding: utf-8 -*-
"""
##----------------------------------------------------------------------
## Vendor: Huawei
## OS: VRP
##----------------------------------------------------------------------
## Copyright (C) 2007-2014 The NOC Project
## See LICENSE for details
##----------------------------------------------------------------------
"""
from noc.core.profile.base import BaseProfile
import re
class Profile(BaseProfile):
name = "Huawei.VRP"
pattern_more = [
(r"^ ---- More ----", " "),
(r"[Cc]ontinue?\S+", "y\n\r"),
(r"[Cc]onfirm?\S+", "y\n\r"),
(r" [Aa]re you sure?\S+", "y\n\r"),
(r"^Delete flash:", "y\n\r"),
(r"^Squeeze flash:", "y\n\r")
]
pattern_prompt = r"^[<#\[](?P<hostname>[a-zA-Z0-9-_\.\[/`\s]+)(?:-[a-zA-Z0-9/]+)*[>#\]]"
pattern_syntax_error = r"(Error: |% Wrong parameter found at|% Unrecognized command found at|Error:Too many parameters found|% Too many parameters found at|% Ambiguous command found at)"
command_more = " "
config_volatile = ["^%.*?$"]
command_disable_pager = "screen-length 0 temporary"
command_enter_config = "system-view"
command_leave_config = "return"
command_save_config = "save"
command_exit = "quit"
def generate_prefix_list(self, name, pl, strict=True):
p = "ip ip-prefix %s permit %%s" % name
if not strict:
p += " le 32"
return "undo ip ip-prefix %s\n" % name + "\n".join([p % x.replace("/", " ") for x in pl])
rx_interface_name = re.compile(
r"^(?P<type>XGE|GE|Eth|MEth)(?P<number>[\d/]+(\.\d+)?)$")
def convert_interface_name(self, s):
"""
>>> Profile().convert_interface_name("XGE2/0/0")
'XGigabitEthernet2/0/0'
>>> Profile().convert_interface_name("GE2/0/0")
'GigabitEthernet2/0/0'
>>> Profile().convert_interface_name("Eth2/0/0")
'Ethernet2/0/0'
>>> Profile().convert_interface_name("MEth2/0/0")
'M-Ethernet2/0/0'
"""
match = self.rx_interface_name.match(s)
if not match:
return s
return "%s%s" % ({
"XGE": "XGigabitEthernet",
"GE": "GigabitEthernet",
"Eth": "Ethernet",
"MEth": "M-Ethernet",
# "Vlanif": "Vlan-interface" - need testing
}[match.group("type")], match.group("number"))
def convert_mac(self, mac):
"""
Convert 00:11:22:33:44:55 style MAC-address to 0011-2233-4455
"""
v = mac.replace(":", "").lower()
return "%s-%s-%s" % (v[:4], v[4:8], v[8:])
spaces_rx = re.compile("^\s{42}|^\s{16}", re.DOTALL | re.MULTILINE)
def clean_spaces(self, config):
config = self.spaces_rx.sub("", config)
return config
def fix_version(self, v):
if v["platform"] == "S5628F-HI" and v["version"] == "5.20":
# Do not change these numbers. Used in get_switchport script
v = "3.10"
return v["version"]
{
"scheme": "telnet",
"address": "192.168.1.1",
"port": 23,
"profile": "Cisco.IOS",
"credentials": {
"user": "login", "password": "pass", "super_password": "", "snmp_ro": "public", "snmp_rw": "private"
},
"caps": {
"SNMP": true,
"SNMP | IF-MIB": true,
"SNMP | Bulk": true,
"SNMP | IF-MIB | HC": true
}
}
# Профили оборудования
## Введение
Взаимодействие с устройствами в NOC'е строится через профили (`SA Profile`).
Можно описать `Профиль SA` (`SA Profile`) как посредника (адаптер).
Он принимает необработанный поток данных от оборудования и преобразует его в данные, которые передаются NOC'у для последующей обработки системой.
Профили жёстко привязаны к используемому на оборудовании программному обеспечению.
Внутри профиля можно проверять версию ПО и модель, но это усложняет код скриптов, по этой причине профили совместимы между собой в рамках одной ОС производителя.
Например, для `Cisco IOS` и `Cisco ASA` используются разные профили.
<!-- prettier-ignore -->
!!! note
`Профиль SA` (`SA Profile`) - это компонент NOC'а, скрывающий от остальной системы особенности взаимодействия с оборудованием.
Можно выделить следующие особенности профилей:
* Пишутся на языке программирования [Python](https://ru.wikipedia.org/wiki/Python)
* Подгружаются автоматически, при старте системы (для применения обновлений необходим перезапуск системы)
* Нет ограничений на использование каких-либо модулей `Python`
* Обязательным является скрипт `get_version`, остальные добавляются в зависимости от возможностей оборудования
* Собранные данные живут в рамах одной сессии
## Структура и взаимодействие с НОКом
### Состав профиля:
Профили расположены в директории `sa/profiles`.
По соглашению имя профиля строится из названия производителя `VendorName` и имени ОС `OSName`: `Juniper.JUNOS`, `Cisco.IOS`.
```
<noc_base>/sa/profiles
- <VendorName>
- __init__.py
- <OSName>
- snmp_metrics/
- confdb/
- middleware/
- __init__.py
- profile.py
- get_version.py
- <scripts>
```
<!-- prettier-ignore -->
!!! note
Имена являются регистрозависимыми!
Сам профиль состоит из:
* `profile.py` - Класс реализует настройки работы с оборудованием, наследуется от `noc.core.profile.base.BaseProfile`
* Набор [скриптов](../../reference/scripts/index.md), реализующих один из доступных *интерфейсов SA* (`SA Interface`)
* `snmp_metrics/` - папка с перечнем `SNMP OID` для скриптов
* `confdb/` - папка с парсерами конфигурации для [ConfDB](../../reference/confdb/index.md)
* `middleware/` - папка для обработчиков `HTTP` запросов к оборудованию
<!-- prettier-ignore -->
!!! note
`Интерфейс SA` (`SA Interface`) описывает формат и состав данных, которые необходимо передать в сторону NOC'а.
### Взаимодействие с NOC'ом
Взаимодействие с НОКом стоится на вызове скриптов профиля из других компонентов системы: [discovery](../../../admin/reference/discovery/box/index.md),
полученная информация используется для сбора информации об оборудовании. Возвращаемая скриптом информация должна соответствовать заявленному интерфейсу (`SA Interface`).
В самой системе профили назначаются устройству ([ManagedObject](../../../user/reference/concepts/managed-object/index.md) по этой причине нет необходимости явно указывать вызываемый профиль, он берётся из настроек.
Схема вызова выглядит следующим образом:
![Схема вызова профиля](images/SA_Script.png)
1. Вызов скрипта для устройства `ManagedObject`. Пример вызова скрипта `get_version`:
```python
from noc.core.mongo.connection import connect
connect()
from noc.sa.models.managedobject import ManagedObject
mo = ManagedObject.objects.get(name="<MONAME>")
r = mo.scripts.get_version()
r
{'vendor': 'Huawei',
'platform': 'S2326TP-EI',
'version': '5.70 (V100R006C05)',
'image': 'V100R006C05',
'attributes': {'Serial Number': '21',
'Patch Version': 'V100R006SPH031'}}
```
2. После вызова система делает `RPC` запрос к сервису [SAE](../../../admin/reference/services/sae.md) для получения параметров вызова скрипта:
* Учётные данные оборудования (пользователь, пароль, Community)
* Возможности (`Capabilities`)
* Настройки работы профиля ([Access Policy](../../../user/reference/concepts/managed-object-profile/index.md#Access(Доступ)))
3. SAE отвечает кодом переадресации на подходящий активатор [Activator](../../../admin/reference/services/activator.md)
4. Активатор создаёт сессию и запускает на исполнение вызываемый скрипт с переданными параметрами.
5. Если в скрипте реализованы методы `execute_cli` или `execute_snmp`, то их вызов происходит согласно приоритетам. При наличии метода `execute` всегда запускается он.
6. Результаты работы скрипта проверяются интерфейсом и возвращаются вызвавшему
7. Если при выполнении произошла ошибка, то возвращается её код
### Интерфейсы SA
Для передачи результатов применяются интерфейсы SA.
:term:`Интерфейс SA` - это специальная сущность NOC'а, предназначенная для обеспечение взаимодействия между компонентами.
Интерфейсы `SA` расположены в директории `sa/interfaces`. Они доступны для указания скриптов профиля.
Есть возможность определять собственные интерфейсы через [Custom](../../reference/custom/index.md)
В *Интерфейсе SA* описывается структура данных передаваемая и возвращаемая скриптом. Передаваемая - это параметры скрипта, а возвращаемая, результат работы. Описывается:
* Имена полей
* Тип данных
* Значения по умолчанию
* Обязательность поля
Если данные не прошли проверку поднимется исключение. В качестве примера возьмём интерфейс `noc.sa.interfaces.igetversion.IGetVersion`
.. autoclass:: noc.sa.interfaces.igetversion.IGetVersion
Описание достаточно наглядно показывает какой результат ожидается от скрипта.
В данном случае, для успешной передачи нам необходимо сформировать словарь (`dict`) с ключами:
* `vendor` - текстовое поле `StringParameter()`
* `version` - текстовое поле `StringParameter()`
* `platform` - текстовое поле `StringParameter()`
* `attributes` (необязательный параметр) - Словарь, при этом список ключей не ограничен. Это означает, что разработчик может самостоятельно выбрать что в нём передавать.
Итог выглядит как-то так:
```json
{
"vendor": "Cisco",
"version": "12.4(5)",
"platform": "IOS",
"attributes":
{
"image": "image.bin",
"type": "type1",
"count": 2
}
}
```
Через консоль разработчика можно проверять данные:
```python
r = {
"vendor": "Cisco",
"version": "12.4(5)",
"platform": "IOS",
"attributes":
{
"image": "image.bin",
"type": "type1",
"count": 2
}
}
from noc.sa.interfaces.igetversion import IGetVersion
IGetVersion().clean_result(r)
{'vendor': 'Cisco',
'version': '12.4(5)',
'platform': 'IOS',
'attributes': {'image': 'image.bin', 'type': 'type1', 'count': 2}}
```
### Настройки взаимодействия с оборудованием
<!-- prettier-ignore -->
!!! note
До версии 19.1 настройки находились в файле `__init__.py`
Настройки взаимодействия с оборудованием сосредоточены в файле `profile.py` профиля.
Большинство настроек описаны в базовом классе - `noc.core.profile.base` и доступны для переопределения в классе профиля. Они включают в себя следующие группы:
* Имя профиля. Должно совпадать со структурой - `cisco/ios` - `Cisco.IOS`
* Настройки работы с `CLI`. Методы и аттрибуты, описывающие работу с `CLI`
* `pattern_prompt` - строка приглашения на оборудовании.
* `pattern_syntax_error` - список строк ошибок команды (при совпадении поднимается исключение `CLISyntaxError`)
* `command_more` - команда (или клавиша), которую необходимо передать оборудованию для продолжения постраничного вывода (`deprecated` вместо него используется `pattern_more`)
* `command_disable_pager` - команда для отключения постраничного вывода информации
* `command_enter_config` - команда для входа в режим настройки
* `command_leave_config` - команда для выхода из режима настройки
* `command_save_config` - команда для сохранения конфигурации
* `command_exit` - команда для завершения сеанса `CLI`
* `rogue_chars` - список символов, для фильтрации из вывода команды
* Настройки работы с `SNMP`
* `snmp_metrics_get_chunk` - размер `SNMP` запроса метрик
* `snmp_metrics_get_timeout` - таймаут при запросе `SNMP`
* Методы нормализация выводимых данных
* `convert_interface_name` - нормализует имя интерфейса, на вход получает имя интерфейса как оно может быть представлено на оборудовании, на выходе имя интерфейса в системе
* `get_interface_type` - позволяет по имени интерфейса получить его тип
* `config_volatile` - список строк в конфигурации, которые необходимо отфильтровать из него при сборе. Обычно используется, чтобы исключить строки, которые могут меняться в конфигурации (например изменение времени).
* Матчеры (`Matchers`) - выставляют аттрибут скрипта при совпадении условий (версия ПО, платформа). Используются для выполнения различных команд в зависимости от версии ПО/модели
* Настройки парсеров `ConfDB`:
* `config_tokenizer` - применяемый токенизатор [Tokenizer](../../reference/confdb/tokenizer.md)
* `config_normalizer` - применяемый нормализатор [Normalizer](../../reference/confdb/normalizer.md)
* `config_applicators` - список аппликаторов [Applicators](../../reference/confdb/index.md)
Пример файла профиля `profile.py`:
```python
from noc.core.profile.base import BaseProfile
import re
class Profile(BaseProfile):
name = "Huawei.VRP"
pattern_more = [
(r"^ ---- More ----", " "),
(r"[Cc]ontinue?\S+", "y\n\r"),
(r"[Cc]onfirm?\S+", "y\n\r"),
(r" [Aa]re you sure?\S+", "y\n\r"),
(r"^Delete flash:", "y\n\r"),
(r"^Squeeze flash:", "y\n\r")
]
pattern_prompt = r"^[<#\[](?P<hostname>[a-zA-Z0-9-_\.\[/`\s]+)(?:-[a-zA-Z0-9/]+)*[>#\]]"
pattern_syntax_error = r"(Error: |% Wrong parameter found at|% Unrecognized command found at|Error:Too many parameters found|% Too many parameters found at|% Ambiguous command found at)"
command_more = " "
config_volatile = ["^%.*?$"]
command_disable_pager = "screen-length 0 temporary"
command_enter_config = "system-view"
command_leave_config = "return"
command_save_config = "save"
command_exit = "quit"
rogue_chars = [
re.compile(rb"\x1b\[42D\s+\x1b\[42D")
]
matchers = {
"is_kernel_3": {"version": {"$gte": "3.0", "$lt": "5.0"}},
"is_kernelgte_5": {"version": {"$gte": "5.0"}},
"is_bad_platform": {
"version": {"$regex": r"5.20.+"},
"platform": {"$in": ["S5628F", "S5628F-HI"]},
}}
config_tokenizer = "indent"
config_normalizer = "VRPNormalizer"
confdb_defaults = [
("hints", "interfaces", "defaults", "admin-status", True),
("hints", "protocols", "lldp", "status", True),
]
config_applicators = ["noc.core.confdb.applicator.collapsetagged.CollapseTaggedApplicator"]
def generate_prefix_list(self, name, pl, strict=True):
...
def convert_interface_name(self, s):
...
```
Полный список доступных методов доступен в базовом классе профиля - `noc.core.profile.base.BaseProfile`
### Скрипты
Полное описание работы скриптов доступно в [Script](../../reference/scripts/index.md). В разделе мы посмотрим взаимоувязку работы скриптов в профиле.
Самым полезным компонентом профиля является скрипт. Скрипты выполняют всю полезную работу по взаимодействию с оборудованием.
Скрипт представляет собой файл (модуль `Python`) реализующий один интерфейс (`SA Interface`). Наследуется от класса `noc.core.script.base.BaseScript`
и реализует метод `execute()`, вызываемый при запуске. Основные компоненты скрипта:
* `name` - имя скрипта. Строится по шаблону: `<profile_name>.<script_name>`
* `interface` - ссылка на класс реализуемого интерфейса
* `execute_snmp()` - вызывается если приоритет исполнения (`access_preference`) выставлен в `SNMP`
* `execute_cli()` - вызывается если приоритет исполнения (`access_preference`) выставлен в `CLI`
* `execute()` - вызывается при начале выполнения
<!-- prettier-ignore -->
!!! note
Как и профиль, скрипты считываются при старте NOC'а и кэшируется. Поэтому, для того чтобы, NOC восприняла изменения необходим перезапуск.
Правило не распространяется на отладку через `./noc script`
Обязательным является скрипт `get_version`, через него получается базовая информация об оборудовании: производитель, версия ПО, модель.
Второй по важности скрипт - `get_capabilities` через него определяется поддержка устройством различных протоколов (`SNMP`) и технологий.
<!-- prettier-ignore -->
!!! note
В работе скриптов `get_version` и `get_capabilities` запрещено использовать вызов других скриптов, иначе может получиться циклическая зависимость.
Для того чтобы вызвать из скрипта метод профиля (`SA Profile`) применяется конструкция `self.profile.<method_name()`
### Отладка
Для отладки профиля используется инструмент ``./noc script``. Он позволяет запускать скрипты из профиля в режиме отладки. Делается это следующим образом:
``./noc script --debug <имя_скрипта> <имя_объекта> <параметры>``, где
* ``<имя_скрипта>`` - полное имя скрипта (в формате <папка1>.<папка2>.<имя_скрипта>
* ``<имя_объекта>`` - имя Объекта (из меню Объекты -> Список объектов)
* ``<параметры>`` - параметры (не обязательно, только если используются)
Для удобства, параметры, можно передавать в файле формата JSON, это не требует добавление объекта в систему.
``./noc script --debug <имя_скрипта> <путь_к_файлу_json>``
![](images/json_example.json)
## Базовый класс профиля
::: noc.core.profile.base:BaseProfile
rendering:
show_source: false
......@@ -2,8 +2,8 @@
Представляют собой пользовательский интерфейс без возможности изменения данных под статичный HTML и обмен ссылками. В основе шаблонизатор [`Jinja`](https://jinja.com). Базовой директории является `<noc>/services/card/`, сама карточка состоит из двух компонентов:
* Бэкэнд (`Backend`), подготовливающий данные для шаблонизатора. Раположены в папке `cards/<card_name>.py`
* Шаблон HTML для формировании страницы карточки. Расположены в папке `templates` расширение `<template_name>.html.j2`
* Бэкэнд (`Backend`), подготавливающий данные для шаблонизатора. Раcположены в папке `cards/<card_name>.py`
* Шаблон HTML для формирования страницы карточки. Расположены в папке `templates` расширение `<template_name>.html.j2`
<!-- prettier-ignore -->
!!! note
......@@ -16,7 +16,7 @@
* `<root>` - базовый URL НОКа
* `<card_name>` - имя карточки, указанное в аттрибуте `name`
* `<ID>` - идентификатор запрашиваемых данных, в случае заполненного `model`, будет прозведён поиск инстанса по идентификатору. Если поиск не удался - будет выдана страница `Not Found`
* `<ID>` - идентификатор запрашиваемых данных, в случае заполненного `model`, будет произведён поиск инстанса по идентификатору. Если поиск не удался - будет выдана страница `Not Found`
Полезные методы класса `BaseCard`:
......@@ -30,6 +30,7 @@
Файл карточки:
```python
from noc.inv.models.firmware import Firmware
from .base import BaseCard # Базовый класс
......@@ -40,13 +41,15 @@ class FirmwarePlanCard(BaseCard):
model = Firmware # Ссылка на модель
def get_data(self): # Формирование данных для шаблонизатора
return {"object": self.object}
# return {"object": self.object}
...
```
Шаблон `html`
```html
```
<table class="table table-condensed table-hover">
<tbody>
</tbody>
......@@ -91,6 +94,7 @@ class FirmwarePlanCard(BaseCard):
Карточка `path` вывод на географическую карту путь между парой `ManagedObject`
```python
class PathCard(BaseCard):
name = "path"
default_template_name = "path"
......@@ -109,7 +113,7 @@ class PathCard(BaseCard):
В некоторых случаях есть необходимость формировать динамическое наполнение через `API` вместо формирования статического HTML. В этом случае отдаваемые данные формируются через метод `get_ajax_data`. Примером такой карточки служит `alarmheat` - `cards/alarmheat.py`:
```
```python
class AlarmHeatCard(BaseCard):
name = "alarmheat"
......
# Scripts
## Описание
Реализация интерфейсов и работы с оборудованием осуществляется в файлах скриптов.
В них, путём наследования класса :py:class:`noc.core.script.base.BaseScript` реализуется логика работы с оборудованием и нормализация полученных данных для передачи в NOC.
## Список скриптов
| Название скрипта | Интерфейс | Generic | Назначение |
| --- | --- | --- | --- |
| [get_version](get_version.md) | `IGetVersion` | x | Сбор версии и платформы устройства |
| [get_capabiliries](get_capabilities.md) | `get_capabilities` | v | Сбор поддержки оборудованием функционала (протоколонов ) |
| [get_config](get_config.md) | `IGetConfig` | x | Сбор конфигурации |
| [get_interfaces](get_interfaces.md) | `IGetInterfaces` | v | Запрашивает список интерфейсов с оборудования. |
| [get_inventory](get_inventory.md) | `IGetInventory` | v | Для сбора состава оборудования |
| [get_chassis_id](get_chassis_id.md) | `IGetChassisid` | v | Заправшивает `MAC` адрес устройства (шасси) |
| [get_fqdn](get_fqdn.md) | `IGetFqdn` | v | Запрашивает `hostname` устройства |
| [get_mac_address_table](get_mac_address_table.md) | `IGetMACAddressTable` | v | Собирает таблицу `MAC` адресов устройства |
| [get_arp](get_arp.md) | `IGetArp` | v | Собирает таблицу `ARP` устройства |
| `get_<method>_neighnbors` | `IGet<method>Neighbors` | v | Заправшивает таблицу соседей устройства указанного метода |
## Структура скрипта
Пример файла скрипта:
```python
from noc.core.script.base import BaseScript
from noc.sa.interfaces.igetversion import IGetVersion
import re
class Script(BaseScript):
name = "Huawei.VRP.get_version"
cache = True
interface = IGetVersion
def execute_cli(self, **kwargs):
v = self.cli("display version")
...
def execute_snmp(self, **kwargs):
v = self.snmp.get("1.XXXX")
...
```
Структура файла скрипта следующая
1. **Область импорта**. В ней, мы импортируем базовый класс (строка 1) скрипта и интерфейс, реализуемый скриптом (строка 2). Здесь же можно импортировать необходимые для работы скрипта модули. Например, модуль поддержки регулярных выражений (строка 3)
2. После импорта необходимых модулей мы объявляем класс `Script`, наследую его от базового класса (`BaseScript`). После указываем полное имя скрипта, интерфейс и есть ли необходимость кэшировать результат выполнения.
3. **cache** - выставленный в `True` кэширует результат выполнения скрипта. При вызове из других скриптов через `self.scripts.<script_name>()`
4. Методы работы с оборудованием - `execute_snmp()` и `execute_cli()` очерёдность выполнения задаётся приоритетом. Приоритет можно задать в настройках [ManagedObject](../../../user/reference/concepts/managed-object/index.md)
5. При наличии метода `execute()` исполнение всегда начинается с него, даже при наличии `execute_snmp()` или `execute_cli()` это можно использовать для вмешательство в определении приоритета выволнения
## Взаимодействия с оборудованием
В базовом классе реализованы методы работы с оборудованием:
Для взаимодействия с оборудованием базовый класс `noc.core.script.base.BaseScript`
### CLI
Текстовый интерфейс работы с оборудование. Реализуется через `Telnet` или `SSH`. За работу отвечает метод `BaseScript.cli`,
позволяет выполнять команды на оборудовании. Команда передаётся в виде текстового аргумента, возвращается вывод запрошенной команды в виде строки с текстом.
Дальнейшая работа остаётся на совести разработчика.
.. automethod:: noc.core.script.base.BaseScript.cli