Add an 'alua' path checker that uses RTPG to check path state, and an 'alua_cached' prioritizer that uses the checker's cached ALUA state.
The checker maps ALUA Asymmetric Access States to path states: - AAS_OPTIMIZED, AAS_NON_OPTIMIZED -> PATH_UP - AAS_STANDBY -> PATH_GHOST - AAS_TRANSITIONING -> PATH_PENDING - AAS_UNAVAILABLE, AAS_OFFLINE -> PATH_DOWN The alua_cached prioritizer first checks for cached state from the alua checker, then falls back to sysfs, then to issuing RTPG directly. This maintains backward compatibility with other checker configurations. When detect_checker is enabled, the alua checker is auto-selected for devices with TPGS support. When the alua checker is in use, detect_prio auto-selects the alua_cached prioritizer. Signed-off-by: Brian Bunker <[email protected]> --- libmultipath/Makefile | 5 + libmultipath/checkers.c | 1 + libmultipath/checkers.h | 1 + libmultipath/checkers/Makefile | 3 +- libmultipath/checkers/alua.c | 426 ++++++++++++++++++++++++ libmultipath/checkers/alua.h | 15 + libmultipath/prio.c | 1 + libmultipath/prio.h | 1 + libmultipath/prioritizers/Makefile | 1 + libmultipath/prioritizers/alua_cached.c | 224 +++++++++++++ libmultipath/prioritizers/alua_rtpg.c | 12 +- libmultipath/prioritizers/alua_rtpg.h | 1 + libmultipath/prioritizers/sysfs.c | 7 +- libmultipath/propsel.c | 20 +- libmultipath/structs.c | 2 + libmultipath/structs.h | 3 + 16 files changed, 716 insertions(+), 7 deletions(-) create mode 100644 libmultipath/checkers/alua.c create mode 100644 libmultipath/checkers/alua.h create mode 100644 libmultipath/prioritizers/alua_cached.c diff --git a/libmultipath/Makefile b/libmultipath/Makefile index 85767ab4..379f4d02 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -38,6 +38,11 @@ nvme-lib.o: nvme-lib.c nvme-ioctl.c nvme-ioctl.h dict.o: dict.c $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -Wno-unused-parameter -c -o $@ $< +# checkers/alua.o needs to find headers in parent directory +checkers/alua.o: checkers/alua.c + @echo building $@ because of $? + $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -I. -c -o $@ $< + make_static = $(shell sed '/^static/!s/^\([a-z]\{1,\} \)/static \1/' <$1 >$2) nvme-ioctl.c: nvme/nvme-ioctl.c diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c index bb6ad1ee..21aa52d4 100644 --- a/libmultipath/checkers.c +++ b/libmultipath/checkers.c @@ -461,6 +461,7 @@ int init_checkers(void) EMC_CLARIION, READSECTOR0, CCISS_TUR, + ALUA_RTPG, }; unsigned int i; diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h index a969e7d1..2905f3fc 100644 --- a/libmultipath/checkers.h +++ b/libmultipath/checkers.h @@ -98,6 +98,7 @@ enum path_check_state { #define EMC_CLARIION "emc_clariion" #define READSECTOR0 "readsector0" #define CCISS_TUR "cciss_tur" +#define ALUA "alua" #define NONE "none" #define INVALID "invalid" diff --git a/libmultipath/checkers/Makefile b/libmultipath/checkers/Makefile index 6f7cfb95..dfdd13a1 100644 --- a/libmultipath/checkers/Makefile +++ b/libmultipath/checkers/Makefile @@ -17,7 +17,8 @@ LIBS= \ libcheckdirectio.so \ libcheckemc_clariion.so \ libcheckhp_sw.so \ - libcheckrdac.so + libcheckrdac.so \ + libcheckalua.so all: $(LIBS) diff --git a/libmultipath/checkers/alua.c b/libmultipath/checkers/alua.c new file mode 100644 index 00000000..cb3d7000 --- /dev/null +++ b/libmultipath/checkers/alua.c @@ -0,0 +1,426 @@ +/* + * ALUA Path Checker + * + * Copyright (c) 2024 + * This file is released under the GPL. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <pthread.h> +#include <urcu.h> +#include <urcu/uatomic.h> + +#include "checkers.h" +#include "debug.h" +#include "structs.h" +#include "prio.h" +#include "util.h" +#include "time-util.h" +#include "../prioritizers/alua.h" +#include "../prioritizers/alua_rtpg.h" +#include "alua.h" + +#define MAX_NR_TIMEOUTS 1 + +enum { + MSG_ALUA_RTPG_NOT_SUPPORTED = CHECKER_FIRST_MSGID, + MSG_ALUA_RTPG_TRANSITIONING, + MSG_ALUA_RUNNING, + MSG_ALUA_TIMEOUT, +}; + +#define IDX_(x) (MSG_ ## x - CHECKER_FIRST_MSGID) +const char *libcheck_msgtable[] = { + [IDX_(ALUA_RTPG_NOT_SUPPORTED)] = " ALUA not supported", + [IDX_(ALUA_RTPG_TRANSITIONING)] = " path is transitioning", + [IDX_(ALUA_RUNNING)] = " still running", + [IDX_(ALUA_TIMEOUT)] = " timed out", + NULL, +}; + +struct alua_checker_context { + dev_t devt; + int state; + int running; /* uatomic access only */ + int fd; + unsigned int timeout; + time_t time; + pthread_t thread; + pthread_mutex_t lock; + pthread_cond_t active; + int holders; /* uatomic access only */ + int msgid; + struct checker_context ctx; + unsigned int nr_timeouts; + bool checked_state; + /* ALUA-specific cached data for prioritizer */ + int aas; /* Asymmetric Access State */ + int tpg; /* Target Port Group */ + time_t timestamp; /* When this data was collected */ + /* Data needed for RTPG in thread */ + struct path *pp; /* Path pointer - only valid during check */ +}; + +int libcheck_init(struct checker *c) +{ + struct alua_checker_context *ct; + struct stat sb; + + ct = malloc(sizeof(struct alua_checker_context)); + if (!ct) + return 1; + memset(ct, 0, sizeof(struct alua_checker_context)); + + ct->state = PATH_UNCHECKED; + ct->fd = -1; + ct->aas = -1; + ct->tpg = -1; + uatomic_set(&ct->holders, 1); + pthread_cond_init_mono(&ct->active); + pthread_mutex_init(&ct->lock, NULL); + if (fstat(c->fd, &sb) == 0) + ct->devt = sb.st_rdev; + ct->ctx.cls = c->cls; + c->context = ct; + + return 0; +} + +static void cleanup_context(struct alua_checker_context *ct) +{ + pthread_mutex_destroy(&ct->lock); + pthread_cond_destroy(&ct->active); + free(ct); +} + +void libcheck_free(struct checker *c) +{ + if (c->context) { + struct alua_checker_context *ct = c->context; + int holders; + int running; + + running = uatomic_xchg(&ct->running, 0); + if (running) + pthread_cancel(ct->thread); + ct->thread = 0; + holders = uatomic_sub_return(&ct->holders, 1); + if (!holders) + cleanup_context(ct); + c->context = NULL; + } + return; +} + +/* + * Map ALUA Asymmetric Access State to path state + */ +static int alua_state_to_path_state(int aas, short *msgid) +{ + switch (aas & 0x0f) { + case AAS_OPTIMIZED: + case AAS_NON_OPTIMIZED: + *msgid = CHECKER_MSGID_UP; + return PATH_UP; + case AAS_STANDBY: + *msgid = CHECKER_MSGID_GHOST; + return PATH_GHOST; + case AAS_TRANSITIONING: + *msgid = MSG_ALUA_RTPG_TRANSITIONING; + return PATH_PENDING; + case AAS_UNAVAILABLE: + case AAS_OFFLINE: + default: + *msgid = CHECKER_MSGID_DOWN; + return PATH_DOWN; + } +} + +/* + * Main ALUA check function - uses alua_rtpg.c library. + * This is called either in sync mode or from the async thread. + */ +static int +alua_check(struct path *pp, int *aas_out, short *msgid) +{ + int aas; + + if (pp->tpg_id == GROUP_ID_UNDEF) { + *msgid = CHECKER_MSGID_DOWN; + return PATH_DOWN; + } + + aas = get_asymmetric_access_state(pp, pp->tpg_id); + if (aas < 0) { + if (aas == -RTPG_LUN_DISCONNECTED) { + *msgid = CHECKER_MSGID_DISCONNECTED; + return PATH_DISCONNECTED; + } + *msgid = CHECKER_MSGID_DOWN; + return PATH_DOWN; + } + + *aas_out = aas; + return alua_state_to_path_state(aas, msgid); +} + +#define alua_thread_cleanup_push(ct) pthread_cleanup_push(cleanup_func, ct) +#define alua_thread_cleanup_pop(ct) pthread_cleanup_pop(1) + +static void cleanup_func(void *data) +{ + int holders; + struct alua_checker_context *ct = data; + + holders = uatomic_sub_return(&ct->holders, 1); + if (!holders) + cleanup_context(ct); +} + +void *libcheck_thread(struct checker_context *ctx) +{ + struct alua_checker_context *ct = + container_of(ctx, struct alua_checker_context, ctx); + int state, running; + short msgid; + int aas; + + /* This thread can be canceled, so setup clean up */ + alua_thread_cleanup_push(ct); + + condlog(4, "%d:%d : alua checker starting up", major(ct->devt), + minor(ct->devt)); + + state = alua_check(ct->pp, &aas, &msgid); + pthread_testcancel(); + + /* ALUA checker done */ + pthread_mutex_lock(&ct->lock); + ct->state = state; + ct->msgid = msgid; + ct->aas = aas; + ct->tpg = ct->pp->tpg_id; + ct->timestamp = time(NULL); + /* Update path-level cache for prioritizer use */ + ct->pp->alua_state = aas; + ct->pp->alua_timestamp = ct->timestamp; + pthread_cond_signal(&ct->active); + pthread_mutex_unlock(&ct->lock); + + condlog(4, "%d:%d : alua checker finished, state %s", major(ct->devt), + minor(ct->devt), checker_state_name(state)); + + running = uatomic_xchg(&ct->running, 0); + if (!running) + pause(); + + alua_thread_cleanup_pop(ct); + + return ((void *)0); +} + +static void alua_set_async_timeout(struct checker *c) +{ + struct alua_checker_context *ct = c->context; + struct timespec now; + + get_monotonic_time(&now); + ct->time = now.tv_sec + c->timeout; +} + +static int alua_check_async_timeout(struct checker *c) +{ + struct alua_checker_context *ct = c->context; + struct timespec now; + + get_monotonic_time(&now); + return (now.tv_sec > ct->time); +} + +static int check_pending(struct checker *c) +{ + struct alua_checker_context *ct = c->context; + int alua_status = PATH_PENDING; + + pthread_mutex_lock(&ct->lock); + if (ct->state != PATH_PENDING || ct->msgid != MSG_ALUA_RUNNING) { + alua_status = ct->state; + c->msgid = ct->msgid; + } + pthread_mutex_unlock(&ct->lock); + if (alua_status == PATH_PENDING && c->msgid == MSG_ALUA_RUNNING) { + condlog(4, "%d:%d : alua checker still running", + major(ct->devt), minor(ct->devt)); + } else { + int running = uatomic_xchg(&ct->running, 0); + if (running) + pthread_cancel(ct->thread); + ct->thread = 0; + } + + ct->checked_state = true; + return alua_status; +} + +bool libcheck_need_wait(struct checker *c) +{ + struct alua_checker_context *ct = c->context; + return (ct && ct->thread && uatomic_read(&ct->running) != 0 && + !ct->checked_state); +} + +int libcheck_pending(struct checker *c) +{ + struct alua_checker_context *ct = c->context; + + /* The if path checker isn't running, just return the exiting value. */ + if (!ct || !ct->thread) + return c->path_state; + + return check_pending(c); +} + +int libcheck_check(struct checker *c) +{ + struct alua_checker_context *ct = c->context; + struct path *pp = container_of(c, struct path, checker); + pthread_attr_t attr; + int alua_status = PATH_PENDING, r; + int aas; + + if (!ct) + return PATH_UNCHECKED; + + if (checker_is_sync(c)) { + alua_status = alua_check(pp, &aas, &c->msgid); + ct->tpg = pp->tpg_id; + ct->aas = aas; + ct->timestamp = time(NULL); + /* Update path-level cache for prioritizer use */ + pp->alua_state = aas; + pp->alua_timestamp = ct->timestamp; + return alua_status; + } + + /* Async mode */ + if (ct->thread) { + ct->checked_state = true; + if (alua_check_async_timeout(c)) { + int running = uatomic_xchg(&ct->running, 0); + if (running) { + pthread_cancel(ct->thread); + condlog(3, "%d:%d : alua checker timeout", + major(ct->devt), minor(ct->devt)); + c->msgid = MSG_ALUA_TIMEOUT; + alua_status = PATH_TIMEOUT; + } else { + pthread_mutex_lock(&ct->lock); + alua_status = ct->state; + c->msgid = ct->msgid; + pthread_mutex_unlock(&ct->lock); + } + ct->thread = 0; + } else if (uatomic_read(&ct->running) != 0) { + /* + * ct->running may be stale - the thread sets + * msgid/state under lock, then clears running + * after releasing it. Check msgid under lock + * to get the truth. Only MSG_ALUA_RUNNING means + * no result yet. + */ + pthread_mutex_lock(&ct->lock); + if (ct->msgid != MSG_ALUA_RUNNING) { + alua_status = ct->state; + c->msgid = ct->msgid; + ct->thread = 0; + } + pthread_mutex_unlock(&ct->lock); + } else { + /* ALUA checker done */ + ct->thread = 0; + pthread_mutex_lock(&ct->lock); + alua_status = ct->state; + c->msgid = ct->msgid; + pthread_mutex_unlock(&ct->lock); + } + } else { + if (uatomic_read(&ct->holders) > 1) { + /* The thread has been cancelled but hasn't quit. */ + if (ct->nr_timeouts <= MAX_NR_TIMEOUTS) { + if (ct->nr_timeouts == MAX_NR_TIMEOUTS) { + condlog(2, "%d:%d : waiting for stalled alua thread to finish", + major(ct->devt), minor(ct->devt)); + } + ct->nr_timeouts++; + c->msgid = MSG_ALUA_TIMEOUT; + return PATH_TIMEOUT; + } + ct->nr_timeouts++; + /* + * Start a new thread while the old one is stalled. + * We have to prevent it from interfering with the new + * thread. We create a new context and leave the old + * one with the stale thread, hoping it will clean up + * eventually. + */ + condlog(3, "%d:%d : alua thread not responding", + major(ct->devt), minor(ct->devt)); + + if (libcheck_init(c) != 0) { + c->msgid = CHECKER_MSGID_DOWN; + return PATH_UNCHECKED; + } + ((struct alua_checker_context *)c->context)->nr_timeouts = ct->nr_timeouts; + + if (!uatomic_sub_return(&ct->holders, 1)) { + /* It did terminate, eventually */ + cleanup_context(ct); + ((struct alua_checker_context *)c->context)->nr_timeouts = 0; + } + + ct = c->context; + } else + ct->nr_timeouts = 0; + + /* Start new ALUA checker */ + pthread_mutex_lock(&ct->lock); + alua_status = ct->state = PATH_PENDING; + c->msgid = ct->msgid = MSG_ALUA_RUNNING; + pthread_mutex_unlock(&ct->lock); + ct->fd = c->fd; + ct->timeout = c->timeout; + ct->pp = pp; + ct->checked_state = false; + uatomic_add(&ct->holders, 1); + uatomic_set(&ct->running, 1); + alua_set_async_timeout(c); + setup_thread_attr(&attr, 32 * 1024, 1); + r = start_checker_thread(&ct->thread, &attr, &ct->ctx); + pthread_attr_destroy(&attr); + if (r) { + uatomic_sub(&ct->holders, 1); + uatomic_set(&ct->running, 0); + ct->thread = 0; + condlog(3, "%d:%d : failed to start alua thread, using" + " sync mode", major(ct->devt), minor(ct->devt)); + alua_status = alua_check(pp, &aas, &c->msgid); + ct->tpg = pp->tpg_id; + ct->aas = aas; + ct->timestamp = time(NULL); + /* Update path-level cache for prioritizer use */ + pp->alua_state = aas; + pp->alua_timestamp = ct->timestamp; + return alua_status; + } + } + + return alua_status; +} + diff --git a/libmultipath/checkers/alua.h b/libmultipath/checkers/alua.h new file mode 100644 index 00000000..b53cef66 --- /dev/null +++ b/libmultipath/checkers/alua.h @@ -0,0 +1,15 @@ +/* + * ALUA Path Checker Header + * + * This file is released under the GPL. + */ +#ifndef _ALUA_CHECKER_H +#define _ALUA_CHECKER_H + +/* ALUA checker caches state in struct path for prioritizer use: + * pp->alua_state - cached AAS value (-1 if not cached) + * pp->alua_timestamp - when the state was last updated + */ + +#endif /* _ALUA_CHECKER_H */ + diff --git a/libmultipath/prio.c b/libmultipath/prio.c index 24f825bd..2d59d04e 100644 --- a/libmultipath/prio.c +++ b/libmultipath/prio.c @@ -29,6 +29,7 @@ int init_prio(void) #ifdef LOAD_ALL_SHARED_LIBS static const char *const all_prios[] = { PRIO_ALUA, + PRIO_ALUA_CACHED, PRIO_CONST, PRIO_DATACORE, PRIO_EMC, diff --git a/libmultipath/prio.h b/libmultipath/prio.h index 119b75f2..b54ece0d 100644 --- a/libmultipath/prio.h +++ b/libmultipath/prio.h @@ -17,6 +17,7 @@ struct path; * Known prioritizers for use in hwtable.c */ #define PRIO_ALUA "alua" +#define PRIO_ALUA_CACHED "alua_cached" #define PRIO_CONST "const" #define PRIO_DATACORE "datacore" #define PRIO_EMC "emc" diff --git a/libmultipath/prioritizers/Makefile b/libmultipath/prioritizers/Makefile index ff2524c2..201c52c0 100644 --- a/libmultipath/prioritizers/Makefile +++ b/libmultipath/prioritizers/Makefile @@ -13,6 +13,7 @@ LIBDEPS = -lmultipath -lmpathutil -lm -lpthread -lrt # If you add or remove a prioritizer also update multipath/multipath.conf.5 LIBS = \ libprioalua.so \ + libprioalua_cached.so \ libprioconst.so \ libpriodatacore.so \ libprioemc.so \ diff --git a/libmultipath/prioritizers/alua_cached.c b/libmultipath/prioritizers/alua_cached.c new file mode 100644 index 00000000..2ba5b2e7 --- /dev/null +++ b/libmultipath/prioritizers/alua_cached.c @@ -0,0 +1,224 @@ +/* + * (C) Copyright IBM Corp. 2004, 2005 All Rights Reserved. + * Modified 2024 to support cached ALUA state from checker + * + * ALUA prioritizer that can use cached state from alua checker + * to avoid duplicate RTPG commands. + * + * This file is released under the GPL. + */ +#include <stdio.h> +#include <string.h> + +#include <time.h> + +#include "debug.h" +#include "prio.h" +#include "structs.h" +#include "checkers.h" +#include "discovery.h" + +#include "alua.h" + +#define ALUA_PRIO_NOT_SUPPORTED 1 +#define ALUA_PRIO_RTPG_FAILED 2 +#define ALUA_PRIO_GETAAS_FAILED 3 +#define ALUA_PRIO_TPGS_FAILED 4 +#define ALUA_PRIO_NO_INFORMATION 5 +#define ALUA_PRIO_CACHED_STALE 6 + +/* Maximum age of cached ALUA state in seconds */ +#define ALUA_CACHE_MAX_AGE_SECS 5 + +static const char * aas_string[] = { + [AAS_OPTIMIZED] = "active/optimized", + [AAS_NON_OPTIMIZED] = "active/non-optimized", + [AAS_STANDBY] = "standby", + [AAS_UNAVAILABLE] = "unavailable", + [AAS_LBA_DEPENDENT] = "logical block dependent", + [AAS_RESERVED] = "ARRAY BUG: invalid TPGs state!", + [AAS_OFFLINE] = "offline", + [AAS_TRANSITIONING] = "transitioning between states", +}; + +static const char *aas_print_string(int rc) +{ + rc &= 0x7f; + + if (rc & 0x70) + return aas_string[AAS_RESERVED]; + rc &= 0x0f; + if (rc > AAS_RESERVED && rc < AAS_OFFLINE) + return aas_string[AAS_RESERVED]; + else + return aas_string[rc]; +} + +/* + * Try to get ALUA info with optimization: + * 1. If using alua checker: use its cached state (it just polled!) + * 2. Try sysfs (kernel maintains this, no I/O needed) + * 3. Fall back to issuing RTPG directly + * + * Rationale: The checker is our active poller. If we're using alua + * and its cache is fresh, we trust that data first. If the cache misses + * (stale or empty), we try sysfs before issuing RTPG. This provides a + * three-tier optimization: checker cache ??? sysfs ??? RTPG. + */ +/* Maximum length of sysfs access_state string */ +#define ALUA_ACCESS_STATE_SIZE 32 + +int +get_alua_info_cached(struct path * pp, bool *used_cache) +{ + int rc; + int tpg; + bool diff_tpg; + char buff[ALUA_ACCESS_STATE_SIZE]; + + *used_cache = false; + + /* First: if using alua checker, use its fresh data from path structure */ + if (checker_selected(&pp->checker)) { + const char *chk_name = checker_name(&pp->checker); + + if (chk_name && strcmp(chk_name, "alua") == 0 && + pp->alua_state >= 0) { + time_t now = time(NULL); + time_t age = now - pp->alua_timestamp; + + if (pp->alua_timestamp > 0 && age <= ALUA_CACHE_MAX_AGE_SECS) { + condlog(4, "%s: using cached ALUA state from checker (fresh poll)", pp->dev); + *used_cache = true; + return pp->alua_state; + } + condlog(4, "%s: cached ALUA state not available or stale, trying sysfs", + pp->dev); + /* Cache miss - try sysfs before issuing RTPG */ + } + } + + /* Second: try sysfs (if cache missed or not using alua checker) */ + rc = sysfs_get_asymmetric_access_state(pp, buff, sizeof(buff)); + if (rc >= 0) { + /* Parse sysfs state string to ALUA state value */ + int aas = -1; + int priopath = rc; /* rc contains preferred bit */ + + if (!strncmp(buff, "active/optimized", 16)) + aas = AAS_OPTIMIZED; + else if (!strncmp(buff, "active/non-optimized", 20)) + aas = AAS_NON_OPTIMIZED; + else if (!strncmp(buff, "lba-dependent", 13)) + aas = AAS_LBA_DEPENDENT; + else if (!strncmp(buff, "standby", 7)) + aas = AAS_STANDBY; + + if (aas >= 0) { + condlog(4, "%s: using sysfs ALUA state (no I/O)", pp->dev); + *used_cache = true; + return (priopath ? 0x80 : 0) | aas; + } + } + + /* Last resort: issue RTPG directly */ + tpg = get_target_port_group(pp); + if (tpg < 0) { + rc = get_target_port_group_support(pp); + if (rc < 0) + return -ALUA_PRIO_TPGS_FAILED; + if (rc == TPGS_NONE) + return -ALUA_PRIO_NOT_SUPPORTED; + return -ALUA_PRIO_RTPG_FAILED; + } + diff_tpg = (pp->tpg_id != GROUP_ID_UNDEF && pp->tpg_id != tpg); + pp->tpg_id = tpg; + condlog((diff_tpg) ? 2 : 4, "%s: reported target port group is %i", + pp->dev, tpg); + rc = get_asymmetric_access_state(pp, tpg); + if (rc < 0) { + condlog(2, "%s: get_asymmetric_access_state returned %d", + __func__, rc); + return -ALUA_PRIO_GETAAS_FAILED; + } + + condlog(3, "%s: aas = %02x [%s]%s", pp->dev, rc, aas_print_string(rc), + (rc & 0x80) ? " [preferred]" : ""); + return rc; +} + +int get_exclusive_pref_arg(char *args) +{ + char *ptr; + + if (args == NULL) + return 0; + ptr = strstr(args, "exclusive_pref_bit"); + if (!ptr) + return 0; + if (ptr[18] != '\0' && ptr[18] != ' ' && ptr[18] != '\t') + return 0; + if (ptr != args && ptr[-1] != ' ' && ptr[-1] != '\t') + return 0; + return 1; +} + +int getprio (struct path * pp, char * args) +{ + int rc; + int aas; + int priopath; + int exclusive_pref; + bool used_cache = false; + + if (pp->fd < 0) + return -ALUA_PRIO_NO_INFORMATION; + + exclusive_pref = get_exclusive_pref_arg(args); + rc = get_alua_info_cached(pp, &used_cache); + + if (used_cache) { + condlog(4, "%s: priority calculated from cached ALUA state (no RTPG issued)", + pp->dev); + } + + if (rc >= 0) { + aas = (rc & 0x0f); + priopath = (rc & 0x80); + switch(aas) { + case AAS_OPTIMIZED: + rc = 50; + break; + case AAS_NON_OPTIMIZED: + rc = 10; + break; + case AAS_LBA_DEPENDENT: + rc = 5; + break; + case AAS_STANDBY: + rc = 1; + break; + default: + rc = 0; + } + if (priopath && (aas != AAS_OPTIMIZED || exclusive_pref)) + rc += 80; + } else { + switch(-rc) { + case ALUA_PRIO_NOT_SUPPORTED: + condlog(0, "%s: alua not supported", pp->dev); + break; + case ALUA_PRIO_RTPG_FAILED: + condlog(0, "%s: couldn't get target port group", pp->dev); + break; + case ALUA_PRIO_GETAAS_FAILED: + condlog(0, "%s: couldn't get asymmetric access state", pp->dev); + break; + case ALUA_PRIO_TPGS_FAILED: + condlog(3, "%s: couldn't get supported alua states", pp->dev); + break; + } + } + return rc; +} + diff --git a/libmultipath/prioritizers/alua_rtpg.c b/libmultipath/prioritizers/alua_rtpg.c index 053cccb7..5da06b50 100644 --- a/libmultipath/prioritizers/alua_rtpg.c +++ b/libmultipath/prioritizers/alua_rtpg.c @@ -75,6 +75,7 @@ enum scsi_disposition { SCSI_GOOD = 0, SCSI_ERROR, SCSI_RETRY, + SCSI_DISCONNECTED, }; static int @@ -124,6 +125,12 @@ scsi_error(struct sg_io_hdr *hdr, int opcode) PRINT_DEBUG("alua: SCSI error for command %02x: status %02x, sense %02x/%02x/%02x", opcode, hdr->status, sense_key, asc, ascq); + /* Check for disconnected LUN (unmapped at target) */ + if (sense_key == 0x5 && asc == 0x25 && ascq == 0x00) { + /* ILLEGAL REQUEST / LOGICAL UNIT NOT SUPPORTED */ + return SCSI_DISCONNECTED; + } + if (sense_key == UNIT_ATTENTION || sense_key == NOT_READY) return SCSI_RETRY; else @@ -325,7 +332,10 @@ retry: } rc = scsi_error(&hdr, OPERATION_CODE_RTPG); - if (rc == SCSI_ERROR) { + if (rc == SCSI_DISCONNECTED) { + PRINT_DEBUG("do_rtpg: LUN disconnected (unmapped at target)!"); + return -RTPG_LUN_DISCONNECTED; + } else if (rc == SCSI_ERROR) { PRINT_DEBUG("do_rtpg: SCSI error!"); return -RTPG_RTPG_FAILED; } else if (rc == SCSI_RETRY) { diff --git a/libmultipath/prioritizers/alua_rtpg.h b/libmultipath/prioritizers/alua_rtpg.h index ff6ec0ef..030ca58d 100644 --- a/libmultipath/prioritizers/alua_rtpg.h +++ b/libmultipath/prioritizers/alua_rtpg.h @@ -21,6 +21,7 @@ #define RTPG_NO_TPG_IDENTIFIER 2 #define RTPG_RTPG_FAILED 3 #define RTPG_TPG_NOT_FOUND 4 +#define RTPG_LUN_DISCONNECTED 5 int get_target_port_group_support(const struct path *pp); int get_target_port_group(const struct path *pp); diff --git a/libmultipath/prioritizers/sysfs.c b/libmultipath/prioritizers/sysfs.c index 5e8adc05..ab058ea9 100644 --- a/libmultipath/prioritizers/sysfs.c +++ b/libmultipath/prioritizers/sysfs.c @@ -10,6 +10,9 @@ #include "discovery.h" #include "prio.h" +/* Maximum length of sysfs access_state string */ +#define ALUA_ACCESS_STATE_SIZE 32 + static const struct { unsigned char value; char *name; @@ -39,11 +42,11 @@ int get_exclusive_pref_arg(char *args) int getprio (struct path * pp, char *args) { int prio = 0, rc, i; - char buff[512]; + char buff[ALUA_ACCESS_STATE_SIZE]; int exclusive_pref; exclusive_pref = get_exclusive_pref_arg(args); - rc = sysfs_get_asymmetric_access_state(pp, buff, 512); + rc = sysfs_get_asymmetric_access_state(pp, buff, sizeof(buff)); if (rc < 0) return PRIO_UNDEF; prio = 0; diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 56895c4b..e421abba 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -262,6 +262,7 @@ verify_alua_prio(struct multipath *mp) if (!prio_selected(&pp->prio)) continue; if (strncmp(name, PRIO_ALUA, PRIO_NAME_LEN) && + strncmp(name, PRIO_ALUA_CACHED, PRIO_NAME_LEN) && strncmp(name, PRIO_SYSFS, PRIO_NAME_LEN)) return false; assume_alua = true; @@ -690,7 +691,12 @@ int select_checker(struct config *conf, struct path *pp) } (void)path_get_tpgs(pp); if (pp->tpgs != TPGS_NONE && pp->tpgs != TPGS_UNDEF) { - ckr_name = TUR; + /* + * For ALUA devices (any TPGS support), use alua + * checker which combines path checking with ALUA state + * caching for optimal performance. + */ + ckr_name = ALUA; goto out; } } @@ -767,8 +773,15 @@ void detect_prio(struct path *pp) if ((tpgs == TPGS_EXPLICIT || !check_rdac(pp)) && sysfs_get_asymmetric_access_state(pp, buff, 512) >= 0) default_prio = PRIO_SYSFS; - else + else { default_prio = PRIO_ALUA; + /* If using alua checker, use cached prioritizer */ + if (checker_selected(&pp->checker)) { + const char *chk_name = checker_name(&pp->checker); + if (chk_name && strcmp(chk_name, ALUA) == 0) + default_prio = PRIO_ALUA_CACHED; + } + } break; default: return; @@ -831,7 +844,8 @@ out: /* * fetch tpgs mode for alua, if its not already obtained */ - if (!strncmp(prio_name(p), PRIO_ALUA, PRIO_NAME_LEN)) { + if (!strncmp(prio_name(p), PRIO_ALUA, PRIO_NAME_LEN) || + !strncmp(prio_name(p), PRIO_ALUA_CACHED, PRIO_NAME_LEN)) { int tpgs = path_get_tpgs(pp); if (tpgs == TPGS_NONE) { diff --git a/libmultipath/structs.c b/libmultipath/structs.c index f4413127..6483a568 100644 --- a/libmultipath/structs.c +++ b/libmultipath/structs.c @@ -126,6 +126,8 @@ alloc_path (void) pp->fd = -1; pp->tpgs = TPGS_UNDEF; pp->tpg_id = GROUP_ID_UNDEF; + pp->alua_state = -1; + pp->alua_timestamp = 0; pp->priority = PRIO_UNDEF; pp->checkint = CHECKINT_UNDEF; checker_clear(&pp->checker); diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 9adedde2..c4bddee9 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -444,6 +444,9 @@ struct path { vector hwe; struct gen_path generic_path; int tpg_id; + /* Cached ALUA state from checker for prioritizer use */ + int alua_state; /* AAS value, -1 if not cached */ + time_t alua_timestamp; /* When alua_state was last updated */ enum ioctl_info_states ioctl_info; }; -- 2.50.1 (Apple Git-155)
