diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index 1745685..8b2e2e0 100644 --- a/test-tool/Makefile.am +++ b/test-tool/Makefile.am @@ -73,6 +73,7 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \ test_prout_reserve_simple.c \ test_prout_reserve_access.c \ test_prout_reserve_ownership.c \ + test_prout_clear_simple.c \ test_read6_simple.c \ test_read6_beyond_eol.c \ test_read10_simple.c \ @@ -215,7 +216,9 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \ test_writeverify16_flags.c \ test_writeverify16_dpo.c \ test_writeverify16_residuals.c \ - test_multipathio_simple.c + test_multipathio_simple.c \ + test_multipathio_reset.c \ + test_multipathio_compareandwrite.c endif diff --git a/test-tool/iscsi-multipath.c b/test-tool/iscsi-multipath.c index 98c4838..d691ed3 100644 --- a/test-tool/iscsi-multipath.c +++ b/test-tool/iscsi-multipath.c @@ -369,3 +369,19 @@ mpath_check_matching_ids(int num_sds, ret = mpath_check_matching_ids_serial_vpd(num_sds, sds); return ret; } + +int +mpath_count_iscsi(int num_sds, + struct scsi_device **sds) +{ + int i; + int found = 0; + + for (i = 0; i < num_sds; i++) { + if (sds[i]->iscsi_ctx != NULL) { + found++; + } + } + + return found; +} diff --git a/test-tool/iscsi-multipath.h b/test-tool/iscsi-multipath.h index 5090503..2fcd6ea 100644 --- a/test-tool/iscsi-multipath.h +++ b/test-tool/iscsi-multipath.h @@ -24,6 +24,13 @@ extern int mp_num_sds; extern struct scsi_device *mp_sds[MPATH_MAX_DEVS]; +int +mpath_check_matching_ids(int num_sds, + struct scsi_device **sds); +int +mpath_count_iscsi(int num_sds, + struct scsi_device **sds); + #define MPATH_SKIP_IF_UNAVAILABLE(_sds, _num_sds) \ do { \ if (_num_sds <= 1) { \ @@ -35,8 +42,15 @@ do { \ } \ } while (0); -int -mpath_check_matching_ids(int num_sds, - struct scsi_device **sds); +#define MPATH_SKIP_UNLESS_ISCSI(_sds, _num_sds) \ +do { \ + if (mpath_count_iscsi(_num_sds, _sds) != _num_sds) { \ + logging(LOG_NORMAL, "[SKIPPED] Non-iSCSI multipath." \ + " Skipping test"); \ + CU_PASS("[SKIPPED] Non-iSCSI multipath." \ + " Skipping test"); \ + return; \ + } \ +} while (0); #endif /* _ISCSI_MULTIPATH_H_ */ diff --git a/test-tool/iscsi-support.c b/test-tool/iscsi-support.c index 42e0e18..ce11612 100644 --- a/test-tool/iscsi-support.c +++ b/test-tool/iscsi-support.c @@ -973,6 +973,55 @@ prout_release(struct scsi_device *sdev, return ret; } +int +prout_clear(struct scsi_device *sdev, unsigned long long key) +{ + struct scsi_persistent_reserve_out_basic poc; + struct scsi_task *task; + int ret = 0; + + /* reserve the target using specified reservation type */ + logging(LOG_VERBOSE, + "Send PROUT/CLEAR to clear all registrations and any PR " + "reservation"); + + if (!data_loss) { + printf("--dataloss flag is not set in. Skipping PROUT\n"); + return -1; + } + + memset(&poc, 0, sizeof (poc)); + poc.reservation_key = key; + task = scsi_cdb_persistent_reserve_out( + SCSI_PERSISTENT_RESERVE_CLEAR, + SCSI_PERSISTENT_RESERVE_SCOPE_LU, + 0, &poc); + assert(task != NULL); + + task = send_scsi_command(sdev, task, NULL); + if (task == NULL) { + logging(LOG_NORMAL, + "[FAILED] Failed to send PROUT command: %s", + iscsi_get_error(sdev->iscsi_ctx)); + return -1; + } + if (status_is_invalid_opcode(task)) { + scsi_free_scsi_task(task); + logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented."); + return -2; + } + + if (task->status != SCSI_STATUS_GOOD) { + logging(LOG_NORMAL, + "[FAILED] PROUT command: failed with sense. %s", + iscsi_get_error(sdev->iscsi_ctx)); + ret = -1; + } + + scsi_free_scsi_task(task); + return ret; +} + int prin_verify_reserved_as(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type) diff --git a/test-tool/iscsi-support.h b/test-tool/iscsi-support.h index 1e55d70..4c43b14 100644 --- a/test-tool/iscsi-support.h +++ b/test-tool/iscsi-support.h @@ -273,6 +273,7 @@ int prout_reserve(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type); int prout_release(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type); +int prout_clear(struct scsi_device *sdev, unsigned long long key); int prin_verify_not_reserved(struct scsi_device *sdev); int prin_verify_reserved_as(struct scsi_device *sdev, unsigned long long key, enum scsi_persistent_out_type pr_type); diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index c290c95..4d950a7 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -192,6 +192,12 @@ static CU_TestInfo tests_prout_reserve[] = { CU_TEST_INFO_NULL }; +static CU_TestInfo tests_prout_clear[] = { + { (char *)"Simple", + test_prout_clear_simple }, + CU_TEST_INFO_NULL +}; + static CU_TestInfo tests_prin_serviceaction_range[] = { { (char *)"Range", test_prin_serviceaction_range }, CU_TEST_INFO_NULL @@ -445,6 +451,8 @@ static CU_TestInfo tests_writeverify16[] = { static CU_TestInfo tests_multipathio[] = { { (char *)"Simple", test_multipathio_simple }, + { (char *)"Reset", test_multipathio_reset }, + { (char *)"CompareAndWrite", test_multipathio_compareandwrite }, CU_TEST_INFO_NULL }; @@ -476,6 +484,7 @@ static libiscsi_suite_info scsi_suites[] = { { "PrinServiceactionRange", NON_PGR_FUNCS, tests_prin_serviceaction_range }, { "ProutRegister", NON_PGR_FUNCS, tests_prout_register }, { "ProutReserve", NON_PGR_FUNCS, tests_prout_reserve }, + { "ProutClear", NON_PGR_FUNCS, tests_prout_clear }, { "Read6", NON_PGR_FUNCS, tests_read6 }, { "Read10", NON_PGR_FUNCS, tests_read10 }, { "Read12", NON_PGR_FUNCS, tests_read12 }, @@ -561,6 +570,7 @@ static libiscsi_suite_info all_suites[] = { tests_prin_serviceaction_range }, { "ProutRegister", NON_PGR_FUNCS, tests_prout_register }, { "ProutReserve", NON_PGR_FUNCS, tests_prout_reserve }, + { "ProutClear", NON_PGR_FUNCS, tests_prout_clear }, { "Read6", NON_PGR_FUNCS, tests_read6 }, { "Read10", NON_PGR_FUNCS, tests_read10 }, { "Read12", NON_PGR_FUNCS, tests_read12 }, diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index 3550a93..fa9a0c0 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -129,6 +129,7 @@ void test_prout_reserve_ownership_earo(void); void test_prout_reserve_ownership_wero(void); void test_prout_reserve_ownership_eaar(void); void test_prout_reserve_ownership_wear(void); +void test_prout_clear_simple(void); void test_read6_simple(void); void test_read6_beyond_eol(void); @@ -301,5 +302,7 @@ void test_writeverify16_dpo(void); void test_writeverify16_residuals(void); void test_multipathio_simple(void); +void test_multipathio_reset(void); +void test_multipathio_compareandwrite(void); #endif /* _ISCSI_TEST_CU_H_ */ diff --git a/test-tool/test_multipathio_compareandwrite.c b/test-tool/test_multipathio_compareandwrite.c new file mode 100644 index 0000000..dee23cb --- /dev/null +++ b/test-tool/test_multipathio_compareandwrite.c @@ -0,0 +1,104 @@ +/* + Copyright (C) 2013 Ronnie Sahlberg + Copyright (C) 2015 David Disseldorp + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include +#include +#include + +#include + +#include "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" +#include "iscsi-multipath.h" + +void +test_multipathio_compareandwrite(void) +{ + int io_bl = 1; /* 1 block CAW IOs */ + int path; + int i, ret; + unsigned char *buf = alloca(2 * io_bl * block_size); + int maxbl; + + CHECK_FOR_DATALOSS; + CHECK_FOR_SBC; + MPATH_SKIP_IF_UNAVAILABLE(mp_sds, mp_num_sds); + + if (inq_bl) { + maxbl = inq_bl->max_cmp; + } else { + /* Assume we are not limited */ + maxbl = 256; + } + if (maxbl < io_bl) { + CU_PASS("[SKIPPED] MAXIMUM_COMPARE_AND_WRITE_LENGTH too small"); + return; + } + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Initialising data prior to COMPARE_AND_WRITE"); + + memset(buf, 0, io_bl * block_size); + ret = writesame10(mp_sds[0], 0, + block_size, 256, 0, 0, 0, 0, buf, + EXPECT_STATUS_GOOD); + if (ret == -2) { + CU_PASS("[SKIPPED] Target does not support WRITESAME10. Skipping test"); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, "Test multipath COMPARE_AND_WRITE"); + for (i = 0; i < 256; i++) { + + for (path = 0; path < mp_num_sds; path++) { + logging(LOG_VERBOSE, + "Test COMPARE_AND_WRITE(%d->%d) using path %d", + path, path + 1, path); + + /* compare data is first half */ + memset(buf, path, io_bl * block_size); + /* write data is the second half, wrap around */ + memset(buf + io_bl * block_size, path + 1, + io_bl * block_size); + + ret = compareandwrite(mp_sds[path], i, + buf, 2 * io_bl * block_size, + block_size, 0, 0, 0, 0, + EXPECT_STATUS_GOOD); + if (ret == -2) { + CU_PASS("[SKIPPED] Target does not support " + "COMPARE_AND_WRITE. Skipping test"); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, + "Test bad COMPARE_AND_WRITE(%d->%d)", + path, path + 1); + + ret = compareandwrite(mp_sds[path], i, + buf, 2 * io_bl * block_size, + block_size, 0, 0, 0, 0, + EXPECT_MISCOMPARE); + CU_ASSERT_EQUAL(ret, 0); + } + } +} diff --git a/test-tool/test_multipathio_reset.c b/test-tool/test_multipathio_reset.c new file mode 100644 index 0000000..e7b78ad --- /dev/null +++ b/test-tool/test_multipathio_reset.c @@ -0,0 +1,102 @@ +/* + Copyright (C) 2013 Ronnie Sahlberg + Copyright (C) 2015 David Disseldorp + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include +#include + +#include + +#include "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" +#include "iscsi-multipath.h" + +#define MPATH_MAX_TUR_RETRIES 5 + +static int +test_iscsi_tur_until_good(struct scsi_device *iscsi_sd, + int *num_uas) +{ + int num_turs; + + *num_uas = 0; + for (num_turs = 0; num_turs < MPATH_MAX_TUR_RETRIES; num_turs++) { + struct scsi_task *tsk; + tsk = iscsi_testunitready_sync(iscsi_sd->iscsi_ctx, + iscsi_sd->iscsi_lun); + if (tsk->status == SCSI_STATUS_GOOD) { + logging(LOG_VERBOSE, "TUR good after %d retries", + num_turs); + return 0; + } else if ((tsk->status == SCSI_STATUS_CHECK_CONDITION) + && (tsk->sense.key == SCSI_SENSE_UNIT_ATTENTION)) { + logging(LOG_VERBOSE, "Got UA for TUR"); + (*num_uas)++; + } else { + logging(LOG_NORMAL, "unexpected non-UA failure: %d,%d", + tsk->status, tsk->sense.key); + } + } + + return -ETIMEDOUT; +} + +void +test_multipathio_reset(void) +{ + int reset_path; + + CHECK_FOR_DATALOSS; + CHECK_FOR_SBC; + MPATH_SKIP_IF_UNAVAILABLE(mp_sds, mp_num_sds); + MPATH_SKIP_UNLESS_ISCSI(mp_sds, mp_num_sds); + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + + for (reset_path = 0; reset_path < mp_num_sds; reset_path++) { + int num_uas; + int ret; + int tur_path; + struct scsi_device *reset_sd = mp_sds[reset_path]; + + logging(LOG_VERBOSE, "Awaiting good TUR"); + ret = test_iscsi_tur_until_good(reset_sd, &num_uas); + CU_ASSERT_EQUAL(ret, 0); + + logging(LOG_VERBOSE, + "Test multipath LUN Reset using path %d", reset_path); + + ret = iscsi_task_mgmt_lun_reset_sync(reset_sd->iscsi_ctx, + reset_sd->iscsi_lun); + if (ret != 0) { + logging(LOG_NORMAL, "LUN reset failed. %s", + iscsi_get_error(reset_sd->iscsi_ctx)); + } + CU_ASSERT_EQUAL(ret, 0); + + /* check for and clear LU reset UA on all paths */ + for (tur_path = 0; tur_path < mp_num_sds; tur_path++) { + logging(LOG_VERBOSE, "check for LU reset unit " + "attention via TUR on path %d", tur_path); + ret = test_iscsi_tur_until_good(mp_sds[tur_path], &num_uas); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_NOT_EQUAL(num_uas, 0); + } + } +} diff --git a/test-tool/test_prout_clear_simple.c b/test-tool/test_prout_clear_simple.c new file mode 100644 index 0000000..25858f2 --- /dev/null +++ b/test-tool/test_prout_clear_simple.c @@ -0,0 +1,87 @@ +/* + Copyright (C) 2015 David Disseldorp + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include +#include + +#include + +#include "iscsi.h" +#include "scsi-lowlevel.h" +#include "iscsi-support.h" +#include "iscsi-test-cu.h" + +void +test_prout_clear_simple(void) +{ + int ret = 0; + uint32_t old_gen; + const unsigned long long key = rand_key(); + struct scsi_task *tsk; + struct scsi_persistent_reserve_in_read_keys *rk; + + CHECK_FOR_DATALOSS; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test Persistent Reserve OUT CLEAR works."); + + /* register our reservation key with the target */ + ret = prout_register_and_ignore(sd, key); + if (ret == -2) { + logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented."); + CU_PASS("PERSISTENT RESERVE OUT is not implemented."); + return; + } + CU_ASSERT_EQUAL(ret, 0); + + ret = prin_read_keys(sd, &tsk, &rk); + CU_ASSERT_EQUAL(ret, 0); + + CU_ASSERT_NOT_EQUAL(rk->num_keys, 0); + /* retain PR generation number to check for increments */ + old_gen = rk->prgeneration; + + scsi_free_scsi_task(tsk); + rk = NULL; /* freed with tsk */ + + /* reserve the target */ + ret = prout_reserve(sd, key, + SCSI_PERSISTENT_RESERVE_TYPE_EXCLUSIVE_ACCESS); + CU_ASSERT_EQUAL(ret, 0); + + /* verify target reservation */ + ret = prin_verify_reserved_as(sd, key, + SCSI_PERSISTENT_RESERVE_TYPE_EXCLUSIVE_ACCESS); + CU_ASSERT_EQUAL(ret, 0); + + /* clear reservation and registration */ + ret = prout_clear(sd, key); + CU_ASSERT_EQUAL(ret, 0); + + ret = prin_verify_not_reserved(sd); + CU_ASSERT_EQUAL(ret, 0); + + ret = prin_read_keys(sd, &tsk, &rk); + CU_ASSERT_EQUAL(ret, 0); + + CU_ASSERT_EQUAL(rk->num_keys, 0); + /* generation incremented once for CLEAR (not for RESERVE) */ + CU_ASSERT_EQUAL(rk->prgeneration, old_gen + 1); + + scsi_free_scsi_task(tsk); + rk = NULL; /* freed with tsk */ +}