| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging, time, traceback |
| from functools import partial |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server import autotest |
| from autotest_lib.server import hosts |
| from autotest_lib.server import test |
| |
| class hardware_StorageStress(test.test): |
| """ |
| Integrity stress test for storage device |
| """ |
| version = 1 |
| |
| # Define default value for the test case |
| _TEST_GAP = 60 # 1 min |
| _TEST_DURATION = 12 * 60 * 60 # 12 hours |
| _FIO_REQUIREMENT_FILE = '8k_async_randwrite' |
| _FIO_WRITE_FLAGS = [] |
| _FIO_VERIFY_FLAGS = ['--verifyonly'] |
| |
| def run_once(self, client_ip, gap=_TEST_GAP, duration=_TEST_DURATION, |
| power_command='reboot', storage_test_command='integrity', |
| storage_test_argument=''): |
| """ |
| Run the Storage stress test |
| Use hardwareStorageFio to run some test_command repeatedly for a long |
| time. Between each iteration of test command, run power command such as |
| reboot or suspend. |
| |
| @param client_ip: string of client's ip address (required) |
| @param gap: gap between each test (second) default = 1 min |
| @param duration: duration to run test (second) default = 12 hours |
| @param power_command: command to do between each test Command |
| possible command: reboot / suspend / nothing |
| @param storage_test_command: FIO command to run |
| - integrity: Check data integrity |
| - full_write: Check performance consistency |
| for full disk write. Use argument |
| to determine which disk to write |
| """ |
| |
| # init test |
| if not client_ip: |
| error.TestError("Must provide client's IP address to test") |
| |
| self._client = hosts.create_host(client_ip) |
| self._client_at = autotest.Autotest(self._client) |
| self._results = {} |
| |
| start_time = time.time() |
| |
| # parse power command |
| if power_command == 'nothing': |
| power_func = self._do_nothing |
| elif power_command == 'reboot': |
| power_func = self._do_reboot |
| elif power_command == 'suspend': |
| power_func = self._do_suspend |
| else: |
| raise error.TestFail( |
| 'Test failed with error: Invalid power command') |
| |
| # parse test command |
| if storage_test_command == 'integrity': |
| setup_func = self._write_data |
| loop_func = self._verify_data |
| elif storage_test_command == 'full_write': |
| setup_func = self._do_nothing |
| loop_func = partial(self._full_disk_write, |
| dev=storage_test_argument) |
| else: |
| raise error.TestFail('Test failed with error: Invalid test command') |
| |
| setup_func() |
| |
| # init statistic variable |
| min_time_per_loop = self._TEST_DURATION |
| max_time_per_loop = 0 |
| all_loop_time = 0 |
| avr_time_per_loop = 0 |
| self._loop_count = 0 |
| |
| while time.time() - start_time < duration: |
| # sleep |
| time.sleep(gap) |
| |
| self._loop_count += 1 |
| |
| # do power command & verify data & calculate time |
| loop_start_time = time.time() |
| power_func() |
| loop_func() |
| loop_time = time.time() - loop_start_time |
| |
| # update statistic |
| all_loop_time += loop_time |
| min_time_per_loop = min(loop_time, min_time_per_loop) |
| max_time_per_loop = max(loop_time, max_time_per_loop) |
| |
| if self._loop_count > 0: |
| avr_time_per_loop = all_loop_time / self._loop_count |
| |
| logging.info(str('check data count: %d' % self._loop_count)) |
| |
| # report result |
| self.write_perf_keyval({'loop_count':self._loop_count}) |
| self.write_perf_keyval({'min_time_per_loop':min_time_per_loop}) |
| self.write_perf_keyval({'max_time_per_loop':max_time_per_loop}) |
| self.write_perf_keyval({'avr_time_per_loop':avr_time_per_loop}) |
| |
| def _do_nothing(self): |
| pass |
| |
| def _do_reboot(self): |
| """ |
| Reboot host machine |
| """ |
| logging.info('Server: reboot client') |
| try: |
| self._client.reboot() |
| except error.AutoservRebootError as e: |
| raise error.TestFail('%s.\nTest failed with error %s' % ( |
| traceback.format_exc(), str(e))) |
| |
| def _do_suspend(self): |
| """ |
| Suspend host machine |
| """ |
| logging.info('Server: suspend client') |
| self._client_at.run_test('power_Resume') |
| passed = self._check_client_test_result(self._client) |
| if not passed: |
| raise error.TestFail('Test failed with error: Suspend Error') |
| |
| |
| def _check_client_test_result(self, client): |
| """ |
| Check result of the client test. |
| Auto test will store results in the file named status. |
| We check that the second to last line in that file begin with 'END GOOD' |
| |
| @ return True if last test passed, False otherwise. |
| """ |
| client_result_dir = '%s/results/default' % client.autodir |
| command = 'tail -2 %s/status | head -1' % client_result_dir |
| status = client.run(command).stdout.strip() |
| logging.info(status) |
| return status[:8] == 'END GOOD' |
| |
| def _write_data(self): |
| """ |
| Write test data to host using hardware_StorageFio |
| """ |
| logging.info('_write_data') |
| result_dir = 'hardware_StorageFio_write' |
| self._client_at.run_test('hardware_StorageFio', wait=0, |
| results_dir=result_dir, |
| requirements=[(self._FIO_REQUIREMENT_FILE, |
| self._FIO_WRITE_FLAGS)]) |
| passed = self._check_client_test_result(self._client) |
| if not passed: |
| raise error.TestFail('Test failed with error: Data Write Error') |
| |
| def _verify_data(self): |
| """ |
| Vertify test data using hardware_StorageFio |
| """ |
| logging.info(str('_verify_data #%d' % self._loop_count)) |
| result_dir = str('hardware_StorageFio_verify_%d' |
| % self._loop_count) |
| self._client_at.run_test('hardware_StorageFio', wait=0, |
| results_dir=result_dir, |
| requirements=[(self._FIO_REQUIREMENT_FILE, |
| self._FIO_VERIFY_FLAGS)]) |
| passed = self._check_client_test_result(self._client) |
| if not passed: |
| raise error.TestFail('Test failed with error: Data Verify #%d Error' |
| % self._loop_count) |
| |
| def _full_disk_write(self, dev): |
| """ |
| Do the root device full area write and report performance |
| """ |
| logging.info(str('_full_disk_write #%d' % self._loop_count)) |
| logging.info(str('target device "%s"' % dev)) |
| |
| # check sanity of target device: begin with /dev/ and can find with ls |
| if dev[0:5] != '/dev/': |
| raise error.TestFail( |
| 'Test failed with error: device should begin with /dev/') |
| |
| # This command return 0 when device exist, return 2 otherwise |
| cmd = 'ls %s >/dev/null 2>&1' % dev |
| if self._client.run(cmd, ignore_status=True).exit_status: |
| raise error.TestFail( |
| 'Test failed with error: device does not exist') |
| |
| # log some hardware status in the first run |
| if self._loop_count == 1: |
| cmd = 'cat /sys/class/block/%s/device/type' % dev[5:] |
| type = self._client.run(cmd).stdout.strip() |
| if type == '0': #scsi disk |
| cmd = 'smartctl -x %s' % dev |
| logging.info(self._client.run(cmd, ignore_status=True).stdout) |
| elif type == 'MMC': |
| for field in ['cid', 'csd', 'name', 'serial']: |
| cmd = 'cat /sys/block/%s/device/%s' % (dev[5:], field) |
| result = self._client.run(cmd).stdout.strip() |
| logging.info(str('%s: %s' % (field, result))) |
| else: |
| raise error.TestFail('Unknown device type') |
| |
| # determine current boot device |
| cur_dev = self._client.run('rootdev -s -d').stdout.strip() |
| logging.info(str('current boot device "%s"' % cur_dev)) |
| |
| if dev == cur_dev: |
| raise error.TestFail( |
| 'Test failed with error: can not test boot device') |
| |
| result_dir = str('hardware_StorageFio_full_disk_write_%d' |
| % self._loop_count) |
| self._client_at.run_test('hardware_StorageFio', dev=dev, filesize=0, |
| results_dir=result_dir, |
| requirements=[('64k_stress', [])]) |
| |
| passed = self._check_client_test_result(self._client) |
| if not passed: |
| raise error.TestFail( |
| "Test failed with error: Full disk Write #%d Error" |
| % self._loop_count) |