Как реализовать A/B-тестирование

OEM и поставщики процессоров, которые хотят реализовать обновления системы A/B, должны убедиться, что их загрузчик операционной системы реализует HAL boot_control и передает ядру правильные параметры.

Реализация HAL для управления загрузкой

Загрузчики операционной системы, поддерживающие A/B-обновления, должны реализовывать HAL boot_control в hardware/libhardware/include/hardware/boot_control.h. Вы можете протестировать реализацию с помощью утилиты system/extras/bootctl и system/extras/tests/bootloader/.

Также необходимо реализовать конечный автомат, показанный ниже.

Рисунок 1. Машина состояний загрузчика

Как настроить ядро

Чтобы реализовать системные обновления A/B:

  1. При необходимости выборочно примените следующие исправления ядра:
  2. Убедитесь, что аргументы командной строки ядра содержат следующие дополнительные аргументы:
    skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity <public-key-id> <path-to-system-partition>"
    …где значение <public-key-id> – это идентификатор открытого ключа, который используется для проверки подписи таблицы verity (подробнее о dm-verity…).
  3. Добавьте сертификат X .509, содержащий открытый ключ, в системную связку ключей:
    1. Скопируйте сертификат X .509 в формате .der в корневой каталог kernel. Если сертификат X .509 имеет формат .pem, используйте следующую команду openssl, чтобы преобразовать его из формата .pem в формат .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Создайте zImage, чтобы включить сертификат в системную связку ключей. Чтобы проверить, включена ли функция, найдите запись procfs (для этого должна быть включена функция KEYS_CONFIG_DEBUG_PROC_KEYS):
      angler:/# cat /proc/keys
      
      1c8a217e I------     1 perm 1f010000     0     0 asymmetri
      Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f []
      2d454e3e I------     1 perm 1f030000     0     0 keyring
      .system_keyring: 1/4
      Успешное включение сертификата .X509 указывает на наличие открытого ключа в системной связке ключей (выделен идентификатор открытого ключа).
    3. Замените пробел на # и передайте его как <public-key-id> в командной строке ядра. Например, вместо <public-key-id> укажите Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f.

Как задать переменные сборки

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

Обязательно для целевой страницы A/B-тестирования
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    и другие разделы, обновленные через update_engine (радио, загрузчик операционной системы и т. д.)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
Пример можно посмотреть на странице /device/google/marlin/+/android-7.1.0_r1/device-common.mk. При необходимости можно выполнить описанный в разделе Компиляция шаг dex2oat после установки, но до перезагрузки.
Настоятельно рекомендуется для целевого значения A/B-тестирования
  • Определение: TARGET_NO_RECOVERY := true
  • Определение: BOARD_USES_RECOVERY_AS_BOOT := true
  • Не указывайте BOARD_RECOVERYIMAGE_PARTITION_SIZE
Нельзя задать для целевого значения A/B-теста
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Необязательно для отладочных сборок. PRODUCT_PACKAGES_DEBUG += update_engine_client

Как задать разделы (слоты)

Устройствам с разделами A/B не нужны разделы восстановления и кеша, поскольку Android больше не использует их. Теперь раздел данных используется для скачанного OTA-пакета, а код образа восстановления находится в разделе загрузки. Все разделы, которые используются для A/B-тестирования, должны быть названы следующим образом (слоты всегда называются a, b и т. д.): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

Кеш

В случае с обновлениями, не относящимися к A/B, раздел кеша использовался для хранения скачанных пакетов беспроводного обновления и временного хранения блоков при применении обновлений. Размер раздела кеша всегда было сложно определить, поскольку он зависел от того, какие обновления вы хотели применить. В худшем случае размер раздела кеша будет равен размеру образа системы. При обновлении A/B не нужно сохранять блоки (поскольку запись всегда выполняется в раздел, который в данный момент не используется), а при потоковой передаче A/B не нужно скачивать весь пакет OTA перед его применением.

Восстановление

Теперь RAM-диск восстановления содержится в файле boot.img. При переходе в режим восстановления загрузчик не может добавить параметр skip_initramfs в командную строку ядра.

При обновлении без использования A/B-разделов раздел восстановления содержит код, который применяется для установки обновлений. Обновления A/B применяются update_engine, который работает в обычном загруженном образе системы. Режим восстановления (Recovery mode) по-прежнему используется для сброса настроек до заводских и установки из неизвестного источника пакетов обновлений (отсюда и название). Код и данные для режима восстановления (Recovery mode) хранятся в обычном загрузочном разделе в RAM-диске. Чтобы загрузить образ системы, загрузчик операционной системы сообщает ядру, что RAM-диск нужно пропустить (иначе устройство загрузится в режиме восстановления (Recovery mode)). Раздел восстановления небольшой (и большая его часть уже находится в загрузочном разделе), поэтому размер загрузочного раздела не увеличивается.

Fstab

Аргумент slotselect должен быть в строке для разделов, в которых проводится A/B-тестирование. Пример:

<path-to-block-device>/vendor  /vendor  ext4  ro
wait,verify=<path-to-block-device>/metadata,slotselect

Ни один раздел не должен называться vendor. Вместо этого будет выбран раздел vendor_a или vendor_b и смонтирован в точке монтирования /vendor.

Аргументы слота ядра

Суффикс текущего слота должен передаваться через узел дерева устройств (/firmware/android/slot_suffix) или через аргумент командной строки ядра androidboot.slot_suffix или bootconfig.

По умолчанию fastboot прошивает текущий слот на устройстве с A/B-разделами. Если пакет обновления также содержит изображения для другого, неактивного слота, fastboot также загрузит эти изображения. Доступны следующие варианты:

  • --slot SLOT. Переопределяет поведение по умолчанию и запрашивает у fastboot прошивку слота, переданного в качестве аргумента.
  • --set-active [SLOT]. Активируйте слот. Если дополнительный аргумент не указан, активным становится текущий слот.
  • fastboot --help. Подробнее о командах…

Если загрузчик реализует fastboot, он должен поддерживать команду set_active <slot>, которая устанавливает текущий активный слот на заданный слот (она также должна очистить флаг, указывающий на то, что слот не загружается, и сбросить количество попыток на значения по умолчанию). Загрузчик также должен поддерживать следующие переменные:

  • has-slot:<partition-base-name-without-suffix>. Возвращает значение "yes", если указанный раздел поддерживает слоты, и "no" в противном случае.
  • current-slot. Возвращает суффикс слота, который будет загружен следующим.
  • slot-count. Возвращает целое число, представляющее количество доступных мест. В настоящее время поддерживаются два слота, поэтому значение – 2.
  • slot-successful:<slot-suffix>. Возвращает "yes", если указанный слот был отмечен как успешно загруженный, и "no" в противном случае.
  • slot-unbootable:<slot-suffix>. Возвращает "yes", если указанный слот помечен как незагружаемый, и "no" в противном случае.
  • slot-retry-count:<slot-suffix>. Количество оставшихся попыток загрузки в указанном слоте.

Чтобы посмотреть все переменные, выполните команду fastboot getvar all.

Как создать пакеты OTA

Инструменты для пакетов OTA используют те же команды, что и команды для устройств без разделов A/B. Файл target_files.zip должен быть сгенерирован путем определения переменных сборки для целевого объекта A/B. Инструменты для создания пакетов OTA автоматически определяют и генерируют пакеты в формате для программы обновления A/B.

Примеры:

  • Чтобы создать полный OTA-пакет:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • Чтобы создать дополнительное обновление OTA:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

Настройка разделов

update_engine может обновлять любую пару разделов A/B, определенных на одном диске. У пары разделов есть общий префикс (например, system или boot) и суффикс для каждого слота (например, _a). Список разделов, для которых генератор полезной нагрузки определяет обновление, настраивается с помощью переменной AB_OTA_PARTITIONS.

Например, если включена пара разделов bootloader_a и booloader_b (_a и _b – суффиксы слотов), вы можете обновить эти разделы, указав в конфигурации продукта или платы следующее:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

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

Настройка после установки

Вы можете настроить этап после установки для каждого обновленного раздела по-разному, используя набор пар "ключ-значение". Чтобы запустить программу, расположенную в каталоге /system/usr/bin/postinst в новом образе, укажите путь относительно корня файловой системы в системном разделе.

Например, usr/bin/postinst – это system/usr/bin/postinst (если не используется RAM-диск). Кроме того, укажите тип файловой системы, который будет передан системному вызову mount(2). Добавьте в файлы .mk для товаров или устройств следующие данные (если применимо):

AB_OTA_POSTINSTALL_CONFIG += \
  RUN_POSTINSTALL_system=true \
  POSTINSTALL_PATH_system=usr/bin/postinst \
  FILESYSTEM_TYPE_system=ext4

Компиляция приложений

Приложения могут компилироваться в фоновом режиме перед перезагрузкой с новым образом системы. Чтобы компилировать приложения в фоновом режиме, добавьте в конфигурацию устройства продукта (в файле device.mk продукта) следующие строки:

  1. Включите нативные компоненты в сборку, чтобы скрипт компиляции и двоичные файлы были скомпилированы и включены в образ системы.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Подключите скрипт компиляции к update_engine так, чтобы он выполнялся на этапе после установки.
      # A/B OTA dexopt update_engine hookup
      AB_OTA_POSTINSTALL_CONFIG += \
        RUN_POSTINSTALL_system=true \
        POSTINSTALL_PATH_system=system/bin/otapreopt_script \
        FILESYSTEM_TYPE_system=ext4 \
        POSTINSTALL_OPTIONAL_system=true
    

Инструкции по установке предварительно оптимизированных файлов в неиспользуемый второй системный раздел приведены в статье Установка файлов DEX_PREOPT при первой загрузке.