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

Я Наталья Грязнова, ведущий инженер по разработке ПО в YADRO. Моя задача — не просто проверить, что СХД работает, а воспроизвести реальные риски отказа системы и проверить ее на устойчивость: высокая нагрузка, внезапные отказы компонентов системы, нестабильные внешние условия, например перебои в сети. В этом тексте расскажу, как мы тестируем отказоустойчивость СХД TATLIN.UNIFIED: какие сбои моделируем, как устроены автотесты и почему короткие прогоны не справляются с поиском критичных багов.

Больше о тестировании и его автоматизации вы узнаете на митапе для QA-специалистов. Ждем вас 19 июня в Санкт-Петербурге или онлайн, чтобы обсудить современные QA-инструменты. Регистрация — по ссылке.

Как устроена СХД

Система хранения данных TATLIN.UNIFIED — это комплекс из взаимосвязанных аппаратных и программных компонентов. 

Система хранения данных TATLIN названа в честь выдающегося архитектора-конструктивиста Владимира Татлина
Система хранения данных TATLIN названа в честь выдающегося архитектора-конструктивиста Владимира Татлина

В основе СХД — два контроллера, каждый из которых выполняет операции записи, чтения и управления. Они работают в режиме резервирования: если один выйдет из строя по каким-либо причинам, второй подхватит все задачи на себя, обеспечивая непрерывность обслуживания. Более подробно о том, как устроена СХД, мы уже писали.

Структура и компоненты СХД
Структура и компоненты СХД

Каждый контроллер содержит:

  • Процессор — осуществляет вычисления и управляет потоками данных.

  • Оперативную память и кэш, которые ускоряют доступ к данным и снижают нагрузку на дисковую подсистему.

  • Сетевые интерфейсы, обеспечивающие соединение с внешними серверами и инфраструктурой по Ethernet или Fibre Channel.

Контроллеры соединены с дисковыми полками — стойками с накопителями (HDD, SSD, NVMe), где хранятся данные. Готовые «коробки» СХД монтируются в стойки дата-центров, там созданы все условия для охлаждения и физической защиты оборудования. После этого система встраивается в IT-инфраструктуру заказчика: подключается к хостам — серверам, на которых расположены приложения или службы, использующие это хранилище как ресурс.

Обратная сторона стойки с СХД: коммутация и подключение кабелей
Обратная сторона стойки с СХД: коммутация и подключение кабелей

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

Подходы к системному тестированию СХД

Системное тестирование СХД проводится на оборудовании, которое настроено почти так же, как в условиях реального использования — у клиентов или в дата-центре. Слово «системный» в определении означает, что тестируется не отдельный компонент, а вся система целиком: контроллеры, диски, программные сервисы, сетевые соединения и т. д. 

Главная задача системного тестирования — убедиться, что СХД:

  • сохраняет целостность данных,

  • работает стабильно при высоких нагрузках,

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

  • соответствует требованиям по производительности.

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

Функциональная спецификация в контексте ISTQB (International Software Testing Qualifications Board) 

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

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

Внутренняя система YADRO для сбора, хранения и анализа информации об оборудовании, предоставленной в рамках согласованных условий эксплуатации

В эту внутреннюю систему поступает телеметрия от установленных у заказчиков систем. Инженеры анализируют эти данные, чтобы понять, какие конфигурации компонентов чаще всего используются на практике, мы собираем статистику из «поля» — от реальных пользователей. Это нужно, чтобы тестировщики могли заранее подготовить тестовое окружение, максимально приближенное к пользовательскому. Тогда мы не тратим время на проверку абстрактных или маловероятных сценариев, а сосредотачиваемся на самых актуальных и реалистичных.

Например, в системе TATLIN.UNIFIED по системным требованиям должен поддерживаться как блочный, так и файловый доступ к данным. Поэтому мы анализируем, как именно пользователи настраивают систему: какие ресурсы создают (блочные или файловые), какие типы накопителей используют, и какие способы подключения выбирают чаще — iSCSI, Fibre Channel и т.д. Все это помогает нам определить приоритетные конфигурации и сосредоточиться на них в тестировании.

Обратная связь от службы поддержки L3 и инженеров по эксплуатации систем

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

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

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

Так каждый тест создается не ради галочки, а как реакция на реальный риск.

Почти любой автотест в рамках системного уровня построен по одному принципу:

  • Сначала создается нагрузка: с помощью инструментов типа FIO система начинает активно читать/писать данные — подробнее о том, какие ошибки можно обнаружить на этом этапе мы рассказывали в этой статье.

  • Затем инженеры по тестированию проверяют реакцию системы: не теряются ли данные, не падают ли контроллеры, сохраняется ли доступ.

  • Дополнительно анализируется: тип накопителей (HDD, SSD, NVMe), уровень RAID-защиты, объемы и интенсивность операций, задержки, очереди, ошибки I/O, состояние кэша и использование памяти.

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

Из-за сложности и многообразия приложений, которые могут использовать СХД, невозможно протестировать все случаи. Но можно смоделировать различные типы FIO-профилей нагрузки, которые приближенно соответствуют поведению приложения, за счет настройки размера блоков, глубины очереди, характера записи (последовательный, случайный), частоты запросов и т. д.

Жизненный цикл теста

У каждого системного теста есть четкая структура:

  1. Подготовка стенда: разворачивание конфигурации СХД, создание пулов и томов, подключение агентов-хостов.

  2. Запуск нагрузки: FIO с параметрами, например random read/write, размер I/O блока 8 КБ, 80% write, 20% read.

  3. Инжекция отказа: сбой одного из компонентов (перезагрузка контроллера, деградация диска).

  4. Наблюдение и логгинг: сбор метрик, логов, проверка целостности данных.

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

В каждый тест встроены дополнительные проверки: количество и состояние multipath-путей, наличие поврежденных данных FIO --do_verify, логи контроллеров и поведение интерфейса управления. Это позволяет фиксировать проблему как можно ближе к моменту сбоя, понять, что именно повлияло на отказ, и воспроизвести баг точечно и быстро.

От пулов до ExitStack: автоматизация тестовой логики

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

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

Архитектурный подход: объектная модель

Каждый ресурс в СХД представлен в автотестах как объект Python. Например, пул представлен в виде класса pool, у которого есть атрибуты: тип накопителей, размер, схема защиты, имя и т. д.

Фреймворк pytest — один из самых популярных инструментов для написания автотестов на Python благодаря своей лаконичности, расширяемости и богатому набору встроенных возможностей. 

Одна из ключевых особенностей pytest — поддержка фикстур, которые упрощают управление подготовкой и завершением тестов, а также выносить повторяющийся код в переиспользуемые компоненты. Pytest-фикстуры позволяют тестам быть чистыми, изолированными и легко масштабируемыми, особенно в условиях сложных сценариев или интеграционного тестирования. 

С помощью этого кода мы реализовали фикстуру pool, которая в начале каждого теста создает пул из накопителей, а в конце теста — пул удаляет. Всю логику по созданию и удалению нашего объекта «пул» мы унесли в класс PoolsService, который управляет пулами по CLI или REST API соответствующего микросервиса:

import pytest

@pytest.fixture(scope='function')
def pool():
    pool = PoolsService.create_pool()
    yield pool
    PoolsService.delete_pool(pool)

Реализованную фикстуру pool мы передаем в тест в качестве аргумента:

def test_pool_create(self, pool):
    # к этому моменту у нас создался объект пул
    # взаимодействуем с созданным пулом

Работаем с ресурсами без лишнего кода

Чтобы управлять файлами, сетевыми соединениями, временными объектами и другими ресурсами в тестах, удобно использовать контекстный менеджер. Это специальный механизм в Python, который автоматически выполняет нужные действия при входе в блок кода (например, открыть файл, создать объект) и при выходе из него (например, закрыть соединение, удалить временные данные).

Рассмотрим упрощенный пример, в котором мы создаем свой класс SimpleContext по протоколу контекстного менеджера — в классе реализованы два обязательных магических метода _enter_ и _exit_. Теперь мы можем обратиться с помощью with к SimpleContext(): _enter_ запускается при входе в блок with, а _exit_ запустится при выходе из блока with.

class SimpleContext:
    def __enter__(self): return self
    def __exit__(self, *args): pass

  
with SimpleContext() as ctx:
    print("Внутри")

Расширение: использование ExitStack для массового управления

Когда нужно создать много объектов с разными параметрами, мы используем ExitStack. Это инструмент из класса встроенного модуля contextlib, который помогает управлять несколькими менеджерами контекста без конструкции with.

Сначала создадим контекстный менеджер для правильной обработки создания и удаления сущности пула. Воспользуемся декоратором для контекстного менеджера @contextmanager, где код до yield выполняется при входе в with (как enter), код после yield — при выходе (как exit), а yield передает управление блоку with и возвращает необходимое значение:

from contextlib import contextmanager

from tatlinlib.tatlin_services import PoolsService


@contextmanager
def pool_context(disk_type='HDD’):
    requested_pool = get_pool_for_request(disk_type=disk_type)
    pool = PoolsService.create_pool(pool=requested_pool)
    
    yield pool

    PoolsService.delete_pool(pool.name)

Далее добавим в conftest.py (файл для обмена фикстур в Pytest) универсальную фикстуру с созданием объекта класса ExitStack():

# conftest.py
from contextlib import contextmanager, ExitStack


@pytest.fixture(scope='function')
def object_stack():
	pool_stack = ExitStack()
	yield pool_stack
	pool_stack.close()
    

И теперь в тесте мы можем пользоваться преимуществами созданного ранее контекстного менеджера pool_context и классом ExitStack. Используя ExitStack.enter_context() в теле теста, мы сколько угодно раз вызываем контекстный менеджер пула без конструкции with. Создаем таким образом пулы с различными конфигурациями внутри теста и передаем объекты пулов в хелперы. А очистку созданных пулов, даже если тест упадет, за нас сделает ExitStack.close() на выходе из теста в фикстуре object_stack, вызвав метод exit соответствующего менеджера контекста:

import pytest


def test_pool_create(self, object_stack):
    """
    Test:
    1. Create N pools
    """

    pool_ssd =object_stack.enter_context(
         pool_context(disk_type=SSD))

    pool_hdd = object_stack.enter_context(
         pool_context(disk_type=HDD))
    # do smth with pool object

Если нам нужно расширить тесты созданием томов в пулах, то понадобится создать менеджер контекста для тома, в фикстуру object_stack добавить создание объекта ExitStack() для томов и указать после ключевого слова yield очередность удаления ExitStack-объектов:

# conftest.py
from contextlib import contextmanager, ExitStack


@pytest.fixture(scope='function')
def object_stack():
	pool_stack = ExitStack()
    volume_stack = ExitStack()
    yield pool_stack, volume_stack
    volume_stack.close()
	pool_stack.close()

Среди преимуществ такого подхода — возможность многократно создавать объекты с разными параметрами прямо во время выполнения теста, а также контролировать их удаление. 

Однако за эту гибкость приходится платить: необходимо заранее продумать и реализовать модель объекта, а также явно написать контекстные менеджеры. Это требует дополнительных усилий на этапе разработки тестовой инфраструктуры.

Долгие автотесты и поиск компромисса с assert

В системном тестировании, особенно при работе с СХД, часто применяются длительные автотесты — они могут выполняться по 6-12 часов и дольше. Это необходимо, чтобы проверять стабильность и поведение системы под реальной или приближенной к реальности нагрузкой во времени. Однако это ставит под вопрос традиционные подходы к верификации, в частности — использование стандартного assert.

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

Например, в методе с постпроверками в классе Failover Helper, отвечающем за эмуляцию выхода из строя контроллеров, у нас два hard-assert, но если первый из них не пройдет, то и второй assert не выполнится. Однако часто нам даже после падения первой проверки все еще нужно проверить второе условие. Пример кода с hard-assert:

class FailoverHelper:
	@staticmethod
	@allure.step('Post-failover health check')
	def post_failover_health_check():
    	assert unavailable_hosts != 2, 'Cannot check hosts. Nodes are offline!’
    	assert pool_state = 'online', f'Pool state expected: online, actual: {pool_state}'

Чтобы избежать прерывания долгих тестов из-за единичной ошибки, используется подход soft-assert — мягких проверок, которые не останавливают выполнение, но фиксируют факт ошибки. В команде мы используем Pytest плагин Pytest-check, которая позволяет писать soft-assert через контекстный менеджер with check. Так мы выполняем десятки и даже сотни проверок в одном прогоне, а вся информация собирается в единый отчет.

В цикле проверок по контроллерам, с помощью soft-assert с конструкцией with check мы не останавливаем выполнение тестового сценария, если первый из списка контроллер стал недоступен по какой-то причине. Все итерации выполняются, и мы соберем необходимую информацию и сделаем проверки на доступном контроллере. Код, который поможет это реализовать:

from pytest_check import check


def object_stack():
    pool_stack = ExitStack()
    yield pool_stack
    for host in hosts:
        with check:
             result = None
             try:
                PoolsService.get_pools_info(host)
             except HostUnavailableException as err:
                result = err
             assert not issubclass(type(result), HostUnavailableException), f'Failed to get pool: {result}'

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

Soft-assert на практике: условие assert упало на первом контроллере, но тест продолжил assert-проверку на втором контроллере
Soft-assert на практике: условие assert упало на первом контроллере, но тест продолжил assert-проверку на втором контроллере

Soft-assert — не замена стандартного assert, а дополнение, применимое в специфичных сценариях:

  • Если важно собрать диагностику по нескольким компонентам, даже в случае локального сбоя.

  • Если падение на одном этапе не делает весь тест недействительным.

  • Если тест проходит долго и повторять его слишком дорого.

При этом важно понимать: soft-assert не должен скрывать серьезные ошибки или заменять критические проверки, особенно там, где последствия фатальны.

Куда уходит память

При системном тестировании СХД важно отслеживать побочные эффекты, например утечки памяти. Даже если сама система справляется с обработкой данных, незаметные накопления в памяти могут привести к перезагрузкам, снижению производительности и проблемам в будущем. Для диагностики таких случаев в тестах используются хелперы и декораторы, позволяющие следить за объемом используемой оперативной памяти.

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

# класс утилит
# os_utils.py
import allure


class OsUtils:
@staticmethod
@allure.step("Show system memory usage on: {ip}")
def show_system_memory_usage(ip: str) -> None:
    terminal = SSHHelper.execute_command(
        'free -h', ip)
    assert_terminal_output(terminal)

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

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

# os_utils.py
# декоратор

def system_memory_usage(func_to_decor):
    def wrapper(self: 'UnixMultipath', *args, **kwargs):
        OsUtils.show_system_memory_usage(self.initiator.ip)

        res = func_to_decor(self, *args, **kwargs)

        OsUtils.show_system_memory_usage(self.initiator.ip)
        return res

    return wrapper

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

Применение декоратора на практике выглядит просто:

# класс утилит
from commons.helpers.os_utils import free_page_buff_cache


class UnixMultipath:
    def __init__(self, initiator: 'UnixInitiator'):
        self.initiator = initiator
		
    @system_memory_usage
    @allure.step('Show multipath devices')
    def show_mpath_devices(self):
        terminal = self.initiator.execute_command('multipath -ll’,                       timeout=self.EXECUTE_TIMEOUT)
	
)

Этот метод, например, проверяет наличие путей между хостом и СХД. Если они не построены, I/O не состоится. Однако команда может возвращать тысячи строк вывода, и важно убедиться, что обработка такого объема информации не приводит к утечке памяти.

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

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

Автоматизация удаленного доступа через SSH

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

Библиотеки мы устанавливаем в Docker с помощью requirements.txt
Библиотеки мы устанавливаем в Docker с помощью requirements.txt

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

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

from paramiko import SFTPClient


class SftpHelper:

    def __init__(self, ip, use_login_and_password: bool = False):
	                …

    def __enter__(self) -> SFTPClient:
        return self.__sftp_client
	                  …
    def __exit__(self, exc_type, exc_value, exc_traceback):
	  self.__sftp_client.close()

Где хранить все тесты

Чтобы упростить анализ тестовых результатов, мы используем Allure — инструмент визуализации, который помогает структурировать, интерпретировать и эффективно разбирать даже самые сложные тестовые сценарии. Среди опций Allure:

  • визуализация шагов,

  • добавление статистических данных, таких как отчеты утилиты iostat с контроллеров и хостов, содержащие информацию о загрузке процессора и параметрах ввода-вывода,

  • сводные таблицы об отказах, деградации и т. д.

Это сильно помогает при передаче информации разработчику: достаточно открыть Allure-репорт — вся диагностика будет в одном месте.

Тестирование отказоустойчивости

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

Отказоустойчивая система:

  • сохраняет доступ к данным при выходе из строя части оборудования,

  • обеспечивает целостность и непрерывность работы сервисов,

  • может самостоятельно восстанавливаться после сбоя.

В случае TATLIN.UNIFIED это означает, что даже при сбое диска, контроллера, сети, питания система должна продолжать работу — без потери данных и падения производительности (или с управляемым снижением).

Как и зачем мы эмулируем отказы

Цель тестирования СХД — не только в том, чтобы убедиться в штатной работе. Важно сделать любые сбои управляемыми еще до того, как с ними столкнется заказчик. 

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

Что именно ломаем

Тесты охватывают широкий спектр ситуаций:

  • Аппаратные сбои — выход из строя дисков, отказ сетевых карт, обрыв соединений, отключение питания СХД или потеря питания дисковой полки.

  • Программные сбои — падение внутренних микросервисов, штатные и нештатные перезагрузки и выключения контроллеров СХД-кластера, паники в ядре Linux, переполнения дисков в пуле.

  • Сетевые сбои — потеря путей между хостом и СХД и прерывание соединений и деградация сети. Потеря сетевой связности между контроллерами.

Такие сбои могут быть вызваны как вручную, так и программно — чаще всего мы используем автоматизацию: SysRQ команды, fault injection, iptables и т. д. Это дает повторяемость и точность.

Все по плану: эмуляция отказов в CI-пайплайне

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

Автоматизация отказов встроена в CI/CD-процесс. Jenkins следит за изменениями в коде и запускает пайплайн: поднимается тестовый стенд, разворачивается нужная версия прошивки, запускаются тесты в Docker-контейнере. Тест взаимодействует с СХД через API и CLI, а по завершении собираются все логи и формируется Allure-отчет. Все это работает без участия человека: разработчику остается только открыть отчет и понять, что пошло не так.

В Allure для обозначения заголовка фикстуры необходимо проставить аннотацию allure.title.setup и allure.title.teardown:

import glamor as allure
import pytest


@pytest.fixture(scope=‘module', autouse=True)
@allure.title.setup(setup_title='Check FC ports are online on SPs before test’)
@allure.title.teardown(teardown_title='Check FC ports are online on SPs after test')
def check_fc_ports_are_online_on_nodes(data_port_name):
sps = Tatlin.main.SPs.values()

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

Пример Allure-отчета
Пример Allure-отчета

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

Вместо заключения

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

Такая система позволяет не просто выявлять баги, но понимать их природу, наблюдать за поведением компонентов во времени и контролировать стабильность на уровне всей архитектуры. Длительные, параметризуемые тесты, использование ExitStack, FIO, многослойная верификация и CI-пайплайны в Jenkins — все это формирует культуру системного качества, в которой автоматизация не заменяет инженера, а усиливает его.

Из моей практики можно сделать несколько выводов:

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

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

  • Хорошо структурированный код, диагностика по ходу теста и осмысленные отчеты важны не меньше самих тестов.

  • Воспроизводимость и прозрачность — основа инженерного доверия к результатам.

Рекомендации просты: не экономьте на инфраструктуре, автоматизируйте отказоустойчивость с самого начала, проектируйте систему так, чтобы вы всегда могли «заглянуть внутрь»: увидеть метрики, логи и поведение компонентов при сбоях и — самое главное — любите то, что делаете. Потому что надежные системы строятся не только на коде, но и на ответственности инженеров. 

Уже работаете в тестировании и хотите решать нетривиальные инженерные задачи? В команде разработки TATLIN.UNIFIED открыты позиции старшего инженера по автоматизированному тестированию и руководителя отдела тестирования. Также мы ищем ведущего инженера по методикам тестирования СХД. Все вакансии и подробности — на карьерном сайте YADRO.

Комментарии (3)


  1. adrozhzhov
    05.06.2025 18:20

    (как человек готовивший сервера к потере данных на СХД, так как у неё с ума контроллеры сходили, но дали сохранить настройки перед полной переинициализацией в декабре)

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


  1. arsmerk777
    05.06.2025 18:20

    это все здорово в контексте автотестов на python, вот только СХД коснулись совсем вскользь - fio rand read\write + долговременная нагрузка + симуляция отказа компонентов - это все что мне удалось узнать


    1. NatGry Автор
      05.06.2025 18:20

      Спасибо за Ваш комментарий. Дополнительные статьи про тестирование СХД можно найти на нашем портале "Истовый инженер":
      https://engineer.yadro.com/article/storage-bugs/

      https://engineer.yadro.com/article/storage-testing/