Как создать мобильную ферму, или Вжух! И ты мобильный фермер
Привет, «Хабр»! Мы — Даня и Ксюша, разработчик и тестировщик команды мобильных приложений в компании ATI.SU. Сегодня хотим рассказать удивительную историю о том, как наши системные администраторы помогли нам развернуть мобильную ферму Android-девайсов, и с чем мы столкнулись. Опишем технические детали, чтобы вы могли повторить наш опыт. Однажды и ваша команда сможет отлаживать и тестировать удалённо на большом парке девайсов.
Ситуация: мы переходим на удалёнку
Когда мы перешли на удалённый режим работы из-за пандемии, у нас появились проблемы с тестовыми девайсами:
девайсов перестало хватать на всех участников команды;
неудобно было обмениваться девайсами, потому что мы временно перестали посещать офис, а баги, специфичные для конкретного девайса, никто не отменял.
Сначала мы решили увеличить парк девайсов, но это лишь частично решало проблему. Закупать несколько идентичных устройств не было смысла, так как у наших пользователей на руках огромное количество моделей с разными версиями Android и прошивками. Именно поэтому удалённая ферма девайсов удовлетворила бы наши потребности.
Мы столкнулись с первым выбором: стоит ли использовать какую-то готовую облачную ферму или разворачивать что-то свое. Вот наиболее популярные решения, которые мы рассматривали:
Ферма Samsung: исключительно с устройствами от Samsung.
Ферма Google: с небольшим парком устройств, выбранных Google. Данная ферма имеет ограничение: на ней можно запускать только автоматизированные тесты, но не тестировать вручную.
Ферма Huawei: схожа ферма с фермой от Samsung, но представлены только телефоны от Huawei.
В облачных фермах мы обнаружили ряд проблем, а именно:
отсутствие на фермах наиболее популярных девайсов, используемых среди наших пользователей;
невозможно пополнить ферму простым способом и своими силами;
невозможно подключить к облачной ферме уже существующий у нас парк устройств.
Таким образом мы пришли к необходимости реализовать собственную облачную ферму устройств.
Этап 1. Выбираем и настраиваем софт
В качестве ПО для фермы мы выбрали STF — инструмент для удалённого тестирования и отладки прямо на странице браузера.
От мобильной фермы мы ожидали следующих возможностей:
проводить ручное тестирование приложений без установки на компьютер дополнительного ПО;
отлаживать код приложения через стандартные средства (подключение через adb);
отлаживать WebView в приложении через Chrome Dev Tools;
снимать логи с устройства.
С учетом запрашиваемого перечня возможностей STF оказался для нас единственным вариантом. Дополнительно мы получили еще ряд приятных бонусов:
упрощенная отправка deeplink;
упрощенная отправка shell-команд;
контроль за файловой системой;
просмотр детальной информации о состоянии батареи, интернета, SIM и процессора.
STF реализует свои возможности через 2 канала управления устройствами:
STF должен быть развернут на машине на которой уже запущен ADB сервер и которая имеет доступ к желаемым телефонам.
STF устанавливает собственное приложение STFService.apk, работающее в состоянии foreground-service, на подключенные устройства. Приложение взаимодействует с бекендом STF через сокеты и существенно расширяет возможности по управлению устройствами.
STF
Этап 2. Выбираем железо для сервера и тестируем концентратор под нагрузкой
После выбора ПО основной задачей для нас был выбор железа. Решить эту задачу нам помогли наши отважные системные администраторы.
Первой идей стало заказать USB-концентратор, который управляется при помощи usbip. Данное решение нам показалось надежнее и красивее, чем обычный ПК и набор хабов. Однако красота красотой, а решение нужно было протестировать.
Мы обратились к российскому производителю, который согласился получить от нас набор девайсов, подключить их к концентратору и дать нам протестировать работу STF в такой конфигурации.
Общая инфраструктура выглядела в этом случае следующим образом:
в нашем офисе в Санкт-Петербурге стоит сервер с развернутым STF;
в Москве у производителя стоит концентратор, к которому подключены 10 наших девайсов;
на концентраторе запущен сервер usbip, к которому подключается клиент usbip, запущенный на нашем сервере;
далее usbip производит bind портов, и нашему серверу с STF начинает «казаться», что в его USB-порты подключены девайсы;
девайсы отображаются в STF.
При тестировании, как и ожидалось, мы столкнулись с набором проблем. Первая проблема была в лимите портов usb_over_ip. В Ubuntu этот лимит равен 8, чего явно недостаточно для нашей фермы. Исследовав проблему, мы выяснили, что в debian этот лимит выше. Также был вариант пересобрать ядро и увеличить лимит.
Решив эту проблему, мы сделали более масштабный тест, в ходе которого мы столкнулись с отваливающимися девайсами, зависанием интерфейса и огромными задержками (даже делая скидку, что когда концентратор и сервер будут находиться в одной сети, задержки должны сократиться). При росте трафика usbip не справлялся, а также начинались проблемы у слабого процессора, который установлен на USB-концентраторе.
После этого мы продолжили изучать темы доступных концентраторов, которые работают по иным протоколам или имеют более мощную конфигурацию железа. Мы нашли еще несколько вариантов в США и от ORICO. У данных лотов была долгая доставка и отсутствовала возможность протестировать железо до покупки, поэтому мы решили не брать «кота в мешке».
Этап 3. Продолжаем выбирать железо и тестируем софт
Потерпев неудачу в тестировании концентратора, мы с системными администраторами остановились на обычном ПК с процессором i7 и 32 Гб оперативной памяти. В качестве ОС использовали Ubuntu.
Для установки и настройки STF нам пригодилась инструкция из GitHub. Мы хотели глубже разобраться, как устроен STF изнутри, поэтому решили обойтись без использования готового Docker-контейнера.
В STF можно настроить доменную авторизацию пользователей, но у нас пока нет в этом необходимости, и мы авторизуемся под аккаунтами STF. Это позволяет увидеть, кто на данный момент использует девайс. Также аккаунты сохраняют все настройки, которые сделал пользователь под себя. Например: какие параметры девайса отображать и в каком порядке их выводить, а также какой язык использовать для описания параметров.
Ещё из полезных функций нам пригодился таймаут — это время, через которое девайс освобождается от текущего пользователя, если девайс не используется (–group-timeout
). В таком случае девайс может быть занят новым пользователем.
Этап 4. Покупаем стойку и хабы
Для фермы мы используем телекоммуникационную стойку на колесах. Она не громоздкая и легко перемещается по офису при необходимости. Таких стоек в продаже немало. К ней мы купили перфорированные полки под девайсы.
Для подключения девайсов к системному блоку мы закупили хабы TP-Link. Для нас были очень важны характеристики хабов: количество портов, наличие usb 3.0 и силы тока больше 1 А, так как современным девайсам недостаточно 0,5 А.
У хабов есть разъемы USB 3.0 с разной силой тока:
по 1,5 А — 3 штуки;
по 0,5 А — 4 штуки.
Хабы
Девайсы, у которых аккумуляторы садятся быстрее, мы подключили к более мощным разъёмам. Также нам пришлось докупить кабели для передачи данных, так как некоторые из них были рассчитаны только на зарядку.
Так выглядит ферма
Этап 5. Настраиваем фермерские девайсы
Большинство девайсов удалось настроить для работы в ферме следующим способом:
Включить на девайсе настройки разработчика.
Включить отладку и установку по USB в настройках разработчика.
Подключить девайс к хабу по USB-кабелю.
Выбрать тип подключения «Передача файлов».
Дождаться, когда девайс покажет диалог про разрешение отладки. Разрешить отладку.
При подключении к ферме на девайс автоматически устанавливается приложение Open STF. В настройках девайса необходимо дать ему все разрешения (Permissions)
Некоторые девайсы отключают сервис приложения Open STF, когда он работает в фоновом режиме. Это объясняется Doze mode — режимом глубокого сна девайса, в который он переходит, если не используется длительное время (по нашему опыту, от часа и больше в зависимости от девайса). Чтоб избежать отключения сервиса, мы настроили девайсы по инструкции с сайта Don«t kill my app!.
Помимо проблем с сервисом в приложении, существует отдельная проблема поддержки конкретного устройства в minicap. Через Minicap производится стриминг экрана устройства, что позволяет дублировать экран телефона в web-интерфейсе STF. Однако Minicap построен на внутренних API Android, которые могут отличаться от прошивки к прошивке и часть девайсов может не работать, так например долгое время были проблемы с устройствами от Xiaomi. Все существующие проблемы вы можете найти в issue к проекту, а также в случае проблем, возникающих у вас, вы можете заводить там новые issue.
Этап 6. Приручаем строптивых: Huawei и Xiaomi
При попытке разблокировать девайсы Huawei STF отображает чёрный экран, поэтому для них мы сделали дополнительные настройки:
в настройках экрана выбрали пункт «Включать спящий режим — никогда»;
в настройках разработчика выбрали пункт «Не выключать экран» или «Оставлять активным» (название отличается на разных девайсах).
Также на некоторых моделях Huawei есть настройка «Умная зарядка». Мы ее отключили, т.к она приводила к отключению устройства от фермы после полного заряда.
Для подключения к ферме и отладки смартфоны Xiaomi также потребовали дополнительных настроек:
включить режим разработчика на девайсе (7 тапов по версии билда MIUI);
перейти в настройки разработчика, включить следующие пункты: отладка по USB, установка через USB, отладка по USB (раздел «Настройки безопасности»);
подключить Mi-аккаунт;
вставить SIM-карту, активировать ее не понадобилось;
перезагрузить девайс.
Этап 7. Отлаживаем и тестируем
Из привычных действий с девайсом в STF доступны все, кроме выключения или перезагрузки, так как после перезагрузки девайс не способен самостоятельно подключиться к ферме.
Страница работы с STF выглядит так:
Для установки приложения на фермерский девайс достаточно перетащить APK-файл. Для сбора логов в STF есть меню, аналогичное Logcat в Android Studio. В нём нужно нажать Get для старта логирования.
Получение и сохранение логовПример логов
Логи можно сохранять в форматах json и log.
Для выполнения команд adb используется Shell — командная строка.
Кроме того, в STF можно использовать debug-режим. Для этого на вашем компьютере должен быть установлен Android Debug Bridge. Алгоритм подключения debug:
В разделе remote-debug лежит команда для удаленного подключения к отладке девайса.
Необходимо выполнить команду в терминале вашего компьютера
При первом выполнении команды подключиться не получится. Терминал выдаст такое сообщение:
failed to authenticate to <адрес из команды>
На странице фермы в браузере появится диалог с предложением верифицировать ключ вашего компьютера на ферме, нажимаем Add Key. Далее нужно перейти в Settings — Keys и проверить, что добавился публичный ключ.
Далее нужно перейти в Settings — Keys и проверить, что добавился публичный ключ
Выполнить команду из раздела remote-debug повторно, и терминал ответит:
already connected to <адрес устройства>.
После этого девайс будет доступен для отладки, с ним можно будет работать с помощью привычных команд adb.
Также будет возможна отладка WebView на странице chrome://inspect.
Этап 8. Отладка WebView
Следующей задачей, связанной с фермой, стало предоставление возможности отладки WebView посредством Chrome Dev Tools без установки adb.
У нас возникла необходимость предоставлять средства для отладки Google Chrome другим командам. Часто у них не было adb, а его установка и использование усложняли им жизнь, поэтому мы решили оптимизировать этот момент.
WebView предоставляет возможности для отладки веб страниц. Это работает благодаря тому, что при включении опции setWebContentsDebuggingEnabled
WebView открывает unix domain socket на девайсе, который работает по протоколу Chrome DevTools Protocol.
Данный сокет запущен на устройстве, порты которого недоступны извне, и Google Chrome, запущенный на локальном компьютере, отображает только те девайсы, которые подключены к ADB (через USB/через remote connect).
У нас возникло несколько идей, как сделать отладку WebView доступной без установки adb. И мы их проработали более подробно. И вот наши результаты.
Идея 1. Можно запустить виртуальный Сhrome на ферме (на той же машине, где запущен STF) с открытым портом для дебага, предварительно поставив x-server.
google-chrome --disable-gpu --no-sandbox --user-data-dir=/tmp/chr --remote-debugging-port=9222
Далее дать доступ к порту 9222 извне и подключиться к этому порту со своего локального Сhrome.
Таким образом вы будете отлаживать Chrome, который в свою очередь отлаживает WebView на девайсе. Схема оказалось нерабочей из-за того, что при запуске отладки WebView (Inspect) запускается новое окно, которое недоступно для отладки через chrome-dev-tools.
Идея 2. Вместо отладки виртуального хрома на ферме, можно подключиться к нему через x-server и работать через GUI-виртуального хрома. Этот вариант оказался чуть более эффективным и даже окно для отладки WebView открывалось, но фрагмент предпоказа страницы на девайсе был недоступен из этой страницы. Также не любой терминал позволяет работать x-server, что понижает эффективность данного решения.
Рабочая идея. Мы решили обратиться к истокам и выяснить, каким образом Google Chrome подключается к портам внутри девайсов и какие средства ADB он использует.
При изучении был обнаружен следующий исходный файл dev_tools_adb_bridge. Оказалось, что Chrome проверяет все unix domain socket на девайсе по магическому фильтру const char kDevToolsChannelNameFormat[] = "%s_devtools_remote"
. Далее, найдя необходимые сокеты для отладки, пробрасывает эти сокеты наружу для доступа к ним.
Соответственно, мы приняли решение написать микросервис, который дублирует эту логику. Такой скрипт должен выполнять следующую логику:
находить девайс по его идентификатору
deviceId
;искать сокеты по фильтру, специфичному для WebView
webview_devtools_remote
;пробрасывать сокет с девайса в tcp порт сервера, который доступен для доступа
portToForward
.
Скрипт на TypeScript, построенный на базе библиотеки, которая также используется внутри STF adbkit, будет выглядеть следующим образом:
import * as adb from "@devicefarmer/adbkit";
export const client = adb.Adb.createClient({
host: "localhost",
});
export const forwardDevicePortToAnother = async (
deviceId: string,
portToForward: string,
) => {
const deviceClient = client.getDevice(deviceId);
const shellResult = await deviceClient.shell(
"cat /proc/net/unix | grep webview_devtools_remote",
);
const shellOutput = (await adb.Adb.util.readAll(shellResult))
.toString()
.trim();
if (shellOutput.length === 0) return;
const webviewRemotePort = shellOutput.split("@")[1];
const forwardResult = await deviceClient.forward(
`tcp:${portToForward}`,
`localabstract:${webviewRemotePort}`,
);
return forwardResult;
};
Соответственно, если наша ферма развёрнута по адресу x.x.x.x и на этой машине у нас есть открытый порт 2222, то выполнив данный скрипт для некоторого девайса, на котором запущен WebView, мы получим доступный порт для отладки WebView: x.x.x.x:2222. Этот порт мы можем вставить в Сhrome на нашей локальной машине, и она найдет девайс.
Дописав несколько вспомогательных методов для получения списка устройств и отключения проброса портов, а также небольшой фронт на Flutter-web, мы получили следующий инструмент.
Запускаем WebView на девайсе в ферме.
Переходим в инструмент для проброса портов и находим нужный девайс.
Запускаем проброс портов.
Прописываем полученный адрес в chrome://inspect/#devices.
Запускаем отладку WebView.
Если кому-то будет полезен данный инструмент, мы с удовольствием приведём код сервиса в порядок и опубликуем его в нашем GitHub.
Этап 9. Светлое будущее фермеров
В будущем мы планируем развивать ферму и видим 3 основных направления:
Пожарная безопасность фермы. Сейчас основная проблема девайсов — это аккумуляторы, которые иногда выходят из строя и могут загореться или взорваться. Мы уже регулярно осматриваем ферму, чтобы обнаружить такие аккумуляторы вовремя, а также мы используем приложение, которое показывает состояние аккумулятора. Также мы уже заказали встроенный шкаф для девайсов с металлическим покрытием и отдельными ячейками для каждого девайса. Вдобавок, мы изучаем направления по избавлению девайсов от аккумуляторов вовсе, то есть попытаться изменить электросхему девайса, чтобы он питался от сети напрямую.
iOS. В нашем парке девайсов помимо Android-устройств также есть и iOS-устройства, которые используются нативной командой iOS-разработки и командой Flutter-разработки. STF имеет средства для поддержки iOS-устройств, но пока мы не успели протестировать этот способ в работе. Также мы будем исследовать варианты расширения нашего микросервиса для отладки WebView и на iOS.
Решение проблем на части девайсов. В нашем парке девайсов все еще есть ряд девайсов, которые либо имеют проблемы с Minicap, либо убивают сервис STF. А некоторые девайсы имеют баги другого рода, что мы выявили по логам STF. Например, на них возникают бесконечные цикличные коннекты-дисконнекты. Для каждого девайса мы планируем провести индивидуальную работу, чтобы максимально расширить наш парк.
Заключение
Хотим добавить, что каждой ферме нужен фермер, поэтому у нас всегда есть хотя бы один человек в офисе, который при возникновении проблем может перезагрузить девайс и сделать несложные настройки.
А также хотим поблагодарить наших системных администраторов, которые помогали нам с выбором железа и софта для фермы, с настройкой и тестированием STF и проделали огромный объём работы для фермы!
Мы очень надеемся, что статья поможет и вам в решении проблем с девайсами при удалённой работе.