diff --git a/block/iscsi.c b/block/iscsi.c index 49cee4dda99b04b66d0348193c5467d6f5676187..a6b8fe295b749152ce8dd3222a75cd6468a44b3c 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -70,6 +70,7 @@ typedef struct IscsiLun { bool dpofua; bool has_write_same; bool force_next_flush; + bool request_timed_out; } IscsiLun; typedef struct IscsiTask { @@ -100,7 +101,8 @@ typedef struct IscsiAIOCB { #endif } IscsiAIOCB; -#define EVENT_INTERVAL 250 +/* libiscsi uses time_t so its enough to process events every second */ +#define EVENT_INTERVAL 1000 #define NOP_INTERVAL 5000 #define MAX_NOP_FAILURES 3 #define ISCSI_CMD_RETRIES ARRAY_SIZE(iscsi_retry_times) @@ -187,13 +189,18 @@ iscsi_co_generic_cb(struct iscsi_context *iscsi, int status, iTask->do_retry = 1; goto out; } - /* status 0x28 is SCSI_TASK_SET_FULL. It was first introduced - * in libiscsi 1.10.0. Hardcode this value here to avoid - * the need to bump the libiscsi requirement to 1.10.0 */ - if (status == SCSI_STATUS_BUSY || status == 0x28) { + if (status == SCSI_STATUS_BUSY || status == SCSI_STATUS_TIMEOUT || + status == SCSI_STATUS_TASK_SET_FULL) { unsigned retry_time = exp_random(iscsi_retry_times[iTask->retries - 1]); - error_report("iSCSI Busy/TaskSetFull (retry #%u in %u ms): %s", + if (status == SCSI_STATUS_TIMEOUT) { + /* make sure the request is rescheduled AFTER the + * reconnect is initiated */ + retry_time = EVENT_INTERVAL * 2; + iTask->iscsilun->request_timed_out = true; + } + error_report("iSCSI Busy/TaskSetFull/TimeOut" + " (retry #%u in %u ms): %s", iTask->retries, retry_time, iscsi_get_error(iscsi)); aio_timer_init(iTask->iscsilun->aio_context, @@ -277,20 +284,26 @@ iscsi_set_events(IscsiLun *iscsilun) iscsilun); iscsilun->events = ev; } - - /* newer versions of libiscsi may return zero events. In this - * case start a timer to ensure we are able to return to service - * once this situation changes. */ - if (!ev) { - timer_mod(iscsilun->event_timer, - qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + EVENT_INTERVAL); - } } -static void iscsi_timed_set_events(void *opaque) +static void iscsi_timed_check_events(void *opaque) { IscsiLun *iscsilun = opaque; + + /* check for timed out requests */ + iscsi_service(iscsilun->iscsi, 0); + + if (iscsilun->request_timed_out) { + iscsilun->request_timed_out = false; + iscsi_reconnect(iscsilun->iscsi); + } + + /* newer versions of libiscsi may return zero events. Ensure we are able + * to return to service once this situation changes. */ iscsi_set_events(iscsilun); + + timer_mod(iscsilun->event_timer, + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + EVENT_INTERVAL); } static void @@ -1093,16 +1106,37 @@ static char *parse_initiator_name(const char *target) return iscsi_name; } +static int parse_timeout(const char *target) +{ + QemuOptsList *list; + QemuOpts *opts; + const char *timeout; + + list = qemu_find_opts("iscsi"); + if (list) { + opts = qemu_opts_find(list, target); + if (!opts) { + opts = QTAILQ_FIRST(&list->head); + } + if (opts) { + timeout = qemu_opt_get(opts, "timeout"); + if (timeout) { + return atoi(timeout); + } + } + } + + return 0; +} + static void iscsi_nop_timed_event(void *opaque) { IscsiLun *iscsilun = opaque; - if (iscsi_get_nops_in_flight(iscsilun->iscsi) > MAX_NOP_FAILURES) { + if (iscsi_get_nops_in_flight(iscsilun->iscsi) >= MAX_NOP_FAILURES) { error_report("iSCSI: NOP timeout. Reconnecting..."); - iscsi_reconnect(iscsilun->iscsi); - } - - if (iscsi_nop_out_async(iscsilun->iscsi, NULL, NULL, 0, NULL) != 0) { + iscsilun->request_timed_out = true; + } else if (iscsi_nop_out_async(iscsilun->iscsi, NULL, NULL, 0, NULL) != 0) { error_report("iSCSI: failed to sent NOP-Out. Disabling NOP messages."); return; } @@ -1260,10 +1294,13 @@ static void iscsi_attach_aio_context(BlockDriverState *bs, timer_mod(iscsilun->nop_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + NOP_INTERVAL); - /* Prepare a timer for a delayed call to iscsi_set_events */ + /* Set up a timer for periodic calls to iscsi_set_events and to + * scan for command timeout */ iscsilun->event_timer = aio_timer_new(iscsilun->aio_context, QEMU_CLOCK_REALTIME, SCALE_MS, - iscsi_timed_set_events, iscsilun); + iscsi_timed_check_events, iscsilun); + timer_mod(iscsilun->event_timer, + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + EVENT_INTERVAL); } static void iscsi_modesense_sync(IscsiLun *iscsilun) @@ -1388,6 +1425,8 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, goto out; } + iscsi_set_timeout(iscsi, parse_timeout(iscsi_url->target)); + if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) { error_setg(errp, "iSCSI: Failed to connect to LUN : %s", iscsi_get_error(iscsi)); @@ -1736,6 +1775,10 @@ static QemuOptsList qemu_iscsi_opts = { .name = "initiator-name", .type = QEMU_OPT_STRING, .help = "Initiator iqn name to use when connecting", + },{ + .name = "timeout", + .type = QEMU_OPT_NUMBER, + .help = "Request timeout in seconds (default 0 = no timeout)", }, { /* end of list */ } }, diff --git a/configure b/configure index 3063739a5eccef9da17b395124578c00f603b0f6..08f9a2297293194309e80762653c4cc3b6096d6c 100755 --- a/configure +++ b/configure @@ -3618,15 +3618,15 @@ if compile_prog "" "" ; then fi ########################################## -# Do we have libiscsi >= 1.9.0 +# Do we have libiscsi >= 1.10.0 if test "$libiscsi" != "no" ; then - if $pkg_config --atleast-version=1.9.0 libiscsi; then + if $pkg_config --atleast-version=1.10.0 libiscsi; then libiscsi="yes" libiscsi_cflags=$($pkg_config --cflags libiscsi) libiscsi_libs=$($pkg_config --libs libiscsi) else if test "$libiscsi" = "yes" ; then - feature_not_found "libiscsi" "Install libiscsi >= 1.9.0" + feature_not_found "libiscsi" "Install libiscsi >= 1.10.0" fi libiscsi="no" fi diff --git a/qemu-options.hx b/qemu-options.hx index e6e38959181952dce1d63063e4f0940838b99ec0..0bdd59f20b9aa3a9251c27ee7550f8f7a42a5479 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2294,6 +2294,9 @@ By default qemu will use the iSCSI initiator-name 'iqn.2008-11.org.linux-kvm[:]' but this can also be set from the command line or a configuration file. +Since version Qemu 2.4 it is possible to specify a iSCSI request timeout to detect +stalled requests and force a reestablishment of the session. The timeout +is specified in seconds. The default is 0 which means no timeout. Example (without authentication): @example @@ -2321,6 +2324,7 @@ DEF("iscsi", HAS_ARG, QEMU_OPTION_iscsi, "-iscsi [user=user][,password=password]\n" " [,header-digest=CRC32C|CR32C-NONE|NONE-CRC32C|NONE\n" " [,initiator-name=initiator-iqn][,id=target-iqn]\n" + " [,timeout=timeout]\n" " iSCSI session parameters\n", QEMU_ARCH_ALL) STEXI