Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Эта статья посвящена автоматизации системного (end-to-end) тестирования с использованием виртуальных машин. В статье рассматриваются такие вопросы, как автоматизация развертывания и настройки виртуальных стендов, а также автоматизация запуска процессов внутри виртуальных машин с последующим контролем полученных результатов. В конце статьи мы получим пусть неидеальный (к этому мы ещё вернемся), но простой и понятный скрипт, с помощью которого вы сможете запускать системные тесты одной кнопкой, даже не имея у себя на компьютере ни одной виртуальной машины.
Статья предполагает наличие следующих навыков у читателя:
- Уверенное пользование ОС семейства Linux;
- Базовое понимание принципов виртуализации;
- Знакомство с гипервизором QEMU и графическим клиентом virt-manager
Статья разбита на две части: в первой части мы познакомимся с основными инструментами, которые позволят нам создавать, развертывать и управлять виртуальными машинами используя исключительно командную строку. Эти знания нам пригодятся для второй части статьи, где мы соединим эти инструменты вместе и попробуем автоматизировать тесты конкретного сетевого приложения.
Эта статья носит исключительно ознакомительный характер. Выбор инструментария обусловлен исключительно простотой ознакомления и общей доступностью. Автор осведомлен о наличии большого количества коммерческих и открытых решений по автоматизации тестирования (в т.ч. системного). Эту статью стоит расценивать как небольшое введение в курс системных тестов, где Вы сможете познакомиться с основными принципами системных тестов, а также увидите пример того, как их можно автоматизировать даже с использованием простых, общеизвестных и доступных решений.
Эта статья НЕ ставит себе целью рассказать Вам о продакшн-системах прогона системных тестов, НЕ ставит целью никоим образом преуменьшить значимость существующих решений, и НЕ ставит целью разжечь какой-либо холивар.
Что такое системное тестирование
Системное тестирование (или, как его ещё иногда называют, end-to-end тестирование) — это тестирование программы (или даже целой системы) с учётом окружения, в которой программе придётся работать. Помимо системного тестирования также существует unit-тестирование (тестирование конкретных функций и модулей), интеграционное тестирование (тестирование больших самостоятельных компонент программы или программы целиком) и ещё много других видов тестирования. Но почему же нас может заинтересовать именно системное?
Наверное, у всех бывали ситуации, когда у разработчика всё работает, а у заказчика вечно что-то ломается. Так вот, системное тестирование хорошо тем, что программа проверяется именно в том виде, в котором её увидит конечный потребитель. Системные тесты позволяют воспроизвести некоторое окружение и проверить, как программа с этим окружением справляется. Вот некоторые примеры настраиваемого окружения:
- Вид и версия ОС;
- Разные настройки ОС (например, в области разграничения прав доступа);
- Наличие или отсутствие определенных программ в ОС (в т.ч. драйверов);
- Наличие сети и других компьютеров в ней;
- Количество оперативной памяти или определённая архитектура процессора.
Довольно часто разработчики и тестировщики просто не знают, как подступиться к задаче автоматизации системных тестов, либо им кажется, что затраты на автоматизацию не окупят себя. Этой статьёй я хотел бы продемонстрировать, что автоматизация системных тестов не настолько сложна, как это может показаться, и я надеюсь, что она послужит для кого-то хорошей отправной точкой.
Почему именно виртуалки?
Может возникнуть вопрос, а зачем использовать именно виртуалки? Ведь для воспроизведения окружения для программы можно использовать и контейнеры (например, если Вас интересует поведение программы в связке с другими программами). И действительно, для контейнеров существует несколько решений по автоматизации тестов. Однако, существуют классы сценариев, которые нельзя протестировать с помощью контейнеров:
- Нельзя протестировать приложения не под Linux, а также гетерогенные стенды из нескольких машин;
- Нельзя протестировать GUI (или даже псевдо-GUI);
- Нельзя протестировать драйвера и работу с оборудованием.
Автоматизация пунктов 1 и 2 потребует от нас существенной подготовки, поэтому оставим их для одной из следующих статей. А вот для пункта 3 вполне хватит той информации, которую Вы узнаете из этой статьи. В этой статье (во второй её части) мы как раз продемонстрируем автоматизацию тестирования приложения, которое взаимодействует с оборудованием напрямую, в обход операционной системы.
Какой выбрать гипервизор?
В целом, схема, которую мы предложим в этой статье, применима к любому гипервизору. Но мы воспользуемся QEMU, потому что он позволит нам получить работающий прототип, затратив при этом минимальные усилия и время. Основные принципы и идеи из этой статьи, при желании, можно реализовать и на других гипервизорах (например, на VirtualBox).
Какие действия мы хотим автоматизировать?
Для того, чтобы автоматизировать системные тесты, нам нужно решить как минимум следующие вопросы:
- Как автоматически создать виртуалку;
- Как "раскатать" и настроить ОС;
- Как научиться управлять виртуалкой после её установки. В частности, нас интересует, как запускать процессы внутри виртуалки и копировать на неё файлы.
По-хорошему, настоящая автоматизация системных тестов предполагает, что все эти действия мы будем выполнять так же, как и тестировщик, выполняющий ручное тестирование: нам нужно нажимать клавиши на виртуальной клавиатуре, двигать виртуальной мышью и анализировать происходящее на виртуальном экране. Такой подход позволяет автоматизировать любые тестовые сценарии, поскольку не делает никаких предположений о том, как устроена внутри тестируемая система. Однако, это очень сложный путь автоматизации. Мы, пока что, воспользуемся менее универсальным, но гораздо более простым способом.
Что ж, погнали!
Создаем виртуалку
Конечно, я думаю все пробовали создавать виртуальные машины вручную. Для этого в графическом интерфейсе рисуются всякие диалоговые окна, где надо выбрать конфигурацию, диски, сетевые адаптеры и прочее прочее. Так вот, для гипервизора QEMU всё это можно сделать и в консольном режиме с помощью утилиты virt-install
. Давайте посмотрим как будет выглядеть простейшее создание виртуальной машины с помощью этой утилиты:
virt-install \
--name my_super_vm \
--ram 1024 \
--disk my_super_vm.qcow2,size=8 \
--cdrom /path/to/ubuntu_server.iso
Вот такой простой командой мы можем создать виртуальную машину с именем my_super_vm
, 1024 Мегабайтами оперативной памяти, новым диском my_super_vm.qcow2
размером 8 Гигабайт. В виртуальном CD-приводе такой машины будет смонтирован установочный образ ubuntu_server.iso
(конечно, этот образ надо предварительно скачать), который, как обычно, нужен для установки ОС на свежесозданную виртуалку.
Впрочем, если Вы выполните такую команду, то увидите как у вас запустилось графическое окно с VNC-клиентом, который подключается к консоли виртуальной машины. В этом окне Вы увидите начало установки Ubuntu Server 18.04. Конечно, реальный человек бы смог протыкать несколько клавиш на клавиатуре и установить Ubuntu Server, но мы лишены такой роскоши, ведь мы работаем исключительно консольными командами.
Но наше положение не такое уж и безвыходное, ведь установить ОС на виртуальную машину можно и по-другому.
Создаём диск из шаблона
Тут следует сделать небольшую ремарку и обозначить (на всякий случай) разницу между виртуальной машиной и виртуальным диском. Виртуальная машина (как и настоящая физическая) может иметь один диск, или несколько дисков, или вообще не иметь дисков. Виртуальные диски можно подключать и отключать от виртуальных машин. Это совершенно разные, независимые сущности. Поэтому, например, в гипервизоре VirtualBox за создание виртуального диска отвечает отдельный мастер.
Вызов команды virt-install
, приведённый выше, выполняет сразу два действия: он создаёт одновременно и машину и диск. Из-за может сложиться неверное впечатление, что эти сущности неотделимы друг от друга.
Но на самом деле никто не мешает нам отдельно создать виртуальный диск, а затем подключить его к виртуальной машине. Вы можете подготовить свой образ диска, но в этой статье мы для простоты воспользуемся уже готовым. Проект libguestfs поддерживает в свободном доступе целый репозиторий таких образов. Кроме того, в рамках этого же проекта существует утилита virt-builder, которая позволяет легко скачать нужный образ диска и доработать его "напильником" под наши нужды.
В первую очередь Libguestfs — это программная библиотека, которая позволяет легко манипулировать содержимым образом виртуальных дисков. Под манипуляцией подразумеваются такие операции как форматирование дисков, копирование файлов на диск и из него и так далее. Кроме того, в рамках проекта реализован целый набор утилит на все случаи жизни, которые используют различные возможности библиотеки. Среди них есть очень простые утилиты (в стиле Unix-way), которые выполняют всего одно действие, например virt-copy-in. С другой стороны, есть команды-комбайны, которые умеют много всего и сразу, такие как virt-builder.
Итак, что же нам позволяет сделать virt-builder
? С помощью этой утилиты мы можем на лету сформировать уже "подготовленный" диск с уже установленной Ubuntu Server. Сделать это можно так:
virt-builder ubuntu-18.04 \
--format qcow2 \
--output my_super_disk.qcow2
Что же здесь происходит? Мы говорим, что хотели бы создать диск в формате qcow2
(можно выбрать другой) и использовать в качестве основы шаблон ubuntu-18.04, который хранится в репозитории шаблонов в проекте libguestfs
. Команда virt-builder
скачает этот шаблон из Интернета и затем на его основе сгенерирует итоговый диск, в котором теперь будет установлена Ubuntu Server!
Приятной особенностью утилиты virt-builder
является то, что шаблон диска кешируется после первого скачивания, поэтому последующие создания дисков будут проходить гораздо быстрее
Ну а теперь нам остаётся создать виртуальную машину, и указать в качестве диска (импортировать) подготовленный образ my_super_disk.qcow2
:
virt-install \
--import \
--name my_super_vm \
--ram 1024 \
--disk my_super_vm.qcow2
Обратите внимание, что исчез параметр --cdrom
, он нам больше не нужен. Также добавился параметр --import
. Этот параметр указывает, что виртуалка будет загружаться не с cdrom, а с виртуального диска (то есть это влияет на Bios Boot Options виртуальной машины). Ну а т.к. диск у нас теперь содержит установленную Ubuntu Server, такая загрузка пройдёт успешно.
Попробуйте выполнить эти команды и создать таким образом виртуальную машину my_super_vm
. Вы сможете убедиться, что Ubuntu Server 18.04 действительно уже установлена и успешно загружается.
Соединяем сетью виртуалку и хост
Вот так вот мы практически незаметно научились автоматически устанавливать виртуалки и даже раскатывать на них готовую ОС. Теперь настало время поговорить о канале управления виртуалкой после её установки и настройки.
Ещё раз хотелось бы отметить, что эталоном автоматизации системных тестов должна выступать автоматизация действий человека: как он нажимает на клавиши, кликает мышкой и совершает другие действия. И ещё раз напомним, что это очень сложный (хоть и правильный) путь. Мы же воспользуемся тем фактом, что нам не требуется взаимодействовать с GUI, нам пока вполне хватит возможности выполнения произвольных bash-команд на гостевой системе.
Для этого существуют разные подходы, но мы с Вами в этой статье рассмотрим только самый простой и, в некоторой степени, топорный вариант: установка SSH-соединения между хостом и виртуалкой.
Не всегда канал управления виртуалкой через ssh является приемлемым вариантом. Такое бывает, если Вы тестируете приложение, которое каким-то образом завязано на сетевую подсистему. Например, в случае если вы как раз и разрабатываете ssh-сервер. Вы же не хотите, чтобы Ваши тесты полагались на работоспособность ещё неготовой программы? Либо Вы хотите проверить, как поведёт себя Ваша программа на виртуалке, где вообще нет сетевых интерфейсов.
В качестве альтернативы можно управлять гостевой системой через последовательный порт. При этом последовательный порт образует как бы трубу (pipe), проложенную между хостом и виртуалкой. Со стороны Linux-хоста эта труба видна как unix-socket, а со стороны гостевой системы — как последовательное устройство. Всё, что отправляется в один конец трубы, тут же появляется из другого. Соответственно, как и в случае с ssh, на гостевой системе должен работать некий сервер, ожидающий получения данных из последовательного порта. Примером такого сервера может служить qemu-guest-agent.
В случае, если Вы работаете с гипервизором Hyper-V, то помимо последовательного порта можно попробовать воспользоваться механизмом KVP (Key-Value Pairs) или Hyper-V Sockets.
Для этого нам потребуется сделать несколько манипуляций:
- Создать виртуальную сеть между хостом и виртуалкой;
- Настроить сетевой интерфейс на виртуалке;
- Задать пароль от пользователя
root
на виртуалке; - Подредактировать SSH-настройке на виртуалке, чтобы можно было соединяться по SSH от имени рута с помощью пароля.
Лучше, конечно, было бы настроить вход по ключу. Это позволило бы немного упростить финальный скрипт. Единственная причина, почему я не стал так делать — это скользкий момент, связанный с sudo. Но вообще, принципиальных препятствий никаких нет.
Для начала займемся созданием виртальной сети между хостом и виртуалкой. Для этого нам надо сделать две простых операции:
- Создать виртуальную сеть;
- Подключить виртуалку к этой сети (хост уже будет подключен к сети по-умолчанию).
Итак, поехали.
Для создания виртуальной сети мы воспользуемся ещё одной утилитой virsh
, которая работает поверх ещё одной замечательной библиотеки — libvirt
.
Libvirt — это, опять же, программная библиотека, предназначенная для управления гипервизорами. Проект libvirt ставит перед собой задачу объять необъятное — сделать интерфейс управления, подходящий сразу для всех гипервизоров. Задача благая, но уж слишком амбициозная. На данный момент гипервизоры, отличные от QEMU, поддерживаются довольно слабо. Однако, если Вам требуется сделать что-то именно с QEMU, то проект libvirt и утилита virsh, входящая в его состав, являются самым быстрым и удобным вариантом.
В libvirt
для создания различных виртуальных объектов (дисков, сетей, виртуалок) используются XML-схемы. XML-схема для создания сети выглядит примерно так:
<network>
<name>net_for_ssh</name>
<bridge name='net_for_ssh'/>
<ip address='192.168.100.1' netmask='255.255.255.0'/>
</network>
Где 192.168.100.1
— это адрес, присваиваемый сетевому интерфейсу, который будет автоматически создан на хосте и ассоциирован с новой виртуальной сетью.
Для того, чтобы создать сеть — необходимо передать путь к файлу с xml схемой сети в следующую команду:
virsh net-define net_for_ssh.xml
После создания сеть создаётся в выключенном состоянии, поэтому её ещё надо включить:
virsh net-start net_for_ssh
Теперь надо подключить виртуалку к свежесозданной сети. Для этого при создании виртуалки необходимо добавить параметр --network
:
virt-install \
--import \
--name my_super_vm \
--ram 1024 \
--disk my_super_vm.qcow2 \
--network network=net_for_ssh \
--noautoconsole
Обратите также внимание на аргумент --noautoconsole
, который отключает автоматическое подключение к консоли виртуалки через VNC-клиент (впрочем, если Вам всё-равно хочется зайти посмотреть на виртуалку, Вы можете воспользоваться virt-manager
).
Соединение готово, но пропинговать с хоста нашу виртуалку мы всё ещё не можем: сетевой интерфейс пока не настроен.
Настройка сетевого интерфейса на виртуалке
Как же мы будем настраивать интерфейс внутри виртуалки, если мы пока не можем выполнять на ней никаких команд (SSH-канал ведь ещё не настроен)? В этом нам снова поможет библиотека libguestfs
и утилита virt-builder
.
Дело в том, эта библиотека позволяет, в числе прочего, копировать файлы на виртуальный диск. Как мы знаем, в Ubuntu Server 18.04 за сетевые настройки отвечает netplan
, и для того, чтобы сконфигурировать сетевой интерфейс, нам достаточно подложить специальный .yaml
файлик в каталог /etc/netplan
. И сделать это можно с помощью той же утилиты virt-builder
с помощью параметра --copy-in
:
network:
version: 2
renderer: networkd
ethernets:
ens3:
addresses:
- 192.168.100.2/24
virt-builder ubuntu-18.04 \
--format qcow2 \
--output my_super_disk.qcow2 \
--copy-in netcfg_ssh.yaml:/etc/netplan/
И теперь при создании виртуального диска после раскатывания Ubuntu Server 18.04 из шаблона virt-builder
дополнительно скопирует файл netcfg_ssh.yaml
и подложит его в директорию /etc/netplan/
на файловой системе виртуального диска.
Теперь виртуальная машина должна пинговаться, проверим:
ping 192.168.100.2 -c5
Почти всё сделали, осталось лишь настроить SSH.
Настраиваем SSH на виртуалке
Для создания канала управления виртуалкой осталось сделать совсем чуть чуть:
- Прописать пароль для
root
-пользователя в виртуалке; - Создать ключи на виртуалке для SSH-сервера, чтобы он стартовал без ругани;
- Прописать в конфиг SSH-сервера возможность подключаться от имени рута с использованием пароля.
Начнём с пароля для root
. Здесь нас снова выручает virt-builder
, который, на самом деле, позволяет Вам делать с виртуальным диском поистине удивительные вещи, одна из которых — прописать пароль для root
-пользователя:
virt-builder ubuntu-18.04 \
--format qcow2 \
--output my_super_disk.qcow2 \
--root-password password:1111 \
--copy-in netcfg_ssh.yaml:/etc/netplan/
Теперь осталось сгенерировать ключи для SSH и подправить конфиг. Для этого нам надо всего-то надо выполнить пару команд:
ssh-keygen -A
sed -i \"s/.*PermitRootLogin.*/PermitRootLogin yes/g\" /etc/ssh/sshd_config
Вот только вопрос, а как же нам выполнить эти команды? Ведь virt-builder
работает только с образами дисков, никаких виртуальных машин на этом этапе не создаётся. Однако, у virt-builder
есть ещё пара козырей в рукаве. Он позволяет запускать программы гостевой системы без запуска собственно гостевой системы с помощью параметра --run-command
:
virt-builder ubuntu-18.04 \
--format qcow2 \
--output my_super_disk.qcow2 \
--root-password password:1111 \
--run-command "ssh-keygen -A" \
--run-command "sed -i \"s/.*PermitRootLogin.*/PermitRootLogin yes/g\" /etc/ssh/sshd_config" \
--copy-in netcfg_ssh.yaml:/etc/netplan/
Хороший вопрос. Документация libguestfs ничего не говорит нам о деталях реализации этого механизма, а исходники довольно запутаны. Но, похоже, что здесь используется User Space Linux Kernel. Это не контейнеры, но очень на них похоже. В любом случае можно сделать следующие выводы:
- в параметре
--run-command
можно смело вызвать любой бинарник, который есть на диске гостевой системы; - при этом можно смело пользоваться сетью. Например — устанавливать пакеты с помощью
apt install
.
И теперь… всё! Канал настроен! Можно было бы даже попробовать подключиться, если бы не одно "но". После выполнения команды virt-install
виртуалка только-только начинает включаться. Между моментом включения виртуалки и запуском на ней ssh сервера может пройти несколько секунд. Поэтому нам потребуется организовать незатейливый механизм ожидания ssh сервера:
#!/bin/bash
SSH_CMD="sshpass -p 1111 ssh -o StrictHostKeyChecking=no"
while ! $SSH_CMD root@192.168.100.2 echo Hello world from my super vm!
do
echo "Waiting for my super vm ..."
sleep 1
done
Также, мне пришлось добавить параметр -o StrictHostKeyChecking=no
к команде ssh для того, чтобы запрос на добавление сервера в список доверенных серверов не блокировал выполнение скрипта. Так же мне пришлось воспользоваться утилитой sshpass
для того, чтобы автоматизировать ввод пароля.
Итоги
В первой части статьи мы пока не написали ни одного реального системного теста, зато познакомились с серьёзным арсеналом утилит по работе с виртуальными машинами, научились автоматически создавать виртуалки, раскатывать на них ОС, настраивать их, а также налаживать канал управления по SSH. С этим запасом знаний мы теперь можем смело переходить к самому главному и интересному: как же, всё-таки, автоматизировать системные тесты на виртуалках.