diff --git a/CHANGELOG.md b/CHANGELOG.md index ad0353392..266d1f571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,12 @@ New `spdk_bdev_copy_blocks` and `spdk_bdev_get_max_copy` APIs to support copy co A new API `spdk_bdev_io_get_submit_tsc` was added to get the submit_tsc of the bdev I/O. +Bdevs will no longer have UUIDs generated based on timestamp and are responsible for +setting this field themselves. Generation of UUIDs for NVMe bdevs may be enabled by +running `bdev_nvme_set_options` RPC with `--generate-uuids` option. These identifiers +are based on serial number and namespace ID and will always be the same for a given +device. + ### event Added core lock file mechanism to prevent the same CPU cores from being used by multiple diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index 4afc56cae..c24c541e1 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -3317,6 +3317,7 @@ ctrlr_loss_timeout_sec | Optional | number | Time to wait until ctrlr i reconnect_delay_sec | Optional | number | Time to delay a reconnect trial. 0 means no reconnect. fast_io_fail_timeout_sec | Optional | number | Time to wait until ctrlr is reconnected before failing I/O to ctrlr. 0 means no such timeout. disable_auto_failback | Optional | boolean | Disable automatic failback. The RPC bdev_nvme_set_preferred_path can be used to do manual failback. +generate_uuids | Optional | boolean | Enable generation of UUIDs for NVMe bdevs that do not provide this value themselves. #### Example diff --git a/include/spdk/bdev_module.h b/include/spdk/bdev_module.h index 6dc39483e..d975cc907 100644 --- a/include/spdk/bdev_module.h +++ b/include/spdk/bdev_module.h @@ -380,8 +380,7 @@ struct spdk_bdev { /** * UUID for this bdev. * - * Fill with zeroes if no uuid is available. The bdev layer - * will automatically populate this if necessary. + * Fill with zeroes if no uuid is available. */ struct spdk_uuid uuid; diff --git a/module/bdev/nvme/bdev_nvme.c b/module/bdev/nvme/bdev_nvme.c index e8242d4d5..1f307fb58 100644 --- a/module/bdev/nvme/bdev_nvme.c +++ b/module/bdev/nvme/bdev_nvme.c @@ -32,6 +32,8 @@ #define SPDK_BDEV_NVME_DEFAULT_DELAY_CMD_SUBMIT true #define SPDK_BDEV_NVME_DEFAULT_KEEP_ALIVE_TIMEOUT_IN_MS (10000) +#define NSID_STR_LEN 10 + static int bdev_nvme_config_json(struct spdk_json_write_ctx *w); struct nvme_bdev_io { @@ -120,6 +122,7 @@ static struct spdk_bdev_nvme_opts g_opts = { .reconnect_delay_sec = 0, .fast_io_fail_timeout_sec = 0, .disable_auto_failback = false, + .generate_uuids = false, }; #define NVME_HOTPLUG_POLL_PERIOD_MAX 10000000ULL @@ -2907,6 +2910,120 @@ nvme_ns_set_ana_state(const struct spdk_nvme_ana_group_descriptor *desc, void *c return 0; } +static void +merge_nsid_sn_strings(const char *sn, char *nsid, int8_t *out) +{ + int i = 0, j = 0; + int sn_len = strlen(sn), nsid_len = strlen(nsid); + + for (i = 0; i < nsid_len; i++) { + out[i] = nsid[i]; + } + + /* Since last few characters are more likely to be unique, + * even among the devices from the same manufacturer, + * we use serial number in reverse. We also skip the + * terminating character of serial number string. */ + for (j = sn_len - 1; j >= 0; j--) { + if (i == SPDK_UUID_STRING_LEN - 1) { + break; + } + + /* There may be a lot of spaces in serial number string + * and they will generate equally large number of the + * same character, so just skip them. */ + if (sn[j] == ' ') { + continue; + } + + out[i] = sn[j]; + i++; + } +} + +/* Dictionary of characters for UUID generation. */ +static char dict[17] = "0123456789abcdef"; + +static struct spdk_uuid +nvme_generate_uuid(const char *sn, uint32_t nsid) +{ + struct spdk_uuid new_uuid; + char buf[SPDK_UUID_STRING_LEN] = {'\0'}, merged_str[SPDK_UUID_STRING_LEN] = {'\0'}; + char nsid_str[NSID_STR_LEN] = {'\0'}, tmp; + uint64_t i = 0, j = 0, rem, dict_size = strlen(dict); + int rc; + + assert(strlen(sn) <= SPDK_NVME_CTRLR_SN_LEN); + + snprintf(nsid_str, NSID_STR_LEN, "%" PRIu32, nsid); + + merge_nsid_sn_strings(sn, nsid_str, merged_str); + + while (i < SPDK_UUID_STRING_LEN) { + /* If 'j' is equal to indexes, where '-' should be placed, + * insert this character and continue the loop without + * increasing 'i'. */ + if ((j == 8 || j == 13 || j == 18 || j == 23)) { + buf[j] = '-'; + j++; + + /* Break, if we ran out of characters in + * serial number and namespace ID string. */ + if (j == strlen(merged_str)) { + break; + } + continue; + } + + /* Change character in shuffled string to lower case. */ + tmp = tolower(merged_str[i]); + + if (isxdigit(tmp)) { + /* If character can be represented by a hex + * value as is, copy it to the result buffer. */ + buf[j] = tmp; + } else { + /* Otherwise get its code and divide it + * by the number of elements in dictionary. + * The remainder will be the index of dictionary + * character to replace tmp value with. */ + rem = tmp % dict_size; + buf[j] = dict[rem]; + } + + i++; + j++; + + /* Break, if we ran out of characters in + * serial number and namespace ID string. */ + if (j == strlen(merged_str)) { + break; + } + } + + /* If there are not enough values to fill UUID, + * the rest is taken from dictionary characters. */ + i = 0; + while (j < SPDK_UUID_STRING_LEN - 1) { + if ((j == 8 || j == 13 || j == 18 || j == 23)) { + buf[j] = '-'; + j++; + continue; + } + buf[j] = dict[i % dict_size]; + i++; + j++; + } + + rc = spdk_uuid_parse(&new_uuid, buf); + if (rc != 0) { + SPDK_ERRLOG("Unexpected spdk_uuid_parse failure on %s.\n", buf); + assert(false); + } + + return new_uuid; +} + static int nvme_disk_create(struct spdk_bdev *disk, const char *base_name, struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns, @@ -2919,6 +3036,7 @@ nvme_disk_create(struct spdk_bdev *disk, const char *base_name, const struct spdk_nvme_ctrlr_opts *opts; enum spdk_nvme_csi csi; uint32_t atomic_bs, phys_bs, bs; + char sn_tmp[SPDK_NVME_CTRLR_SN_LEN + 1] = {'\0'}; cdata = spdk_nvme_ctrlr_get_data(ctrlr); csi = spdk_nvme_ns_get_csi(ns); @@ -2974,6 +3092,9 @@ nvme_disk_create(struct spdk_bdev *disk, const char *base_name, uuid = spdk_nvme_ns_get_uuid(ns); if (uuid) { disk->uuid = *uuid; + } else if (g_opts.generate_uuids) { + spdk_strcpy_pad(sn_tmp, cdata->sn, SPDK_NVME_CTRLR_SN_LEN + 1, '\0'); + disk->uuid = nvme_generate_uuid(sn_tmp, spdk_nvme_ns_get_id(ns)); } } else { memcpy(&disk->uuid, nguid, sizeof(disk->uuid)); @@ -6579,6 +6700,7 @@ bdev_nvme_opts_config_json(struct spdk_json_write_ctx *w) spdk_json_write_named_int32(w, "ctrlr_loss_timeout_sec", g_opts.ctrlr_loss_timeout_sec); spdk_json_write_named_uint32(w, "reconnect_delay_sec", g_opts.reconnect_delay_sec); spdk_json_write_named_uint32(w, "fast_io_fail_timeout_sec", g_opts.fast_io_fail_timeout_sec); + spdk_json_write_named_bool(w, "generate_uuids", g_opts.generate_uuids); spdk_json_write_object_end(w); spdk_json_write_object_end(w); diff --git a/module/bdev/nvme/bdev_nvme.h b/module/bdev/nvme/bdev_nvme.h index a43746c22..73e037197 100644 --- a/module/bdev/nvme/bdev_nvme.h +++ b/module/bdev/nvme/bdev_nvme.h @@ -250,6 +250,7 @@ struct spdk_bdev_nvme_opts { uint32_t reconnect_delay_sec; uint32_t fast_io_fail_timeout_sec; bool disable_auto_failback; + bool generate_uuids; }; struct spdk_nvme_qpair *bdev_nvme_get_io_qpair(struct spdk_io_channel *ctrlr_io_ch); diff --git a/module/bdev/nvme/bdev_nvme_rpc.c b/module/bdev/nvme/bdev_nvme_rpc.c index 6a87ff395..2392a8aa7 100644 --- a/module/bdev/nvme/bdev_nvme_rpc.c +++ b/module/bdev/nvme/bdev_nvme_rpc.c @@ -68,6 +68,7 @@ static const struct spdk_json_object_decoder rpc_bdev_nvme_options_decoders[] = {"reconnect_delay_sec", offsetof(struct spdk_bdev_nvme_opts, reconnect_delay_sec), spdk_json_decode_uint32, true}, {"fast_io_fail_timeout_sec", offsetof(struct spdk_bdev_nvme_opts, fast_io_fail_timeout_sec), spdk_json_decode_uint32, true}, {"disable_auto_failback", offsetof(struct spdk_bdev_nvme_opts, disable_auto_failback), spdk_json_decode_bool, true}, + {"generate_uuids", offsetof(struct spdk_bdev_nvme_opts, generate_uuids), spdk_json_decode_bool, true}, }; static void diff --git a/python/spdk/rpc/bdev.py b/python/spdk/rpc/bdev.py index 94bf47c79..cdc67e7af 100644 --- a/python/spdk/rpc/bdev.py +++ b/python/spdk/rpc/bdev.py @@ -521,7 +521,7 @@ def bdev_nvme_set_options(client, action_on_timeout=None, timeout_us=None, timeo nvme_adminq_poll_period_us=None, nvme_ioq_poll_period_us=None, io_queue_requests=None, delay_cmd_submit=None, transport_retry_count=None, bdev_retry_count=None, transport_ack_timeout=None, ctrlr_loss_timeout_sec=None, reconnect_delay_sec=None, - fast_io_fail_timeout_sec=None, disable_auto_failback=None): + fast_io_fail_timeout_sec=None, disable_auto_failback=None, generate_uuids=None): """Set options for the bdev nvme. This is startup command. Args: @@ -559,6 +559,8 @@ def bdev_nvme_set_options(client, action_on_timeout=None, timeout_us=None, timeo This can be overridden by bdev_nvme_attach_controller. (optional) disable_auto_failback: Disable automatic failback. bdev_nvme_set_preferred_path can be used to do manual failback. By default, immediately failback to the preferred I/O path if it is restored. (optional) + generate_uuids: Enable generation of unique identifiers for NVMe bdevs only if they do not provide UUID themselves. + These strings are based on device serial number and namespace ID and will always be the same for that device. """ params = {} @@ -624,6 +626,9 @@ def bdev_nvme_set_options(client, action_on_timeout=None, timeout_us=None, timeo if disable_auto_failback is not None: params['disable_auto_failback'] = disable_auto_failback + if generate_uuids is not None: + params['generate_uuids'] = generate_uuids + return client.call('bdev_nvme_set_options', params) diff --git a/scripts/rpc.py b/scripts/rpc.py index fb21b7475..747f1465a 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -556,7 +556,8 @@ if __name__ == "__main__": ctrlr_loss_timeout_sec=args.ctrlr_loss_timeout_sec, reconnect_delay_sec=args.reconnect_delay_sec, fast_io_fail_timeout_sec=args.fast_io_fail_timeout_sec, - disable_auto_failback=args.disable_auto_failback) + disable_auto_failback=args.disable_auto_failback, + generate_uuids=args.generate_uuids) p = subparsers.add_parser('bdev_nvme_set_options', help='Set options for the bdev nvme type. This is startup command.') @@ -621,6 +622,10 @@ if __name__ == "__main__": help="""Disable automatic failback. bdev_nvme_set_preferred_path can be used to do manual failback. By default, immediately failback to the preferred I/O path if it restored.""", action='store_true') + p.add_argument('--generate-uuids', + help="""Enable generation of unique identifiers for NVMe bdevs only if they do + not provide UUID themselves. These strings are based on device serial number and + namespace ID and will always be the same for that device.""", action='store_true') p.set_defaults(func=bdev_nvme_set_options)