From 6b3ee9931a78b304c1d5e0d9a4e8824aed485715 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Thu, 17 Sep 2015 18:03:41 +0200 Subject: [PATCH 1/5] test/multipath: add helper to check that all paths are iSCSI MPATH_SKIP_UNLESS_ISCSI(_sds, _num_sds) iterates over all _sds, and skips the test unless all paths are iSCSI based. Signed-off-by: David Disseldorp --- test-tool/iscsi-multipath.c | 16 ++++++++++++++++ test-tool/iscsi-multipath.h | 20 +++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) 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_ */ From b9dd210194b027b5f6e940c5a34b8013274bbd58 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Wed, 3 Jun 2015 16:47:36 +0200 Subject: [PATCH 2/5] test/multipath: add logical unit reset test This test issues a logical unit reset iSCSI TMF request, and then confirms that all paths report a subsequent unit attention condition. Signed-off-by: David Disseldorp --- test-tool/Makefile.am | 3 +- test-tool/iscsi-test-cu.c | 1 + test-tool/iscsi-test-cu.h | 1 + test-tool/test_multipathio_reset.c | 102 +++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 test-tool/test_multipathio_reset.c diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index 1745685..fceda37 100644 --- a/test-tool/Makefile.am +++ b/test-tool/Makefile.am @@ -215,7 +215,8 @@ 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 endif diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index c290c95..d3dcbf6 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -445,6 +445,7 @@ static CU_TestInfo tests_writeverify16[] = { static CU_TestInfo tests_multipathio[] = { { (char *)"Simple", test_multipathio_simple }, + { (char *)"Reset", test_multipathio_reset }, CU_TEST_INFO_NULL }; diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index 3550a93..eb13703 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -301,5 +301,6 @@ void test_writeverify16_dpo(void); void test_writeverify16_residuals(void); void test_multipathio_simple(void); +void test_multipathio_reset(void); #endif /* _ISCSI_TEST_CU_H_ */ 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); + } + } +} From a569d55a0eed4d4d1082800919e0131c2b19f1de Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Fri, 24 Jul 2015 18:59:06 +0200 Subject: [PATCH 3/5] test/multipath: add COMPARE AND WRITE test Confirm that data is consistent across paths when using the atomic COMPARE AND WRITE operation. This should be extended in future to issue the requests simultaneously across both paths, in an attempt to trigger a read/write race. Signed-off-by: David Disseldorp --- test-tool/Makefile.am | 3 +- test-tool/iscsi-test-cu.c | 1 + test-tool/iscsi-test-cu.h | 1 + test-tool/test_multipathio_compareandwrite.c | 104 +++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 test-tool/test_multipathio_compareandwrite.c diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index fceda37..8c702cd 100644 --- a/test-tool/Makefile.am +++ b/test-tool/Makefile.am @@ -216,7 +216,8 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \ test_writeverify16_dpo.c \ test_writeverify16_residuals.c \ test_multipathio_simple.c \ - test_multipathio_reset.c + test_multipathio_reset.c \ + test_multipathio_compareandwrite.c endif diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index d3dcbf6..c155390 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -446,6 +446,7 @@ 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 }; diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index eb13703..9dda8b5 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -302,5 +302,6 @@ 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); + } + } +} From a90e5a3d4cef44db92760f4d5eff0ccb8a24b561 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Fri, 18 Sep 2015 17:58:42 +0200 Subject: [PATCH 4/5] test/iscsi-support: PR Out CLEAR helper function Add a helper function to dispatch Persistent Reserve Out requests with CLEAR service action. Signed-off-by: David Disseldorp --- test-tool/iscsi-support.c | 49 +++++++++++++++++++++++++++++++++++++++ test-tool/iscsi-support.h | 1 + 2 files changed, 50 insertions(+) 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); From 5c0b1b9913159c181d3df3ff144272c3aa629932 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Fri, 18 Sep 2015 18:00:51 +0200 Subject: [PATCH 5/5] test/pr: simple PR Out CLEAR test Check that a Persistent Reserve Out CLEAR request: - clears an existing reservation - clears existing registration keys - bumps the PRgeneration number Signed-off-by: David Disseldorp --- test-tool/Makefile.am | 1 + test-tool/iscsi-test-cu.c | 8 +++ test-tool/iscsi-test-cu.h | 1 + test-tool/test_prout_clear_simple.c | 87 +++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 test-tool/test_prout_clear_simple.c diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index 8c702cd..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 \ diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index c155390..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 @@ -478,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 }, @@ -563,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 9dda8b5..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); 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 */ +}