Русский
Русский
English
Статистика
Реклама

Postman

Автоматизация сетевых сервисов или как собрать виртуальную лабораторию при помощи OpenDaylight, Postman и Vrnetlab

07.07.2020 18:11:11 | Автор: admin


В этой статье я расскажу, как настроить OpenDaylight для работы с сетевым оборудованием, а также покажу, как с помощью Postman и простых RESTCONF запросов этим оборудованием можно управлять. Работать с железом мы не будем, а вместо этого развернем небольшие виртуальные лаборатории с одним-единственным роутером с помощью Vrnetlab поверх Ubuntu 20.04 LTS.


Подробную настройку я покажу сначала на примере роутера Juniper vMX 20.1R1.11, а затем мы сравним ее с настройкой Cisco xRV9000 7.0.2.


Содержание


  • Необходимые знания
  • Часть 1: кратко обсуждаем OpenDaylight (далее по тексту ODL), Postman и Vrnetlab и зачем они нам потребуются
  • Часть 2: описание виртуальной лаборатории
  • Часть 3: настраиваем OpenDaylight
  • Часть 4: настраиваем Vrnetlab
  • Часть 5: с помощью Postman подключаем виртуальный роутер (Juniper vMX) к ODL
  • Часть 6: получаем и изменяем конфигурацию роутера с помощью Postman и ODL
  • Часть 7: добавляем Cisco xRV9000
  • Заключение
  • P.S.
  • Список Литературы

Необходимые знания


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



Часть 1: немного теории



  • Открытая SDN платформа для управления и автоматизации всевозможных сетей, поддерживаемая Linux Foundation
  • Java inside
  • Основан на Model-Driven Service Abstraction Level (MD-SAL)
  • Использует YANG модели для автоматического создания RESTCONF API сетевых устройств

Основной модуль для управления сетью. Именно через него мы будем общаться с подключенными устройствами. Управляется через свой собственный API.
Более подробно про OpenDaylight можно прочитать здесь.



  • Инструмент для тестирования API
  • Простой и удобный для использования интерфейс

В нашем случае он нам интересен как средство для отправки REST запросов на API OpenDaylight'а. Можно, конечно, и вручную запросы отправлять, но в Postman все выглядит очень наглядно и для наших целей подходит как нельзя лучше.
Для желающих покопаться: по нему написано много обучающих материалов (например).



  • Инструмент для развертывания виртуальных роутеров в Docker'е
  • Поддерживает: Cisco XRv, Juniper vMX, Arista vEOS, Nokia VSR и др.
  • Open Source

Очень интересный, но малоизвестный инструмент. В нашем случае с его помощью мы запустим Juniper vMX и Cisco xRV9000 на обычной Ubuntu 20.04 LTS.
Прочитать подробнее о нем можно на странице проекта.


Часть 2: лабораторная работа


В рамках этого туториала мы будем настраиваить следующую систему:


Как это работает


  • Juniper vMX поднимается в Docker контейнере (средствами Vrnetlab) и функционирует как самый обычный виртуальный роутер.
  • ODL подключен к роутеру и позволяет управлять им.
  • Postman запущен на отдельной машине и через него мы отправляем команды ODL: на подключение/удаление роутера, изменение конфигурации и тп.

Комментарий к устройству системы

Juniper vMX и ODL требуют довольно много ресурсов для своей стабильной работы. Один только vMX просит 6 Gb оперативной памяти и 4 ядра. Поэтому было принято решение вынести всех "тяжеловесов" на отдельную машину (Heulett Packard Enterprise MicroServer ProLiant Gen8, Ubuntu 20.04 LTS). Роутер, конечно, на ней не "летает", но для небольших экспериментов производительности хватает.


Часть 3: настраиваем OpenDaylight



Актуальная версия ODL на момент написания статьи Magnesium SR1
1) Устанавливаем Java OpenJDK 11 (за более подробной установкой сюда)


ubuntu:~$ sudo apt install default-jdk

2) Находим и скачиваем свежую сборку ODL отсюда
3) Разархивируем скачанный архив
4) Переходим в полученную директорию
5) Запускаем ./bin/karaf


На этом шаге ODL должен запуститься и мы окажемся в консоли (Для доступа извне используется порт 8181, чем мы воспользуемся далее).


Далее устанавливаем ODL Features, предназначенные для работы с протоколами NETCONF и RESTCONF. Для этого в консоли ODL выполняем:


opendaylight-user@root> feature:install odl-netconf-topology odl-restconf-all

На этом простейшая настройка ODL завершена. (Более подробно можно прочитать здесь).


Часть 4: настраиваем Vrnetlab



Подготовка системы


Перед установкой Vrnetlab необходимо поставить требуемые для его работы пакеты. Такие как Docker, git, sshpass:


ubuntu:~$ sudo apt updateubuntu:~$ sudo apt -y install python3-bs4 sshpass makeubuntu:~$ sudo apt -y install gitubuntu:~$ sudo apt install -y \    apt-transport-https ca-certificates \    curl gnupg-agent software-properties-commonubuntu:~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -ubuntu:~$ sudo add-apt-repository \   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \   $(lsb_release -cs) \   stable"ubuntu:~$ sudo apt updateubuntu:~$ sudo apt install -y docker-ce docker-ce-cli containerd.io

Установка Vrnetlab


Для установки Vrnetlab клонируем соответствующий репозиторий с github:


ubuntu:~$ cd ~ubuntu:~$ git clone https://github.com/plajjan/vrnetlab.git

Переходим в директорию vrnetlab:


ubuntu:~$ cd ~/vrnetlab

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


ubuntu:~/vrnetlab$ lsCODE_OF_CONDUCT.md  config-engine-lite        openwrt           vr-bgpCONTRIBUTING.md     csr                       routeros          vr-xconLICENSE             git-lfs-repo.sh           sros              vrnetlab.shMakefile            makefile-install.include  topology-machine  vrpREADME.md           makefile-sanity.include   veos              vsr1000ci-builder-image    makefile.include          vmx               xrvcommon              nxos                      vqfx              xrv9k

Создаем image роутера


Каждый роутер, который поддерживается Vrnetlab, имеет свою уникальную процедуру настройки. В случае Juniper vMX нам достаточно закинуть .tgz архив с роутером (скачать его можно с официального сайта) в директорию vmx и выполнить команду make:


ubuntu:~$ cd ~/vrnetlab/vmxubuntu:~$ # Копируем в эту директорию .tgz архив с роутеромubuntu:~$ sudo make

Сборка образа vMX займет порядка 10-20 минут. Самое время сходить заварить кофе!


Почему же так долго, спросите вы?

Перевод ответа автора на этот вопрос:


"Это связано с тем, что при первом запуске VCP (Control Plane) считывает файл конфигурации, который определяет, будет ли он работать в качестве VRR VCP в vMX. Ранее этот запуск выполнялся во время запуска Docker, но это означало, что VCP всегда перезапускался один раз, прежде чем виртуальный маршрутизатор становился доступным, что приводило к длительному времени загрузки (около 5 минут). Теперь первый запуск VCP выполняется во время сборки образа Docker, и поскольку сборка Docker не может быть запущена с параметром --privileged, это означает, что qemu работает без аппаратного ускорения KVM и, таким образом, сборка занимает очень много времени. Во время этого процесса выводится много логов, так что, по крайней мере, вы сможете увидеть, что происходит. Я думаю, что длительная сборка не так страшна, потому что образ мы создаем один раз, а запускаем множество."


После можно будет увидеть image нашего роутера в Docker:


ubuntu:~$ sudo docker image listREPOSITORY          TAG                 IMAGE ID            CREATED             SIZEvrnetlab/vr-vmx     20.1R1.11           b1b2369b453c        3 weeks ago         4.43GBdebian              stretch             614bb74b620e        7 weeks ago         101MB

Запускаем контейнер vr-vmx


Запускаем командой:


ubuntu:~$ sudo docker run -d --privileged --name jun01 b1b2369b453c

Далее можем посмотреть информацию об активных контейнерах:


ubuntu:~$ sudo docker container listCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS                                                 NAMES120f882c8712        b1b2369b453c        "/launch.py"        2 minutes ago       Up 2 minutes (unhealthy)   22/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp   jun01

Подключаемся к роутеру


IP-адрес сетевого интерфейса роутера можно получить следующей командой:


ubuntu:~$ sudo docker inspect --format '{{.NetworkSettings.IPAddress}}' jun01172.17.0.2

По умолчанию, Vrnetlab создает у роутера пользователя vrnetlab/VR-netlab9. Подключаемся с помощью ssh:


ubuntu:~$ ssh vrnetlab@172.17.0.2The authenticity of host '172.17.0.2 (172.17.0.2)' can't be established.ECDSA key fingerprint is SHA256:g9Sfg/k5qGBTOX96WiCWyoJJO9FxjzXYspRoDPv+C0Y.Are you sure you want to continue connecting (yes/no/[fingerprint])? yesWarning: Permanently added '172.17.0.2' (ECDSA) to the list of known hosts.Password:--- JUNOS 20.1R1.11 Kernel 64-bit  JNPR-11.0-20200219.fb120e7_builvrnetlab> show versionModel: vmxJunos: 20.1R1.11

На этом настройка роутера завершена.
Рекомендации по установке для роутеров различных вендоров можно найти на github проекта в соответствующих директориях.


Часть 5: Postman подключаем роутер к OpenDaylight


Установка Postman


Для установки достаточно скачать приложение отсюда.


Подключение роутера к ODL


Создадим PUT запрос:


  1. Строка запроса:
    PUT http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01
    
  2. Тело запроса (вкладка Body):
    <node xmlns="urn:TBD:params:xml:ns:yang:network-topology"><node-id>jun01</node-id><host xmlns="urn:opendaylight:netconf-node-topology">172.17.0.2</host><port xmlns="urn:opendaylight:netconf-node-topology">22</port><username xmlns="urn:opendaylight:netconf-node-topology">vrnetlab</username><password xmlns="urn:opendaylight:netconf-node-topology">VR-netlab9</password><tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only><schema-cache-directory xmlns="urn:opendaylight:netconf-node-topology">jun01_cache</schema-cache-directory></node>
    
  3. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin. Это необходимо для доступа к ODL:
  4. На вкладке Headers необходимо добавить два заголовка:
    • Accept application/xml
    • Content-Type application/xml

Наш запрос сформирован. Отправляем. Если все было настроено правильно, то нам должен вернуться статус "201 Created":


Что делает этот запрос?

Мы создаем node внутри ODL с параметрами реального роутера, к которому мы хотим получить доступ.


xmlns="urn:TBD:params:xml:ns:yang:network-topology"xmlns="urn:opendaylight:netconf-node-topology"

Это внутренние пространства имен XML (XML namespace) для ODL в соответствии с которыми он создает node.
Далее, соответственно, имя роутера это node-id, адрес роутера host и тд.
Самая интересная строчка последняя. Schema-cache-directory создает директорию, в которую выкачиваются все файлы YANG Schema подключенного роутера. Найти их можно в $ODL_ROOT/cache/jun01_cache.


Проверяем подключение роутера


Создадим GET запрос:


  1. Строка запроса:
    GET http://10.132.1.202:8181/restconf/operational/network-topology:network-topology/topology/topology-netconf/
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

Отправляем. Должны получить статус "200 OK" и список всех поддерживаемых устройством YANG Schema:


Комментарий: Чтобы увидеть последнее, в моем случае необходимо было подождать порядка 10 минут после выполнения PUT, пока все YANG sсhema выгрузятся на ODL. До этого момента при выполнении данного GET запроса будет выведено следующее:


Удаляем роутер


Создадим DELETE запрос:


  1. Строка запроса:
    DELETE http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

Часть 6: Изменяем конфигурацию роутера


Получаем конфигурацию


Создадим GET запрос:


  1. Строка запроса:
    GET http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

Отправляем. Должны получить статус "200 OK" и конфигурацию роутера:


Создаем конфигурацию


В качестве примера создадим следующую конфигурацию и поизменяем ее:


protocols {    bgp {        disable;        shutdown;    }}

Создадим POST запрос:


  1. Строка запроса:
    POST http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
    
  2. Тело запроса (вкладка Body):
    <bgp xmlns="http://personeltest.ru/away/yang.juniper.net/junos/conf/protocols"><disable/><shutdown></shutdown></bgp>
    
  3. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.
  4. На вкладке Headers необходимо добавить два заголовка:
    • Accept application/xml
    • Content-Type application/xml

После отправки должны получить статус "204 No Content"


Чтобы проверить, что конфигурация изменилась, можно воспользоваться предыдущим запросом. Но для примера мы создадим еще один, который выведет нам информацию только о сконфигурированных на роутере протоколах.
Создадим GET запрос:


  1. Строка запроса:
    GET http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

После выполнения запроса увидим следующее:


Изменяем конфигурацию


Изменим информацию о протоколе BGP. После наших действий она будет выглядеть следующим образом:


protocols {    bgp {        disable;    }}

Создадим PUT запрос:


  1. Строка запроса:
    PUT http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
    
  2. Тело запроса (вкладка Body):
    <protocols xmlns="http://personeltest.ru/away/yang.juniper.net/junos/conf/protocols"><bgp>    <disable/></bgp></protocols>
    
  3. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.
  4. На вкладке Headers необходимо добавить два заголовка:
    • Accept application/xml
    • Content-Type application/xml

Используя предыдущий GET запрос, видим изменения:


Удаляем конфигурацию


Создадим DELETE запрос:


  1. Строка запроса:
    DELETE http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

При вызове GET запроса с информацией о протоколах увидим следующее:


Дополнение:


Для того, чтобы изменить конфигурацию, не обязательно отправлять тело запроса в формате XML. Это можно сделать и в формате JSON.
Для этого, например, в запросе PUT на изменение конфигурации заменим тело запроса на:


{    "junos-conf-protocols:protocols": {        "bgp": {            "description" : "Changed in postman"         }    }}

Не забудьте поменять на вкладке Headers заголовки на:


  • Accept application/json
  • Content-Type application/json

После отправки получим следующий результат (Ответ смотрим используя GET запрос):


Часть 7: добавляем Cisco xRV9000


Что мы все о Джунипере, да о Джунипере? Давайте о Cisco поговорим!
У меня нашелся xRV9000 версии 7.0.2 (зверюга, которому нужны 8Gb RAM и 4 ядра. В свободном доступе не лежит, поэтому обращайтесь в Cisco) его и запустим.


Запуск контейнера


Процесс создания Docker контейнера практически ничем не отличается от Juniper. Аналогично, закидываем .qcow2 файл с роутером в директорию, соответствующую его названию, (в данном случае xrv9k) и выполняем команду make docker-image.
Через несколько минут видим, что образ создался:


ubuntu:~$ sudo docker image lsREPOSITORY          TAG                 IMAGE ID            CREATED             SIZEvrnetlab/vr-xrv9k   7.0.2               54debc7973fc        4 hours ago         1.7GBvrnetlab/vr-vmx     20.1R1.11           b1b2369b453c        4 weeks ago         4.43GBdebian              stretch             614bb74b620e        7 weeks ago         101MB

Производим запуск контейнера:


ubuntu:~$ sudo docker run -d --privileged --name xrv01 54debc7973fc

Через некоторое время смотрим, что контейнер запустился:


ubuntu:~$ sudo docker psCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                 PORTS                                                      NAMES058c5ecddae3        54debc7973fc        "/launch.py"        4 hours ago         Up 4 hours (healthy)   22/tcp, 830/tcp, 5000-5003/tcp, 10000-10099/tcp, 161/udp   xrv01

Подключаемся по ssh:


ubuntu@ubuntu:~$ ssh vrnetlab@172.17.0.2Password:RP/0/RP0/CPU0:ios#show versionMon Jul  6 12:19:28.036 UTCCisco IOS XR Software, Version 7.0.2Copyright (c) 2013-2020 by Cisco Systems, Inc.Build Information: Built By     : ahoang Built On     : Fri Mar 13 22:27:54 PDT 2020 Built Host   : iox-ucs-029 Workspace    : /auto/srcarchive15/prod/7.0.2/xrv9k/ws Version      : 7.0.2 Location     : /opt/cisco/XR/packages/ Label        : 7.0.2cisco IOS-XRv 9000 () processorSystem uptime is 3 hours 22 minutes

Подключаем роутер к OpenDaylight


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


Через некоторое время вызываем GET запрос, чтобы проверить, что все подключилось:


Изменяем конфигурацию


Настроим следующую конфигурацию:


!router ospf LAB mpls ldp auto-config!

Создадим POST запрос:


  1. Строка запроса:
    POST http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/xrv01/yang-ext:mount/Cisco-IOS-XR-ipv4-ospf-cfg:ospf
    
  2. Тело запроса (вкладка Body):
    {    "processes": {        "process": [            {                "process-name": "LAB",                "default-vrf": {                    "process-scope": {                        "ldp-auto-config": [                            null                        ]                    }                }            }        ]    }}
    
  3. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.
  4. На вкладке Headers необходимо добавить два заголовка:
    • Accept application/json
    • Content-Type application/json

После его выполнения должны получить статус "204 No Content".


Проверим, что у нас получилось.
Для этого создадим GET запрос:


  1. Строка запроса:
    GET http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/xrv01/yang-ext:mount/Cisco-IOS-XR-ipv4-ospf-cfg:ospf
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

После выполнения должны увидеть следующее:


Для удаления конфигурации используем DELETE:


  1. Строка запроса:
    DELETE http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/xrv01/yang-ext:mount/Cisco-IOS-XR-ipv4-ospf-cfg:ospf
    
  2. На вкладке Authorization необходимо выставить параметр Basic Auth и логин/пароль: admin/admin.

Заключение


Итого, как вы могли заметить, процедуры подключения Cisco и Juniper к OpenDaylight не отличаются это открывает довольно широкий простор для творчества. Начиная от управления конфигурациями всех компонентов сети и заканчивая созданием собственных сетевых политик.
В этом туториале я привел простейшие примеры того, как можно взаимодействовать с сетевым оборудованием при помощи OpenDaylight. Без сомнения, запросы из приведенных примеров можно сделать сильно сложнее и настраивать целые сервисы одним кликом мыши все ограничено только вашей фантазией*


Продолжение следует...


P.S.


Если вы вдруг все это уже знаете или, наоборот, прошли и вам запал в душу ODL, то рекомендую посмотреть в сторону разработки приложений на контроллере ODL. Начать можно отсюда.
Успешных экспериментов!


Список литературы


  1. Vrnetlab: Emulate networks using KVM and Docker / Brian Linkletter
  2. OpenDaylight Cookbook / Mathieu Lemay, Alexis de Talhouet, Et al
  3. Network Programmability with YANG / Benot Claise, Loe Clarke, Jan Lindblad
  4. Learning XML, Second Edition / Erik T. Ray
  5. Effective DevOps / Jennifer Davis, Ryn Daniels
Подробнее..

Как восстановить NSX Edge и перенести его настройки через API

11.02.2021 12:14:32 | Автор: admin

В этой статье расскажу, как работать через API с NSX Edge. Это решение от VMware выполняет для виртуального дата-центра функции маршрутизации, Firewall, NAT, DHCP, VPN и другие. Благодаря возможностям работы через API отправка запросов к Edge становится удобнее и нагляднее, чем в командной строке.

Описанный здесь способ также решает некоторые проблемы обращения к Edge через vCloud Director. При работе через API у нас есть возможность работать с Edge напрямую через NSX или через vCloud Director, а также с помощью API обращаться к БД vCloud Director. Покажу оба варианта.

Вот наиболее интересные сценарии, когда нам пригодится использование API:

  1. Миграция Edge в другой NSX-менеджер.

  2. Восстановление Edge или части его настроек. Например, если после миграции из одного дата-центра в другой мы также переносим настройки файрвола, VPN, балансировщика нагрузки и т. п.

  3. Резервное копирование настроек. Например, если мы хотим сохранить конфигурацию Edge в формате XML и при необходимости вернуться к ней.

В описании я использую NSX-V 6.4.6 и vCloud Director 10.2, однако статья актуальна и для других версий ПО. Для всех экспериментов пользовался документацией по API отсюда.

Готовим инструмент для работы с API

Для работы с API вы можете использовать любой удобный продукт. В моем примере будем работать через Postman: чаще всего его применяют для тестирования различных API и отправки запросов на сервер. Сами специалисты VMware нередко используют его для работы с API, так что можно считать это косвенной рекомендацией вендора.

Сразу напомню самые распространенные типы запросов:

GET получение информации из инфраструктуры, не выполняет изменения.

POST чаще всего создание нового объекта или добавление конфигурации к существующему.

PUT обновление текущего объекта, старые данные объекта затираются.

DELETE удаление объекта.

Чтобы запросы работали корректно, настроим Postman для работы с NSX-менеджером, который управляет всеми объектами Edge.

  1. Открываем Postman и настраиваем авторизацию. Выбираем тип Basic Auth, указываем логин и пароль от админской учетной записи.

  2. Настраиваем заголовки. Указываем Content-Type: application/xml

  3. Пробуем вывести список Edge командой GET https://nsx-fqdn/api/4.0/edges (где nsx-fqdn это IP-адрес или FQDN NSX-менеджера).

Получили 200 ОК, значит, все в порядке: авторизация, заголовки и другие параметры указаны верно.

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

Восстанавливаем Edge целиком или его отдельные настройки

Возьмем пример, где актуальны все три сценария использования API.

Итак, у нас есть 2 NSX-менеджера, один из которых мы восстановили в изолированную сеть из бэкапа недельной давности, как описано здесь.

Обозначим оригинальный NSX-менеджер как nsx-fqdn-1, а восстановленный NSX-manager как nsx-fqdn-2. Предположим, по какой-то причине объект edge-8 был удален, и нам необходимо его восстановить.

  1. Для начала сформируем запрос на получение конфига этого Edge из восстановленного NSX. Авторизация и заголовки у нас уже настроены, необходимо только указать в запросе нужный FQDN NSX-менеджера.

    GET https://nsx-fqdn-2/api/4.0/edges/edge-8

  2. Получаем вот такую конфигурацию. Часть конфигурации изменил, чтобы избежать утечки данных.

    Конфиг целиком.
    <?xml version="1.0" encoding="UTF-8"?><edge>    <id>edge-8</id>    <version>8</version>    <description></description>    <status>deployed</status>    <tenant>88ed64d3-516d-4932-a262-9987e9779f1e</tenant>    <name>vse-test-delete-edge (877a6842-8a67-4dad-87cf-81e155c45763)</name>    <fqdn>vse-f8b2ccec-ef9b-464f-8bab-eb67e27f15c3</fqdn>    <enableAesni>true</enableAesni>    <enableFips>false</enableFips>    <vseLogLevel>info</vseLogLevel>    <vnics>        <vnic>            <label>vNic_0</label>            <name>vnic0</name>            <addressGroups>                <addressGroup>                    <primaryAddress>esxternal-ip</primaryAddress>                    <secondaryAddresses>                        <ipAddress>esxternal-ip</ipAddress>                    </secondaryAddresses>                    <subnetMask>255.255.255.192</subnetMask>                    <subnetPrefixLength>26</subnetPrefixLength>                </addressGroup>            </addressGroups>            <mtu>1500</mtu>            <type>uplink</type>            <isConnected>true</isConnected>            <index>0</index>            <portgroupId>dvportgroup-731</portgroupId>            <portgroupName>internet</portgroupName>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_1</label>            <name>vnic1</name>            <addressGroups>                <addressGroup>                    <primaryAddress>10.0.0.1</primaryAddress>                    <subnetMask>255.255.255.0</subnetMask>                    <subnetPrefixLength>24</subnetPrefixLength>                </addressGroup>            </addressGroups>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>true</isConnected>            <index>1</index>            <portgroupId>virtualwire-380</portgroupId>            <portgroupName>dvs.VCDVStest-1-5ca1ab95-ded5-4af5-bf90-96eaa70e5512</portgroupName>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_2</label>            <name>vnic2</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>2</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_3</label>            <name>vnic3</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>3</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_4</label>            <name>vnic4</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>4</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_5</label>            <name>vnic5</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>5</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_6</label>            <name>vnic6</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>6</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_7</label>            <name>vnic7</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>7</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_8</label>            <name>vnic8</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>8</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>        <vnic>            <label>vNic_9</label>            <name>vnic9</name>            <addressGroups/>            <mtu>1500</mtu>            <type>internal</type>            <isConnected>false</isConnected>            <index>9</index>            <enableProxyArp>false</enableProxyArp>            <enableSendRedirects>true</enableSendRedirects>        </vnic>    </vnics>    <appliances>        <applianceSize>compact</applianceSize>        <appliance>            <highAvailabilityIndex>0</highAvailabilityIndex>            <vcUuid>500615b5-3f65-146a-1d5c-0dce84fc60ea</vcUuid>            <vmId>vm-4274</vmId>            <resourcePoolId>resgroup-53</resourcePoolId>            <resourcePoolName>System vDC (c8a308dd-2509-48ad-ab8e-54e93938394d)</resourcePoolName>            <datastoreId>datastore-1</datastoreId>            <datastoreName>DATASTORE</datastoreName>            <hostId>host-18</hostId>            <hostName>ESXi-host</hostName>            <vmFolderId>group-v453</vmFolderId>            <vmFolderName>Service VMs</vmFolderName>            <vmHostname>vse-f8b2ccec-ef9b-464f-8bab-eb67e27f15c3-0</vmHostname>            <vmName>vse-test-delete-edge (877a6842-8a67-4dad-87cf-81e155c45763)-0</vmName>            <deployed>true</deployed>            <cpuReservation>                <limit>-1</limit>                <reservation>64</reservation>            </cpuReservation>            <memoryReservation>                <limit>-1</limit>                <reservation>256</reservation>            </memoryReservation>            <edgeId>edge-8</edgeId>            <configuredResourcePool>                <id>resgroup-53</id>                <name>System vDC (c8a308dd-2509-48ad-ab8e-54e93938394d)</name>                <isValid>true</isValid>            </configuredResourcePool>            <configuredDataStore>                <id>datastore-1</id>                <name>DATASTORE</name>                <isValid>true</isValid>            </configuredDataStore>            <configuredHost>                <id>host-18</id>                <name>ESXi-host</name>                <isValid>true</isValid>            </configuredHost>            <configuredVmFolder>                <id>group-v453</id>                <name>Service VMs</name>                <isValid>true</isValid>            </configuredVmFolder>        </appliance>        <deployAppliances>true</deployAppliances>    </appliances>    <cliSettings>        <remoteAccess>false</remoteAccess>        <userName>admin</userName>        <sshLoginBannerText>***************************************************************************NOTICE TO USERS This computer system is the private property of its owner, whetherindividual, corporate or government.  It is for authorized use only.Users (authorized or unauthorized) have no explicit or implicitexpectation of privacy. Any or all uses of this system and all files on this system may beintercepted, monitored, recorded, copied, audited, inspected, anddisclosed to your employer, to authorized site, government, and lawenforcement personnel, as well as authorized officials of governmentagencies, both domestic and foreign. By using this system, the user consents to such interception, monitoring,recording, copying, auditing, inspection, and disclosure at thediscretion of such personnel or officials.  Unauthorized or improper useof this system may result in civil and criminal penalties andadministrative or disciplinary action, as appropriate. By continuing touse this system you indicate your awareness of and consent to these termsand conditions of use. LOG OFF IMMEDIATELY if you do not agree to theconditions stated in this warning. ****************************************************************************</sshLoginBannerText>        <passwordExpiry>99999</passwordExpiry>    </cliSettings>    <features>        <nat>            <version>3</version>            <enabled>true</enabled>            <natRules>                <natRule>                    <ruleId>196609</ruleId>                    <ruleTag>196609</ruleTag>                    <loggingEnabled>false</loggingEnabled>                    <enabled>true</enabled>                    <translatedAddress>esxternal-ip</translatedAddress>                    <ruleType>user</ruleType>                    <action>snat</action>                    <vnic>0</vnic>                    <originalAddress>10.0.0.0/24</originalAddress>                    <snatMatchDestinationAddress>any</snatMatchDestinationAddress>                    <protocol>any</protocol>                    <originalPort>any</originalPort>                    <translatedPort>any</translatedPort>                    <snatMatchDestinationPort>any</snatMatchDestinationPort>                </natRule>                <natRule>                    <ruleId>196610</ruleId>                    <ruleTag>196610</ruleTag>                    <loggingEnabled>false</loggingEnabled>                    <enabled>true</enabled>                    <translatedAddress>10.0.0.3</translatedAddress>                    <ruleType>user</ruleType>                    <action>dnat</action>                    <vnic>0</vnic>                    <originalAddress>esxternal-ip</originalAddress>                    <dnatMatchSourceAddress>any</dnatMatchSourceAddress>                    <protocol>tcp</protocol>                    <originalPort>443</originalPort>                    <translatedPort>8443</translatedPort>                    <dnatMatchSourcePort>any</dnatMatchSourcePort>                </natRule>            </natRules>            <nat64Rules/>        </nat>        <l2Vpn>            <version>2</version>            <enabled>false</enabled>            <logging>                <enable>true</enable>                <logLevel>notice</logLevel>            </logging>        </l2Vpn>        <featureConfig/>        <featureConfig/>        <dns>            <version>2</version>            <enabled>false</enabled>            <cacheSize>16</cacheSize>            <listeners>                <vnic>any</vnic>            </listeners>            <dnsViews>                <dnsView>                    <viewId>view-0</viewId>                    <name>vsm-default-view</name>                    <enabled>true</enabled>                    <viewMatch>                        <ipAddress>any</ipAddress>                        <vnic>any</vnic>                    </viewMatch>                    <recursion>false</recursion>                </dnsView>            </dnsViews>            <logging>                <enable>false</enable>                <logLevel>info</logLevel>            </logging>        </dns>        <syslog>            <version>2</version>            <enabled>false</enabled>            <protocol>udp</protocol>        </syslog>        <sslvpnConfig>            <version>2</version>            <enabled>false</enabled>            <logging>                <enable>true</enable>                <logLevel>notice</logLevel>            </logging>            <advancedConfig>                <enableCompression>false</enableCompression>                <forceVirtualKeyboard>false</forceVirtualKeyboard>                <randomizeVirtualkeys>false</randomizeVirtualkeys>                <preventMultipleLogon>false</preventMultipleLogon>                <clientNotification></clientNotification>                <enablePublicUrlAccess>false</enablePublicUrlAccess>                <timeout>                    <forcedTimeout>0</forcedTimeout>                    <sessionIdleTimeout>10</sessionIdleTimeout>                </timeout>            </advancedConfig>            <clientConfiguration>                <autoReconnect>true</autoReconnect>                <upgradeNotification>false</upgradeNotification>            </clientConfiguration>            <layoutConfiguration>                <portalTitle>VMware</portalTitle>                <companyName>VMware</companyName>                <logoExtention>jpg</logoExtention>                <logoUri>/api/4.0/edges/edge-8/sslvpn/config/layout/images/portallogo</logoUri>                <logoBackgroundColor>56A2D4</logoBackgroundColor>                <titleColor>996600</titleColor>                <topFrameColor>000000</topFrameColor>                <menuBarColor>999999</menuBarColor>                <rowAlternativeColor>FFFFFF</rowAlternativeColor>                <bodyColor>FFFFFF</bodyColor>                <rowColor>F5F5F5</rowColor>            </layoutConfiguration>            <authenticationConfiguration>                <passwordAuthentication>                    <authenticationTimeout>1</authenticationTimeout>                    <primaryAuthServers/>                    <secondaryAuthServer/>                </passwordAuthentication>            </authenticationConfiguration>        </sslvpnConfig>        <featureConfig/>        <highAvailability>            <version>3</version>            <enabled>false</enabled>            <declareDeadTime>15</declareDeadTime>            <logging>                <enable>false</enable>                <logLevel>info</logLevel>            </logging>            <security>                <enabled>false</enabled>            </security>        </highAvailability>        <routing>            <version>3</version>            <enabled>true</enabled>            <routingGlobalConfig>                <ecmp>false</ecmp>                <logging>                    <enable>false</enable>                    <logLevel>info</logLevel>                </logging>            </routingGlobalConfig>            <staticRouting>                <defaultRoute>                    <vnic>0</vnic>                    <mtu>1500</mtu>                    <gatewayAddress>external-ip</gatewayAddress>                    <adminDistance>1</adminDistance>                </defaultRoute>                <staticRoutes/>            </staticRouting>            <ospf>                <enabled>false</enabled>                <ospfAreas>                    <ospfArea>                        <areaId>51</areaId>                        <type>nssa</type>                        <authentication>                            <type>none</type>                        </authentication>                    </ospfArea>                    <ospfArea>                        <areaId>0</areaId>                        <type>normal</type>                        <authentication>                            <type>none</type>                        </authentication>                    </ospfArea>                </ospfAreas>                <ospfInterfaces/>                <redistribution>                    <enabled>false</enabled>                    <rules/>                </redistribution>                <gracefulRestart>true</gracefulRestart>                <defaultOriginate>false</defaultOriginate>            </ospf>        </routing>        <featureConfig/>        <gslb>            <version>2</version>            <enabled>false</enabled>            <serviceTimeout>6</serviceTimeout>            <persistentCache>                <maxSize>20</maxSize>                <ttl>300</ttl>            </persistentCache>            <queryPort>5666</queryPort>            <logging>                <enable>false</enable>                <logLevel>info</logLevel>            </logging>        </gslb>        <firewall>            <version>6</version>            <enabled>true</enabled>            <globalConfig>                <tcpPickOngoingConnections>false</tcpPickOngoingConnections>                <enableFtpLooseMode>false</enableFtpLooseMode>                <tcpAllowOutOfWindowPackets>false</tcpAllowOutOfWindowPackets>                <tcpSendResetForClosedVsePorts>true</tcpSendResetForClosedVsePorts>                <dropInvalidTraffic>true</dropInvalidTraffic>                <logInvalidTraffic>false</logInvalidTraffic>                <tcpTimeoutOpen>30</tcpTimeoutOpen>                <tcpTimeoutEstablished>21600</tcpTimeoutEstablished>                <tcpTimeoutClose>30</tcpTimeoutClose>                <udpTimeout>60</udpTimeout>                <icmpTimeout>10</icmpTimeout>                <icmp6Timeout>10</icmp6Timeout>                <ipGenericTimeout>120</ipGenericTimeout>                <enableSynFloodProtection>false</enableSynFloodProtection>                <logIcmpErrors>false</logIcmpErrors>                <dropIcmpReplays>false</dropIcmpReplays>                <enableSnmpAlg>true</enableSnmpAlg>                <enableFtpAlg>true</enableFtpAlg>                <enableTftpAlg>true</enableTftpAlg>            </globalConfig>            <defaultPolicy>                <action>deny</action>                <loggingEnabled>false</loggingEnabled>            </defaultPolicy>            <firewallRules>                <firewallRule>                    <id>131076</id>                    <ruleTag>131076</ruleTag>                    <name>firewall</name>                    <ruleType>internal_high</ruleType>                    <enabled>true</enabled>                    <loggingEnabled>false</loggingEnabled>                    <description>firewall</description>                    <action>accept</action>                    <source>                        <exclude>false</exclude>                        <vnicGroupId>vse</vnicGroupId>                    </source>                </firewallRule>                <firewallRule>                    <id>131077</id>                    <ruleTag>131077</ruleTag>                    <name>test</name>                    <ruleType>user</ruleType>                    <enabled>true</enabled>                    <loggingEnabled>false</loggingEnabled>                    <action>accept</action>                    <source>                        <exclude>false</exclude>                        <vnicGroupId>vnic-index-1</vnicGroupId>                    </source>                    <application>                        <service>                            <protocol>icmp</protocol>                            <icmpType>any</icmpType>                        </service>                    </application>                </firewallRule>                <firewallRule>                    <id>131075</id>                    <ruleTag>131075</ruleTag>                    <name>default rule for ingress traffic</name>                    <ruleType>default_policy</ruleType>                    <enabled>true</enabled>                    <loggingEnabled>false</loggingEnabled>                    <description>default rule for ingress traffic</description>                    <action>deny</action>                </firewallRule>            </firewallRules>        </firewall>        <loadBalancer>            <version>2</version>            <enabled>false</enabled>            <enableServiceInsertion>false</enableServiceInsertion>            <accelerationEnabled>false</accelerationEnabled>            <monitor>                <monitorId>monitor-1</monitorId>                <type>tcp</type>                <interval>5</interval>                <timeout>15</timeout>                <maxRetries>3</maxRetries>                <name>default_tcp_monitor</name>            </monitor>            <monitor>                <monitorId>monitor-2</monitorId>                <type>http</type>                <interval>5</interval>                <timeout>15</timeout>                <maxRetries>3</maxRetries>                <method>GET</method>                <url>/</url>                <name>default_http_monitor</name>            </monitor>            <monitor>                <monitorId>monitor-3</monitorId>                <type>https</type>                <interval>5</interval>                <timeout>15</timeout>                <maxRetries>3</maxRetries>                <method>GET</method>                <url>/</url>                <name>default_https_monitor</name>            </monitor>            <logging>                <enable>false</enable>                <logLevel>info</logLevel>            </logging>        </loadBalancer>        <ipsec>            <version>2</version>            <enabled>false</enabled>            <logging>                <enable>true</enable>                <logLevel>warning</logLevel>            </logging>            <sites/>            <global>                <psk>******</psk>                <caCertificates/>                <crlCertificates/>            </global>        </ipsec>        <bridges>            <version>2</version>            <enabled>false</enabled>        </bridges>        <dhcp>            <version>2</version>            <enabled>false</enabled>            <staticBindings/>            <ipPools/>            <logging>                <enable>false</enable>                <logLevel>info</logLevel>            </logging>        </dhcp>    </features>    <autoConfiguration>        <enabled>true</enabled>        <rulePriority>high</rulePriority>    </autoConfiguration>    <type>gatewayServices</type>    <isUniversal>false</isUniversal>    <hypervisorAssist>false</hypervisorAssist>    <tunnels/></edge>
    
  3. Теперь на основе полученной конфигурации в виде XML создадим новый Edge. Для этого:

    • Удаляем из него данную часть

      <id>edge-8</id><version>8</version><status>deployed</status>
      
    • Меняем <name> </name>, если Edge с таким именем уже есть.

    • Если мы хотим восстановиться в другое место, меняем теги расположения

      <resourcePoolId><resourcePoolName><vmFolderId><vmFolderName>
      

      и другие.

    • С помощью тега <password> </password> вставляем пароль на Edge между тегами <userName> и <sshLoginBannerText>, например:

      <userName>admin</userName><password>Test123!test123!</password><sshLoginBannerText>
      
    • Удаляем из раздела NAT теги ruleId, ruleTag, ruleType, например:

      <ruleId>196609</ruleId><ruleTag>196609</ruleTag><ruleType>user</ruleType>
      
  4. С помощью итоговой XML выполняем команду на создание нового Edge. Для этого вставим в поле Body всю XML, укажем тип raw XML и отправим запрос.

    POST https://nsx-fqdn-1/api/4.0/edges/

Edge восстановлен под именем edge-9.

Теперь отдельно восстановим настройки.

  1. Разберемся с ситуацией, когда нам нужно отдельно добавить правила NAT. Для начала нужно понять, в каком формате Edge формирует эти правила. Можно посмотреть в общем конфиге по тегу <nat>. Но чтобы долго не искать, получим NAT-правила отдельным запросом:

    GET https://nsx-fqdn-1/api/4.0/edges/edge-9/nat/config

  2. Добавим правила NAT с помощью POST-запроса. Для этого сначала удалим теги ruleId, ruleTag, ruleType, в нашем случае:

    <ruleId>196609</ruleId><ruleTag>196609</ruleTag><ruleType>user</ruleType>
    

    И отправляем POST https://nsx-fqdn-1/api/4.0/edges/edge-9/nat/config/rules

    Пример NAT-правила:

    <natRules><natRule><action>dnat</action><vnic>0</vnic><originalAddress>esxternal_ip</originalAddress><translatedAddress>192.168.1.9</translatedAddress><loggingEnabled>false</loggingEnabled><enabled>true</enabled><description></description><protocol>udp</protocol><originalPort>80</originalPort><translatedPort>80</translatedPort></natRule></natRules>
    
  3. Важно учесть, что правила NAT через POST-запрос не перезаписываются, а добавляются заново.

    Вот что будет, если выполнить запрос дважды:

Аналогично можно выполнить операции и с настройками по другим сервисам (firewall, vpn, load balancer и другие). На будущее мы можем сохранить настройки в виде XML и хранить этот файл в качестве резервной копии.

Используем API вместе с vCloud Director. В нашем сценарии мы случайно удалили нужный нам Edge и затем восстановили его из резервной копии через API. Если Edge использовался vCloud Directorом, но был удален через NSX-менеджер, то сам объект edge-8 удалится из vCenter, а ссылка на него останется. После восстановления наш Edge получил новый id, о котором vCenter еще не знает. При попытке обратиться к сервисам через vCloud Director всплывет белый экран. Чтобы исправить это, внесем исправления в БД vCloud Director для изменения id c edge-8 на edge-9.

  1. Отправим запрос для таблицы gateway, чтобы узнать id:

    select * from gateway where name like 'test-delete-edge%'

    Получим:

    -- id=' 877a6842-8a67-4dad-87cf-81e155c45763 ' --name=' test-delete-edge' --backing-ref='edge-8'

  2. Проверяем, в каких таблицах есть упоминание об этом Edge:

    select * from global_search('edge-8')

  3. Проверяем, что выбрали нужный Edge:

    select * from gateway where id = '877a6842-8a67-4dad-87cf-81e155c45763'

  4. Обновляем id Edge и убеждаемся, что он изменен.

    update gateway set backing_ref = 'edge-9' where id = '877a6842-8a67-4dad-87cf-81e155c45763'

  5. Проверим Edge со стороны vCloud Director.

Теперь сервисы отображаются корректно.

Перенесем сервисы из одного Edge в другой

Иногда с Edge требуется работать напрямую через vCloud Director, но и здесь Postman может пригодиться. Рассмотрим сценарий переноса сервисов через API vCloud Director с сохранением адресации:

  1. Открываем Postman.

  2. Настраиваем авторизацию:

    Autorization: Basic Auth - administrator@system

  3. Затем выполняем GET https://vCD-fqdn/api/versions

    Проверяем, отработал ли запрос и смотрим на версии api.

  4. Добавляем заголовок с этой версией:

    Accept application/*+xml;version=35.0

  5. Теперь перейдем на авторизацию по токену. Сначала получим токен с помощью запроса POST https://vCD-fqdn/api/sessions

    Берем отсюда: X-VMWARE-VCLOUD-ACCESS-TOKEN.

  6. Поменяем тип авторизации на Bearer Token и вставим значение X-VMWARE-VCLOUD-ACCESS-TOKEN.

  7. Выполняем запрос GET https://vCD-fqdn/api/admin, чтобы убедиться, что авторизация по токену работает.

  8. Открываем Powershell и выполняем connect-ciserver vCD-fqdn

    Далее: Get-OrgVdc OrgVDCName| Get-EdgeGateway EdgeName

    Копируем ссылку после Href.

    Href: https://vCD-fqdn/api/admin/edgeGateway/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

  9. Возвращаемся в Postman и получаем исходный конфиг с помощью этой ссылки:

    GET https://vCD-fqdn/api/admin/edgeGateway/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

  10. Создаем тело запроса на основе полученной конфигурации. Для этого берем такую шапку:

    <?xml version="1.0" encoding="UTF-8"?><EdgeGatewayServiceConfiguration   xmlns="http://personeltest.ru/away/www.vmware.com/vcloud/v1.5">
    

    и дальше копируем то, что было между тегами <EdgeGatewayServiceConfiguration></EdgeGatewayServiceConfiguration>

    Например:

    <?xml version="1.0" encoding="UTF-8"?><EdgeGatewayServiceConfiguration   xmlns="http://personeltest.ru/away/www.vmware.com/vcloud/v1.5">            <GatewayDhcpService>                <IsEnabled>false</IsEnabled>            </GatewayDhcpService>            <FirewallService>                <IsEnabled>true</IsEnabled>                <DefaultAction>allow</DefaultAction>                <LogDefaultAction>false</LogDefaultAction>            </FirewallService>            <NatService>                <IsEnabled>true</IsEnabled>                <NatRule>                    <RuleType>SNAT</RuleType>                    <IsEnabled>true</IsEnabled>                    <Id>196609</Id>                    <GatewayNatRule>                        <Interface href="http://personeltest.ru/aways/fqdn-vcd/api/admin/network/xxxxxx" name="network" type="application/vnd.vmware.admin.network+xml"/>                        <OriginalIp>10.0.0.0/24</OriginalIp>                        <TranslatedIp>external-ip</TranslatedIp>                    </GatewayNatRule>                </NatRule>            </NatService>            <GatewayIpsecVpnService>                <IsEnabled>false</IsEnabled>            </GatewayIpsecVpnService>            <StaticRoutingService>                <IsEnabled>true</IsEnabled>            </StaticRoutingService>            <LoadBalancerService>                <IsEnabled>false</IsEnabled>            </LoadBalancerService></EdgeGatewayServiceConfiguration>
    

    Если на Edge меняются портгруппы, необходимо в теге <Interface/> заменить сети из старого Edge на сети из нового Edge, куда переносим сервисы:

    <Interface href="http://personeltest.ru/aways/fqdn-vcd/api/admin/network/xxxxxx" name="network" type="application/vnd.vmware.admin.network+xml"/>
    
  11. Добавим новую конфигурацию POST-запросом. Для этого вставим полученную XML в Body в формате raw для пустого Edge. Добавляем заголовок content-type application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml

    В сам запрос вставляем ссылку на нужный Edge, добавив к url /action/configureServices, например:

    POST https://vCD-fqdn/api/admin/edgeGateway/XXXX/action/configureServices

Конфигурация перенесена.

Этим же способом можно автоматизировать процесс сбора конфигурации. Так можно собирать XML для каждого Edge с помощью скриптов, опрашивающих api. Еще одна отдельная тема открытие доступа к БД vCloud Director, а также некоторые операции с БД. Если интересно узнать об этом подробнее, пишите, расскажу отдельно.

Подробнее..

Перевод Как спокойно спать, когда у вас облачный сервис основные архитектурные советы

19.08.2020 18:13:30 | Автор: admin
LOST by sophiagworld

В этой статей собраны некоторые общие шаблоны, помогающие инженерам работать с масштабными сервисами, к которым делают запросы миллионы пользователей.

По опыту автора, это не исчерпывающий список, но действительно эффективные советы. Итак, начнем.

Переведено при поддержке Mail.ru Cloud Solutions.

Начальный уровень


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

Инфраструктура как код


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

Развертывание 100 виртуальных машин

  • с Ubuntu
  • 2 ГБ RAM на каждой
  • у них будет следующий код
  • с такими параметрами

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

Модернист во мне говорит, что можно использовать Kubernetes/Docker, чтобы сделать всё выше перечисленное, и он прав.

Кроме того, обеспечить автоматизацию, можно с помощью Chef, Puppet или Terraform.

Непрерывная интеграция и доставка


Для создания масштабируемого сервиса важно наличие конвейера сборки и теста для каждого пул-реквеста. Даже если тест самый простой, он, по крайней мере, гарантирует, что код, который вы деплоите, компилируется.

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


Нет ничего прекраснее, чем видеть эти галочки

Для этой технологии можете оценить Github, CircleCI или Jenkins.

Балансировщики нагрузки


Итак, мы хотим запустить балансировщик нагрузки, чтобы перенаправлять трафик, и обеспечить равную нагрузку на всех узлах или работу сервиса в случае сбоя:


Балансировщик нагрузки, как правило, хорошо помогает распределять трафик. Наилучшей практикой является избыточная балансировка, чтобы у вас не было единой точки отказа.

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

RayID, сorrelation ID или UUID для запросов


Вам когда-нибудь встречалась ошибка в приложении с сообщением вроде такого: Что-то пошло не так. Сохраните этот id и отправьте его в нашу службу поддержки?


Уникальный идентификатор, correlation ID, RayID или любой из вариантов это уникальный идентификатор, который позволяет отслеживать запрос в течение его жизненного цикла. Это позволяет отследить весь путь запроса в логах.


Пользователь делает запрос к системе A, затем А связывается с B, та связывается с C, сохраняет в X и затем запрос возвращается в A

Если бы вы удаленно подключились к виртуальным машинам и попытались проследить путь запроса (и вручную соотнести, какие происходят вызовы), то сошли бы с ума. Наличие уникального идентификатора значительно облегчает жизнь. Это одна из самых простых вещей, которую можно сделать, чтобы сэкономить время по мере роста сервиса.

Средний уровень


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

Централизованное ведение журналов


Поздравляю! Вы развернули 100 виртуальных машин. На следующий день генеральный директор приходит и жалуется на ошибку, которую получил во время тестирования сервиса. Он сообщает соответствующий идентификатор, о котором мы говорили выше, но вам придется просматривать журналы 100 машин, чтобы найти ту, которая вызвала сбой. И ее нужно найти до завтрашней презентации.

Хотя это звучит как забавное приключение, однако, лучше убедиться, что у вас есть возможность поиска по всем журналам из одного места. Я решил задачу централизации журналов с помощью встроенной функциональности стека ELK: здесь поддерживается сбор журналов с возможностью поиска. Это действительно поможет решить проблему с поиском конкретного журнала. В качестве бонуса вы можете создавать диаграммы и тому подобные забавные штуки.


Функциональность стека ELK

Агенты мониторинга


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

На этом этапе вы проверяете, что запущенная сборка хорошо себя чувствует и нормально работает.

Для небольших и средних проектов я рекомендую Postman для мониторинга и документирования API. Но в целом просто следует убедиться, что у вас есть способ узнать, когда произошел сбой, и получить своевременное оповещение.

Автомасштабирование в зависимости от нагрузки


Это очень просто. Если у вас есть виртуальная машина, обслуживающая запросы, и она приближается к тому, что 80% памяти занято, то можно либо увеличить ее ресурсы, либо добавить в кластер больше виртуальных машин. Автоматическое выполнение этих операций отлично подходит для эластичного изменения мощности под нагрузкой. Но вы всегда должны быть осторожны в том, сколько денег тратите, и установить разумные лимиты.


В большинстве облачных служб вы можете настроить автоматическое масштабирование, используя большее количество серверов или более мощные серверы.

Система экспериментов


Хорошим способом безопасно развернуть обновления станет возможность протестировать что-то для 1% пользователей в течение часа. Вы, конечно, видели такие механизмы в действии. Например, Facebook показывает части аудитории другой цвет или меняет размер шрифта, чтобы посмотреть, как пользователи воспринимают изменения. Это называют A/B-тестированием.

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

Продвинутый уровень


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

Сине-зеленые развертывания


Это то, что я называю эрланговским способом развертывания. Erlang стали широко использовать, когда появились телефонные компании. Для маршрутизации телефонных звонков стали применять программные коммутаторы. Основная задача программного обеспечения этих коммутаторов заключалась в том, чтобы не сбрасывать вызовы во время обновления системы. У Erlang есть прекрасный способ загрузки нового модуля без падения предыдущего.

Этот шаг зависит от наличия балансировщика нагрузки. Представим, что у вас версия N вашего программного обеспечения, а затем вы хотите развернуть версию N+1.

Вы могли бы просто остановить службу и развернуть следующую версию в то время, которое считаете удобным для ваших пользователей, и получить некоторое время простоя. Но предположим, что у вас действительно строгие условия SLA. Так, SLA 99,99% означает, что вы можете уходить в офлайн только на 52 минуты в год.

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

  • тот, который есть прямо сейчас (N);
  • следующая версия (N+1).

Вы указываете балансировщику нагрузки перенаправить процент трафика на новую версию (N+1), в то время как сами активно отслеживаете регрессии.


Здесь у нас есть зеленый деплой N, который нормально работает. Мы пытаемся перейти к следующей версии этого деплоя

Сначала мы посылаем действительно небольшой тест, чтобы посмотреть, работает ли наш деплой N+1 с небольшим количеством трафика:


Наконец, у нас есть набор автоматических проверок, которые мы в конечном итоге запускаем до тех пор, пока наше развертывание не будет завершено. Если вы очень-очень осторожны, также можете сохранить свое развертывание N навсегда для быстрого отката в случае плохой регрессии:


Если хотите перейти на еще более продвинутый уровень, пусть всё в сине-зеленом деплое выполняется автоматически.

Обнаружение аномалий и автоматическое смягчение последствий


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


С обнаружением аномалий вы начинаете изучать некоторые подсказки, которые выдает сервис. Например, всплеск нагрузки на CPU может подсказать, что жесткий диск выходит из строя, а всплеск количества запросов означает, что нужно масштабироваться. Такого рода статистические данные позволяют сделать сервис проактивным.

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

Вот и всё!


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

Автор оригинальной статьи приглашает читателей оставлять свои комментарии и вносить изменения. Статья распространяется как open source, пул-реквесты автор принимает на Github.

Что еще почитать по теме:

  1. Go и кэши CPU.
  2. Kubernetes в духе пиратства с шаблоном по внедрению.
  3. Наш канал Вокруг Kubernetes в Телеграме.
Подробнее..

Перевод Автоматизированное тестирование баз данных в Java с помощью JdbcTemplate

15.03.2021 14:07:31 | Автор: admin

В преддверии старта курса "Java QA Automation Engineer" подготовили перевод полезного материала.

Также приглашаем поучаствовать в открытом вебинаре на тему HTTP. Postman, Newman, Fiddler (Charles), curl, SOAP. SoapUI. На этом занятии участники вместе с экспертом разберут, какие бываю API и каким способом можно проверить, что backend отдает ожидаемые данные, а также познакомятся с основными инструментами для тестирования.


Бывает, и достаточно часто, что во время автоматизированного тестирования наши тесты должны взаимодействовать с базами данных. Иногда нам нужно установить какие-либо тестовые данные. В других случаях нам нужно совершать запросы в базу данных, чтобы получить те самые тестовые данные. И давайте не будем забывать об очистке данных, которые мы использовали и которые больше нам не нужны. В этой статье я покажу, как вы можете использовать класс Spring JdbcTemplate для упрощения работы с базой данных MySQL из ваших автоматизированных тестов на Java.

Вам также следует взглянуть на это замечательное дополнение к базе данных MySQL, созданное сообществом для библиотеки дополнений TestProject, позволяющее расширить возможности тестирования с помощью предварительно созданных автоматизированных экшенов, которые вы можете мгновенно внедрить в ваши написанные и закодированные тесты.

Требования

Прежде чем мы сможем начать наши взаимодействия с базой данных, нам нужно кое-что настроить. А именно зависимости, которые нам нужно добавить в наш проект. В моем случае, поскольку я использую Maven, зависимости, которые мне нужно добавить в файл pom.xml, будут выглядеть следующим образом:

<dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>8.0.23</version></dependency>

Первая зависимость, которую мы здесь видим, это зависимость из пакета Spring. Здесь мы можем найти класс JdbcTemplate, который мы будем использовать для коммуникации с базой данных. Этот класс содержит полезные методы для обновления или получения данных из базы данных. Вторая зависимость требуется для связи с инстансом MySQL.

Примечание: эти зависимости имеют последнюю доступную на момент написания статьи версию (какую вы можете увидеть в репозитории Maven). Версия mysql-connector-java должна быть синхронизирована с версией инстанса MySQL, на котором работает ваша база данных. В моем случае, мой сервер MySQL имеет версию > 8, поэтому версия моего mysql-connector-java также выше чем 8.

Подключение к базе данных

После того как мы разобрались с зависимостями, мы можем установить связь с нашей базой данных. Мы могли бы написать код, необходимый для этой операции, в нашем тесте. Однако нам обязательно понадобится этот код и в других тестовых классах. Следовательно, этот код может быть написан либо в специальном классе для работы с базой данных, либо в базовом классе, расширяемом вашими тестами. Независимо от того, какой вариант вы выберете, соединение может быть установлено с помощью подобного метода:

public DataSource mysqlDataSource() {    DriverManagerDataSource dataSource = new DriverManagerDataSource();    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");    dataSource.setUrl("jdbc:mysql://dbURL:portNumber/nameOfDB?useSSL=false");    dataSource.setUsername("username");    dataSource.setPassword("password");    return dataSource;}

Первое, на что нам нужно обратить внимание, это то, что этот метод возвращает DataSource. Это необходимо для инициализации класса JdbcTemplate, который мы будем использовать в наших тестах, поскольку он хранит соединение с базой данных.

Затем в качестве имени класса драйвера в этом примере я использовал значение com.mysql.cj.jdbc.Driver. Опять же это требуется для установки соединения, и в некоторых случаях в более старых версиях зависимостей коннектора MySQL вместо него следует использовать com.mysql.jdbc.Driver. Если вы используете неправильное имя, вы получите соответствующее предупреждение при попытке подключения к базе данных.

Вам нужно будет указать расположение базы данных в методе setUrl. Он состоит из URL-адреса, порта и имени базы данных. И, конечно же, вам необходимо указать имя пользователя и пароль для подключения к базе данных, с помощью методов setUsername и setPassword.

Теперь, когда соединение установлено, нам нужно инициализировать класс JdbcTemplate. Мы можем объявить переменную этого типа в нашем тестовом классе:

private JdbcTemplate jdbcTemplate;

Затем в методе @BeforeAll мы можем инициализировать эту переменную, предоставив соединение, которое мы установили с базой данных:

jdbcTemplate = new JdbcTemplate(nameOfClass.mysqlDataSource());

На этом настройка завершена, соединение установлено, и мы можем начать обновление (updating) или запрашивание (querying) базы данных.

Update

В классе JdbcTemplate мы можем найти много полезных методов. Один из них, update, может быть использован для создания и обновления таблиц, добавления в них данных или даже удаления данных. Существует несколько вариантов этого метода (с разными сигнатурами), но тот, который я приведу здесь в качестве примера, принимает один параметр: SQL-запрос в виде String.

Пример

Создадим две новые таблицы: одну с именем meal (блюдо) и ingredient (ингредиент). В таблице meal мы хотим хранить название блюда, присвоенную ему категорию (представляющую, будь то завтрак, обед или ужин) и автоматически сгенерированный id в качестве первичного ключа (primary key). Для создания таблицы напишем в тестовом методе следующий код:

jdbcTemplate.update("create table meal(\n" +                 " meal_id bigint auto_increment primary key,\n" +                 " name varchar(50) not null unique,\n" +                 " category varchar(50) not null\n" + ");");

Когда мы запустим тест, таблица будет создана. Для создания таблицы больше ничего не требуется. Допустим, мы также хотим добавить к этому столу два блюда: фахитас из курицы и энчилада. Сделать это легко просто передадим требуемый SQL-запрос в метод update следующим образом:

jdbcTemplate.update("insert into meal (name, category) values ('Chicken Fajita', 'lunch');");jdbcTemplate.update("insert into meal (name, category) values ('Enchilada', 'lunch');");

Как видите, у нас по одному вызову метода update на одну SQL-операцию.

Теперь давайте создадим таблицу под названием ingredient. У нее не будет автоматически сгенерированного первичного ключа. Однако у нее будет внешний ключ (foreign key), соответствующий значению meal_id из таблицы meal. Каждая запись в этой таблице представляет собой ингредиент, соответствующий блюду из таблицы meal. Этот внешний ключ свяжет ингредиент с блюдом. Кроме того, в таблице ingredient есть столбцы для хранения названия ингредиента (name), количества (quantity) и единицы измерения (uom - unit of measure) для количества ингредиента.

Для того чтобы создать эту таблицу, а затем добавить к ней внешний ключ, мы снова будем использовать метод update, которому мы передадим соответствующий SQL-запрос:

jdbcTemplate.update("create table ingredient(\n" +         " meal_id bigint not null,\n" +         " name varchar(50) not null,\n" +         " quantity bigint not null,\n" +         " uom varchar(50) not null\n" + ");");jdbcTemplate.update("alter table ingredient add foreign key (meal_id)" +         " references meal(meal_id);\n");

Чтобы иметь больше данных для наших следующих примеров, я также добавлю некоторые данные в таблицу ingredient:

jdbcTemplate.update("insert into ingredient (meal_id, name, quantity,"                + " uom) values ((select meal_id from meal where name = 'Chicken Fajita'), 'chicken', 1, 'kg');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity, uom) " +                "values ((select meal_id from meal where name = 'Chicken Fajita'), 'red pepper', 1, 'piece');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity, uom) " +                "values ((select meal_id from meal where name = 'Chicken Fajita'), 'green pepper', 1, 'piece');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity, uom) " +                "values ((select meal_id from meal where name = 'Chicken Fajita'), 'yellow pepper', 1, 'piece');");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity," + " uom) " +                "values ((select meal_id from meal where name = " + "'Enchilada'), 'chicken', 1, 'kg');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity," + " uom) " +                "values ((select meal_id from meal where name = " + "'Enchilada'), 'cheese', 100, 'grams');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity," + " uom) " +                "values ((select meal_id from meal where name = " + "'Enchilada'), 'tomato', 1, 'piece');\n");

Отлично, у нас есть 2 таблицы с данными, которые мы можем запрашивать. Теперь же мы будем использовать разные методы из класса JdbcTemplate для получения результатов разных типов.

queryForObject получить одно значение

Если нам нужно запросить из базы данных одно значение, мы можем использовать метод queryForObject. У этого метода также есть несколько вариантов использования, но здесь мы рассмотрим наиболее простой:

jdbcTemplate.queryForObject(String sqlStatement, Class returnType);

При вызове этого метода нам нужно указать, какой тип возвращаемого значения должен иметь запрос (Class). Мы могли бы, например, получить значение String (указав String.class) или целое число (указав Integer.class).

Пример

Нам нужно запросить базу данных, чтобы получить значение meal_id из таблицы meal для блюда Chicken Fajita. Нам нужно сохранить этот результат в переменной с типом int:

int id = jdbcTemplate.queryForObject("select meal_id from meal where name='Chicken Fajita';", Integer.class);

Здесь вы можете видеть, что тип возвращаемого значения запроса указан как Integer.class, поэтому результат сохраняется в переменной с типом int. Допустим, в тесте мы также хотим вывести в консоль результат этого запроса:

System.out.println("Meal id for Chicken Fajita = " + id);

Результатом этого вывода будет:

Meal id for Chicken Fajita = 1

queryForMap получить строку

Теперь предположим, что вы хотите получить целую строку из таблицы. Или части строки. Вы можете сделать это с помощью метода queryForMap, которому вы передаете необходимый SQL-запрос:

jdbcTemplate.queryForMap(String sqlStatement);

Результат этого запроса можно сохранить в переменной типа Map. Ключи map будут соответствовать имени каждого столбца, которому принадлежит элемент строки. Значение будет соответствовать фактическому значению из строки, соответствующей этому столбцу.

Пример

Мы хотим извлечь все данные о блюде с id 1 из таблицы meal, сохранить их в переменной и вывести результат в консоль. Это легко можно сделать следующим образом:

Map<String, Object> entireRowAsMap = jdbcTemplate.queryForMap("select * from meal where meal_id = 1");System.out.println("All details of meal with id 1 = " + entireRowAsMap);

Как видите, переменная entireRowAsMap представляет Map, ключи которой String, а значения Object. Это происходит потому, что некоторые значения являются целыми числами, некоторые строками и, конечно же, все эти типы являются объектами в Java. Вывод в консоль для приведенного выше кода:

All details of meal with id 1 = {meal_id=1, name=Chicken Fajita, category=lunch}

queryForList получить столбец

Когда вам нужно получить либо все значения, либо часть значений из конкретного столбца, вы можете использовать метод queryForList. В этом варианте использования я покажу на примере, что для результирующих элементов требуется SQL-запрос и тип возвращаемого значения. Речь идет о типе элементов, которые вы будете сохранять в список (List) Java. Например, если все элементы, которые вы извлекаете с помощью этого запроса, являются целыми числами, типом возврата будет Integer.class. Основной пример использования метода выглядит так:

jdbcTemplate.queryForList(String sqlStatement, Class returnType);

Пример

Мы хотим сохранить в список Java все названия ингредиентов, которые есть в таблице ingredient. Мы также хотим вывести эти значения в консоль. Этого можно добиться следующим образом:

List<String> queryForColumn = jdbcTemplate.queryForList("select " +         "distinct name from ingredient", String.class);System.out.println("All available ingredients = " + queryForColumn);

Поскольку все названия ингредиентов имеют тип String, тип возвращаемого значения для метода queryForList String.class. Вот что будет выведено на консоль:

All available ingredients = [chicken, red pepper, green pepper, yellow pepper, cheese, tomato]

queryForList получение списка строк

Другой вариант использования метода queryForList получение сразу нескольких строк. В этом случае единственный параметр, требуемый при вызове этого метода, это SQL-запрос, который собирает данные. Типом возврата будет список элементов типа map, где каждая map будет иметь ключ с типом String и соответствующее значение с типом Object. Этот метод выглядит так:

jdbcTemplate.queryForList(String sqlStatement);

Пример

Выберите все значения из таблицы meal, сохраните и выведите их в консоль.

List<Map<String, Object>> severalRowsAsListOfMaps = jdbcTemplate.queryForList("select * from meal;"); System.out.println("All available meals = " + severalRowsAsListOfMaps);

Вывод здесь представляет собой список элементов типа map:

All available meals = [{meal_id=1, name=Chicken Fajita, category=lunch}, {meal_id=2, name=Enchilada, category=lunch}]

Передача параметров запросам

В некоторых случаях SQL-запросы нуждаются в передаче параметра для замены захардкоженного значения из запроса. Например, вы можете захотеть выполнить тот же запрос для поиска строки в базе данных на основе ее id. Но вам может потребоваться передать id в тест через DataProvider. Следовательно, при каждом запуске метода для выполнения запроса у вас будет другое значение id.

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

Пример

В тестовом методе нам нужно выяснить, сколько строк существует в таблице ingredient с именем, которое предоставляется в виде параметра. Результат этого запроса будет сохранен в переменной типа int и будет выведен в консоль. Этого можно добиться следующим образом:

Integer howManyUsages = jdbcTemplate.queryForObject("select count(*) "                 + "from ingredient where name=?", Integer.class, ingredientToLookFor);        System.out.println("How many time does the ingredient passed as "                 + "parameter appear in the DB " + " = " + howManyUsages);

Второй параметр, переданный методу queryForObject, это тип возвращаемого значения для запроса, а третий параметр это имя параметра, который будет отправлен в запрос из DataProvider. Например, если значение параметра ingredientToLookFor будет chicken, вывод в консоль будет следующим:

How many time does the ingredient passed as parameter appear in the DB = 2

Извлечение данных в объект Java

Помните мою статью об использовании объектов Java для моделирования данных, извлеченных из БД? Вы можете легко использовать JdbcTemplate для запроса базы данных и извлечения результата непосредственно в объект (Object). Все, что вам нужно для выполнения этой задачи, это объект Java для моделирования данных; класс преобразователя строк (row mapper), который сопоставляет столбец из базы данных со свойствами объекта; запрос, который извлекает данные в объект с помощью преобразователя строк.

Пример

Допустим, нам нужно смоделировать данные, соответствующие ингредиенту, название которого содержит текст yellow, в объект ингредиента (Ingredient Object). Это означает, что мы хотим, чтобы объект имел те же свойства, что и ингредиент в таблице. Мы хотим сопоставить каждый столбец со свойством. Поэтому мы создадим объект Java под названием Ingredient. Его свойства будут следующими:

public int meal_id; public String name; public int quantity; public String uom;

Рекомендуется синхронизировать имена свойств с именами столбцов базы данных. Таким образом, вы можете легко идентифицировать каждое свойство. Поскольку это объект, нам потребуется создать методы equals, hashCode и toString. Пока я пропущу эту часть.

Вместо этого я покажу кое-что еще, что вам понадобиться, а именно сеттеры для каждого свойства. Вы можете легко автоматически сгенерировать их в IntelliJ, используя в редакторе сочетание клавиш Alt+Insert. Они будут выглядеть следующим образом:

public void setMeal_id ( int meal_id){        this.meal_id = meal_id;}        public void setName (String name){          this.name = name;}        public void setQuantity ( int quantity){            this.quantity = quantity;        }        public void setUom (String uom){            this.uom = uom;        }

Вы будете использовать их для отображения данных БД в свойства объекта. И это произойдет внутри класса преобразователя строк, который мы создадим следующим. Тело этого класса выглядит следующим образом:

public class IngredientRowMapper implements RowMapper<Ingredient> {@Override        public Ingredient mapRow(ResultSet rs, int rowNum) throws SQLException {          Ingredient ingredient = new Ingredient();          ingredient.setMeal_id(rs.getInt("meal_id"));          ingredient.setName(rs.getString("name"));          ingredient.setQuantity(rs.getInt("quantity"));          ingredient.setUom(rs.getString("uom"));          return ingredient;        }}

Как видите, этот класс должен реализовать интерфейс под названием RowMapper. Из-за этого нам потребуется реализовать метод mapRow. И внутри этого метода вы будете сопоставлять каждое свойство объекта со столбцом базы данных, используя сеттеры. Так, например, для свойства quantity метод setQuantity установит значение, извлеченное из строки, имя соответствующего столбца которой тоже quantity.

Создав класс IntegerRowMapper, мы можем выполнить поставленную задачу, используя queryForObject для извлечения данных, соответствующих желтому (yellow) ингредиенту:

Ingredient ingredient = jdbcTemplate.queryForObject("select * from "         + "ingredient where name like '%yellow%'", new IngredientRowMapper());System.out.println("The ingredient object = " + ingredient);

Результат этого запроса будет отображаться как объект с соответствующими свойствами:

The ingredient object = Ingredient{meal_id=1, name='yellow pepper', quantity=1, uom='piece'}

Заключение

Мы рассмотрели несколько способов работы с базами данных из наших автоматизированных тестов: нужно ли нам обновлять их или просто извлекать данные в классе JdbcTemplate есть множество методов, которые могут нам помочь с этим. Вы можете выбрать, какой из них использовать, в зависимости от того, что должен возвращать ваш SQL-запрос.


Узнать подробнее о курсе "Java QA Automation Engineer".

Смотреть открытый вебинар на тему HTTP. Postman, Newman, Fiddler (Charles), curl, SOAP. SoapUI.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru