Можжевельник: Выращивание Можжевельника В Домашних Условиях



Можжевельник: выращивание можжевельника в домашних условиях

Привет Хабр! Меня зовут Дмитрий и я разработчик DCIменеджер — панели управления оборудованием ISPsystem. Я довольно долго провёл в команде, занимающейся разработкой программного обеспечения для управления коммутаторами.

Вместе мы переживали взлеты и падения: от написания сервисов по управлению оборудованием до краха офисной сети и часовых свиданий в серверной в надежде не потерять своих близких.

И вот пришло время испытаний.

Некоторые процессоры нам удалось охватить готовыми решениями для тестирования.

Но с Джунипером это не сработало.

Исследования и внедрение послужили идеей написания этой статьи.

Если интересно, добро пожаловать под кат! DCImanager работает с разными типами оборудования: коммутаторами, распределителями питания, серверами.

В настоящее время DCImanager поддерживает четыре обработчика переключателей.

Два по протоколу SNMP (Cisco Catalyst и общий snmp common) и еще два по протоколу NETCONF (Juniper с поддержкой ELS и без нее).

Все работы с оборудованием мы подробно освещаем тестами.

Для автоматического тестирования невозможно использовать реальное оборудование: тесты запускаются при каждом нажатии и выполняются параллельно.

Вот почему мы стараемся использовать эмуляторы.

Обработчики, поддерживающие протокол SNMP, нам удалось охватить тестами с использованием библиотеки SNMP Agent Simulator. Но были проблемы с Джунипером.

Поискав готовые решения, мы выбрали пару библиотек, но одна из них не запустилась, а другая не сделала того, что нужно — я потратил больше времени на то, чтобы оживить это чудо.

Возник вопрос, как эмулировать работу коммутаторов Juniper? Juniper использует протокол NETCONF, который, в свою очередь, работает поверх SSH. В голове промелькнула идея написать небольшой сервис, который бы работал поверх SSH и эмулировал работу свитча.

Соответственно, нам понадобится сам сервис, а также «снимок» Juniper для эмуляции данных.

В snmpsim снимок представляет собой полную копию состояния коммутатора со всеми поддерживаемыми OID и их текущими значениями.

В Джунипере все немного сложнее: такую картинку сделать нельзя.

Здесь имеется в виду снапшот как набор шаблонов типа: запрос-ответ.



Часть первая: посадка архитектуры

Сейчас мы активно расширяем наш «зоопарк» процессоров для работы со свитчами.

Скоро у нас появятся новые процессоры, и мы не сможем охватить их все готовыми решениями для тестирования.

Однако можно попробовать написать общую сервисную архитектуру, которая будет моделировать работу разных устройств по разным протоколам.

В самом простом варианте — фабрика, которая в зависимости от протокола и обработчика (некоторые свитчи могут работать по нескольким протоколам) будет возвращать объект свитча, в котором уже будет реализована вся логика его поведения.

В случае Juniper это небольшой парсер запросов.

В зависимости от входного rpc-запроса с параметрами он выполнит необходимые действия.

Важное ограничение: мы не сможем полностью смоделировать работу переключателя.

На описание всей логики уйдет много времени, а добавляя новый функционал в реальный обработчик, нам придется редактировать макет переключателя.



Часть вторая: выбор почвы для посадки

Мой взгляд упал на библиотеку paramiko, предоставляющую удобный интерфейс для работы по протоколу SSH. Для начала я не хотел разбирать архитектуру, а проверил базовые вещи, например соединение и какой-нибудь простой запрос.

Мы все еще проводим исследования.

Поэтому с авторизацией не заморачиваемся: простой ServerInterface и сокет-сервер в сочетании дают нам нечто похожее на рабочий вариант:

  
  
  
  
  
  
  
  
  
  
   

class SshServer(paramiko.ServerInterface): def check_auth_password(self, user, password): if user == SSH_USER_NAME and password == SSH_USER_PASSWORD: return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) socket.bind(("127.0.0.1", 8300)) socket.listen(10) client, address = socket.accept() session = paramiko.Transport(client) server = SshServer() session.start_server(server=server)

Примерная реализация того, что хотелось бы видеть, но выглядит пугающе Когда клиент подключается к серверу, второй должен ответить списком своих возможностей.

Например вот так:

reply = """ <hello> <capabilities> <capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability> <capability>xml.juniper.net/netconf/junos/1.0</capability> <capability>xml.juniper.net/dmi/system/1.0</capability> </capabilities> <session-id>1</session-id> </hello> ]]>]]> """ socket.send(reply)

Да, это XML ]]> ]]> Во всяком случае, код нестабильный.

В этой реализации есть проблема с закрытием сокета.

Я нашел пару зарегистрированных проблем с этой проблемой в Paramiko. Я отложил его на время, решив проверить оставшийся вариант.

Часть третья: посадка

Туз в рукаве — перевернутый.

Это среда разработки сетевых приложений с поддержкой большого количества протоколов.

Он имеет обширную документацию и замечательный модуль.

Кред , что нам поможет. Кред — это механизм аутентификации, который позволяет подключаться к системе по различным сетевым протоколам в зависимости от ваших требований.

Для организации всей логики используется Область — часть приложения, отвечающая за бизнес-логику и доступ к ее объектам.

Но обо всем по порядку.

Ядро входа в систему Портал .

Если мы хотим написать надстройку по сетевому протоколу, мы определяем стандартный портал.

У него уже есть методы:

  • логин (даёт клиенту доступ к подсистеме)
  • RegisterChecker (непосредственная проверка учетных данных).

Объект Realm используется для привязки бизнес-логики к системе аутентификации.

Поскольку клиент уже авторизован, именно здесь начинается логика нашей надстройки SSH. У этого интерфейса есть только один метод requestAvatar, который вызывается при успешной авторизации в Портале и возвращает основной объект — SwitchProtocolAvatar:

@implementer(portal.IRealm) class SwitchRealm(object): def __init__(self, switch_obj): self.switch_obj = switch_obj def requestAvatar(self, avatarId, mind, *interfaces): return interfaces[0], SwitchProtocolAvatar(avatarId, switch_obj=self.switch_obj), lambda: None

Простейшая реализация объекта Realm, возвращающая требуемый аватар.

За управление бизнес-логикой отвечают специальные объекты — Аватары.

В нашем случае именно здесь начинается надстройка к протоколу SSH. При отправке запроса данные поступают в SwitchProtocolAvatar, который проверяет движок запросов и обновляет конфигурацию:

class SwitchProtocolAvatar(avatar.ConchUser): def __init__(self, username, switch_core): avatar.ConchUser.__init__(self) self.username = username self.channelLookup.update({b'session': session.SSHSession}) netconf_protocol = switch_core.get_netconf_protocol() if netconf_protocol: self.subsystemLookup.update({b'netconf': netconf_protocol})

Проверяем подсистему и обновляем конфигурацию при условии, что этот обработчик переключения работает через NETCONF Кстати о протоколах.

Не забывайте, что мы работаем с NETCONF, и приступим.

Протокол используется для написания дополнений к существующим протоколам и реализации собственной логики.

Интерфейс этого класса прост:

  • dataReceived — используется для обработки событий получения данных;
  • makeConnection — используется для установления соединения;
  • ConnectionMade — используется, когда соединение уже установлено.

    Здесь вы можете определить некоторую логику, прежде чем клиент начнет отправлять запросы.

    В нашем случае нам нужно отправить список наших возможностей.



class Netconf(Protocol): def __init__(self, capabilities=None): self.session_count = 0 self.capabilities = capabilities def __call__(self, *args, **kwargs): return self def connectionMade(self): self.session_count += 1 self.send_capabilities() def send_capabilities(self): rpc_capabilities_reply = "<hello><capabilities>{capabilities}</capabilities>" \ "<session-id>{session_id}</session-id></hello>]]>]]>" rpc_capabilities = "".

join(f"<capability>{cap}</capability>" for cap in self.capabilities) self.transport.write(rpc_capabilities_reply.format(capabilities=rpc_capabilities, session_id=self.session_count)) def dataReceived(self, data): # Process received data pass

Минимальная реализация оболочки протокола.

Удалена ненужная логика для ясности.

Начинаем скатывать нашу матрешку.

Поскольку мы используем надстройку для SSH, нам необходимо реализовать логику сервера SSH. В нем мы определим ключи для сервера и обработчики для SSH-сервисов.

Реализация этого класса нас не особо интересует, так как авторизация будет по паролю:

class SshServerFactory(factory.SSHFactory): protocol = SSHServerTransport publicKeys = {b'ssh-rsa': keys.Key.fromFile(SERVER_RSA_PUBLIC)} privateKeys = {b'ssh-rsa': keys.Key.fromFile(SERVER_RSA_PRIVATE)} services = { b'ssh-userauth': userauth.SSHUserAuthServer, b'ssh-connection': connection.SSHConnection } def getPrimes(self): return PRIMES

Реализация SSH-сервера Для работы SSH-сервера необходимо определить логику сеансов, которая работает вне зависимости от того, по какому протоколу они к нам пришли и какой интерфейс запрошен:

class EchoProtocol(protocol.Protocol): def dataReceived(self, data): if data == b'\r': data = b'\r\n' elif data == b'\x03': # Ctrl+C self.transport.loseConnection() return self.transport.write(data) class Session: def __init__(self, avatar): pass def getPty(self, term, windowSize, attrs): pass def execCommand(self, proto, cmd): pass def openShell(self, transport): protocol = EchoProtocol() protocol.makeConnection(transport) transport.makeConnection(session.wrapProtocol(protocol)) def eofReceived(self): pass def closed(self): pass

Логика сеанса для всех описанных интерфейсов Чуть не забыл про сам обработчик.

После всех проверок и авторизаций логика перемещается в объект, эмулирующий работу коммутатора.

Здесь можно определить логику обработки запросов: получение или редактирование интерфейсов, настройку устройств и т.д.

class Juniper: def __init__(self): self.protocol = Netconf(capabilities=self.capabilities()) def get_netconf_protocol(self): return self.protocol @staticmethod def capabilities(): return [ "Candidate1_0urn:ietf:params:xml:ns:netconf:capability:candidate:1.0", "urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0", "urn:ietf:params:xml:ns:netconf:capability:validate:1.0", "urn:ietf:params:xml:ns:netconf:capability:url:1.0Эprotocol=http,ftp,file", "urn:ietf:params:netconf:capability:candidate:1.0", "urn:ietf:params:netconf:capability:confirmed-commit:1.0", "urn:ietf:params:netconf:capability:validate:1.0", "urn:ietf:params:netconf:capability:url:1.0Эscheme=http,ftp,file" ]

Основная логика обработчика.

Вырезали весь функционал и обработку запросов, оставив только приобретение возможностей Что ж, мы наконец-то объединим все это воедино.

Регистрируем адаптер сеанса (описываем поведение подключения), определяем метод подключения по логину и паролю, настраиваем Портал и запускаем наш сервис:

components.registerAdapter(Session, SwitchProtocolAvatar, session.ISession) switch_factory = SwitchFactory() switch = switch_factory.get("juniper") portal = portal.Portal(CustomRealm(switch)) credential_source = InMemoryUsernamePasswordDatabaseDontUse() credential_source.addUser(b'admin', b'admin') portal.registerChecker(credential_source) SshServerFactory.portal = portal reactor.listenTCP(830, SshServerFactory()) reactor.run()

Настройка и запуск сервера Запускаем мок-сервер.

Для проверки работоспособности можно подключиться с помощью библиотеки ncclient. Достаточно простой проверки соединения и просмотра возможностей сервера:

from ncclient import manager connection = manager.connect(host="127.0.0.1", port=830, username="admin", password="admin", timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) for capability in connection.server_capabilities: print(capability)

Подключаемся к мок-серверу по протоколу NETCONF и выводим список возможностей сервера.

Результат запроса представлен ниже.

Мы успешно установили соединение, и сервер выдал нам список своих возможностей:

Candidate1_0urn:ietf:params:xml:ns:netconf:capability:candidate:1.0 urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0 urn:ietf:params:xml:ns:netconf:capability:validate:1.0 urn:ietf:params:xml:ns:netconf:capability:url:1.0Эprotocol=http,ftp,file urn:ietf:params:netconf:capability:candidate:1.0 urn:ietf:params:netconf:capability:confirmed-commit:1.0 urn:ietf:params:netconf:capability:validate:1.0 urn:ietf:params:netconf:capability:url:1.0Эscheme=http,ftp,file

Возможности сервера

Заключение

У этого решения есть немало плюсов и минусов.

С одной стороны, мы тратим много времени на реализацию и описание всей логики обработки запросов.

С другой стороны, мы получаем возможность гибкой настройки и эмуляции поведения.

Но главное — масштабируемость.

Платформа Twisted богата функциональностью и поддерживает большое количество протоколов, поэтому вы можете легко описывать новые интерфейсы обработчиков.

И если все хорошо продумать, эту архитектуру можно использовать не только для работы с коммутаторами, но и для другого оборудования.

Хотелось бы узнать мнение читателей.

Делали ли вы это и если да, то какие технологии использовали и как выстроили процесс тестирования? Теги: #python #программирование #разработка с использованием #ispsystem #juniper #juniper

Вместе с данным постом часто просматривают: