Whamcloud - gitweb
LU-17054 lnet: Change cpt-of-nid to get result from kernel 02/52502/5
authorChris Horn <chris.horn@hpe.com>
Tue, 29 Aug 2023 16:46:13 +0000 (10:46 -0600)
committerOleg Drokin <green@whamcloud.com>
Sat, 18 Nov 2023 21:44:34 +0000 (21:44 +0000)
The lnetctl cpt-of-nid command leverages a userspace implementation
of the kernel hash_long() function to compute the CPT for a given
NID. However, the kernel hash_long() function has changed over time
such that the userspace version may give a different result than the
kernel version. Since Lustre supports such a wide range of kernels
we cannot simply update the userspace implementation of hash_long() to
match newer kernel.

Address this by re-implementing lnetctl cpt-of-nid to call into kernel
space to compute the CPT and return the result to userspace.

lnetctl cpt-of-nid now works with extended NIDs (e.g., IPv6).

lnetctl cpt-of-nid no longer accepts the --ncpt argument because the
kernel functions for computing the cpt do not support this.

lnetctl cpt-of-nid no longer accepts the --nid argument. Instead, the
command now takes a space separated list of nids.

Example:
$ lnetctl cpt-of-nid 867@kfi 5.3.0.9@tcp
cpt-of-nid:
- nid: 867@kfi
  cpt: 0
- nid: 5.3.0.9@tcp
  cpt: 1
$

Because the old implementation could return a wrong result it is
completely removed.

HPE-bug-id: LUS-11785
Test-Parameters: trivial
Signed-off-by: Chris Horn <chris.horn@hpe.com>
Change-Id: I7c2bc48c5c0da7da8a4425d319c0b99207814ae1
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/52502
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: James Simmons <jsimmons@infradead.org>
Reviewed-by: Serguei Smirnov <ssmirnov@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lnet/include/lnet/lib-types.h
lnet/include/uapi/linux/lnet/lnet-dlc.h
lnet/lnet/api-ni.c
lnet/utils/lnetconfig/liblnetconfig.c
lnet/utils/lnetconfig/liblnetconfig.h
lnet/utils/lnetctl.c

index d68ee29..6083ecb 100644 (file)
@@ -1049,6 +1049,29 @@ enum lnet_udsp_info_pref_nids_attr {
 
 #define LNET_UDSP_INFO_PREF_NIDS_ATTR_MAX (__LNET_UDSP_INFO_PREF_NIDS_ATTR_MAX_PLUS_ONE - 1)
 
+/** enum lnet_cpt_of_nid_attr                  - Attributes to support
+ *                                               lnetctl cpt-of-nid command
+ *
+ * @LNET_CPT_OF_NID_ATTR_UNSPEC                          unspecified attribute to catch
+ *                                               errors
+ * @LNET_CPT_OF_NID_ATTR_HDR                     Grouping for cpt-of-nid
+ *                                               (NLA_NUL_STRING)
+ * @LNET_CPT_OF_NID_ATTR_NID                     The NID whose CPT we want to
+ *                                               calculate (NLA_STRING)
+ * LNET_CPT_OF_NID_ATTR_CPT                      The CPT for the specified NID
+ *                                               (NLA_U32)
+ */
+enum lnet_cpt_of_nid_attr {
+       LNET_CPT_OF_NID_ATTR_UNSPEC = 0,
+
+       LNET_CPT_OF_NID_ATTR_HDR,
+       LNET_CPT_OF_NID_ATTR_NID,
+       LNET_CPT_OF_NID_ATTR_CPT,
+       __LNET_CPT_OF_NID_ATTR_MAX_PLUS_ONE,
+};
+
+#define LNET_CPT_OF_NID_ATTR_MAX (__LNET_CPT_OF_NID_ATTR_MAX_PLUS_ONE - 1)
+
 struct lnet_ni {
        /* chain on the lnet_net structure */
        struct list_head        ni_netlist;
index 47ac566..aefcc88 100644 (file)
@@ -59,6 +59,7 @@
  * @LNET_CMD_NETS:             command to manage the LNet networks
  * @LNET_CMD_ROUTES:           command to manage LNet routes
  * @LNET_CMD_PING:             command to send pings to LNet connections
+ * @LNET_CMD_CPT_OF_NID:       command to calculate the CPT of specified NIDs
  */
 enum lnet_commands {
        LNET_CMD_UNSPEC         = 0,
@@ -69,6 +70,7 @@ enum lnet_commands {
        LNET_CMD_ROUTES         = 4,
        LNET_CMD_CONNS          = 5,
        LNET_CMD_PING           = 6,
+       LNET_CMD_CPT_OF_NID     = 7,
 
        __LNET_CMD_MAX_PLUS_ONE
 };
index 18159a3..d1b98df 100644 (file)
@@ -4785,6 +4785,244 @@ report_discover_err:
 }
 EXPORT_SYMBOL(LNetCtl);
 
+struct lnet_nid_cpt {
+       struct lnet_nid lnc_nid;
+       unsigned int lnc_cpt;
+};
+
+struct lnet_genl_nid_cpt_list {
+       unsigned int lgncl_index;
+       unsigned int lgncl_list_count;
+       GENRADIX(struct lnet_nid_cpt) lgncl_lnc_list;
+};
+
+static inline struct lnet_genl_nid_cpt_list *
+lnet_cpt_of_nid_dump_ctx(struct netlink_callback *cb)
+{
+       return (struct lnet_genl_nid_cpt_list *)cb->args[0];
+}
+
+static int lnet_cpt_of_nid_show_done(struct netlink_callback *cb)
+{
+       struct lnet_genl_nid_cpt_list *lgncl;
+
+       lgncl = lnet_cpt_of_nid_dump_ctx(cb);
+
+       if (lgncl) {
+               genradix_free(&lgncl->lgncl_lnc_list);
+               LIBCFS_FREE(lgncl, sizeof(*lgncl));
+               cb->args[0] = 0;
+       }
+
+       return 0;
+}
+
+static int lnet_cpt_of_nid_show_start(struct netlink_callback *cb)
+{
+       struct genlmsghdr *gnlh = nlmsg_data(cb->nlh);
+#ifdef HAVE_NL_PARSE_WITH_EXT_ACK
+       struct netlink_ext_ack *extack = NULL;
+#endif
+       struct lnet_genl_nid_cpt_list *lgncl;
+       int msg_len = genlmsg_len(gnlh);
+       struct nlattr *params, *top;
+       int rem, rc = 0;
+
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+
+       mutex_lock(&the_lnet.ln_api_mutex);
+       if (the_lnet.ln_state != LNET_STATE_RUNNING) {
+               NL_SET_ERR_MSG(extack, "Network is down");
+               mutex_unlock(&the_lnet.ln_api_mutex);
+               return -ENETDOWN;
+       }
+
+       msg_len = genlmsg_len(gnlh);
+       if (!msg_len) {
+               NL_SET_ERR_MSG(extack, "Missing NID argument(s)");
+               mutex_unlock(&the_lnet.ln_api_mutex);
+               return -ENOENT;
+       }
+
+       LIBCFS_ALLOC(lgncl, sizeof(*lgncl));
+       if (!lgncl) {
+               mutex_unlock(&the_lnet.ln_api_mutex);
+               return -ENOMEM;
+       }
+
+       genradix_init(&lgncl->lgncl_lnc_list);
+       lgncl->lgncl_list_count = 0;
+       cb->args[0] = (long)lgncl;
+
+       params = genlmsg_data(gnlh);
+       nla_for_each_attr(top, params, msg_len, rem) {
+               struct nlattr *nids;
+               int rem2;
+
+               switch (nla_type(top)) {
+               case LN_SCALAR_ATTR_LIST:
+                       nla_for_each_nested(nids, top, rem2) {
+                               char nidstr[LNET_NIDSTR_SIZE + 1];
+                               struct lnet_nid_cpt *lnc;
+
+                               if (nla_type(nids) != LN_SCALAR_ATTR_VALUE)
+                                       continue;
+
+                               memset(nidstr, 0, sizeof(nidstr));
+                               rc = nla_strscpy(nidstr, nids, sizeof(nidstr));
+                               if (rc < 0) {
+                                       NL_SET_ERR_MSG(extack,
+                                                      "failed to get NID");
+                                       GOTO(report_err, rc);
+                               }
+
+                               lnc = genradix_ptr_alloc(&lgncl->lgncl_lnc_list,
+                                                        lgncl->lgncl_list_count++,
+                                                        GFP_ATOMIC);
+                               if (!lnc) {
+                                       NL_SET_ERR_MSG(extack,
+                                                      "failed to allocate NID");
+                                       GOTO(report_err, rc = -ENOMEM);
+                               }
+
+                               rc = libcfs_strnid(&lnc->lnc_nid, strim(nidstr));
+                               if (rc < 0) {
+                                       NL_SET_ERR_MSG(extack, "invalid NID");
+                                       GOTO(report_err, rc);
+                               }
+                               rc = 0;
+                               CDEBUG(D_NET, "nid: %s\n", libcfs_nidstr(&lnc->lnc_nid));
+                       }
+                       fallthrough;
+               default:
+                       break;
+               }
+       }
+report_err:
+       mutex_unlock(&the_lnet.ln_api_mutex);
+
+       if (rc < 0)
+               lnet_cpt_of_nid_show_done(cb);
+
+       return rc;
+}
+
+static const struct ln_key_list cpt_of_nid_props_list = {
+       .lkl_maxattr                    = LNET_CPT_OF_NID_ATTR_MAX,
+       .lkl_list                       = {
+               [LNET_CPT_OF_NID_ATTR_HDR]      = {
+                       .lkp_value              = "cpt-of-nid",
+                       .lkp_key_format         = LNKF_SEQUENCE | LNKF_MAPPING,
+                       .lkp_data_type          = NLA_NUL_STRING,
+               },
+               [LNET_CPT_OF_NID_ATTR_NID]      = {
+                       .lkp_value              = "nid",
+                       .lkp_data_type          = NLA_STRING,
+               },
+               [LNET_CPT_OF_NID_ATTR_CPT]      = {
+                       .lkp_value              = "cpt",
+                       .lkp_data_type          = NLA_U32,
+               },
+       },
+};
+
+static int lnet_cpt_of_nid_show_dump(struct sk_buff *msg,
+                                    struct netlink_callback *cb)
+{
+       struct lnet_genl_nid_cpt_list *lgncl;
+#ifdef HAVE_NL_PARSE_WITH_EXT_ACK
+       struct netlink_ext_ack *extack = NULL;
+#endif
+       int portid = NETLINK_CB(cb->skb).portid;
+       int seq = cb->nlh->nlmsg_seq;
+       int idx;
+       int rc = 0;
+       bool need_hdr = true;
+
+#ifdef HAVE_NL_DUMP_WITH_EXT_ACK
+       extack = cb->extack;
+#endif
+
+       mutex_lock(&the_lnet.ln_api_mutex);
+       if (the_lnet.ln_state != LNET_STATE_RUNNING) {
+               NL_SET_ERR_MSG(extack, "Network is down");
+               GOTO(send_error, rc = -ENETDOWN);
+       }
+
+       lgncl = lnet_cpt_of_nid_dump_ctx(cb);
+       idx = lgncl->lgncl_index;
+
+       if (!lgncl->lgncl_index) {
+               const struct ln_key_list *all[] = {
+                       &cpt_of_nid_props_list, NULL, NULL
+               };
+
+               rc = lnet_genl_send_scalar_list(msg, portid, seq, &lnet_family,
+                                               NLM_F_CREATE | NLM_F_MULTI,
+                                               LNET_CMD_CPT_OF_NID, all);
+               if (rc < 0) {
+                       NL_SET_ERR_MSG(extack, "failed to send key table");
+                       GOTO(send_error, rc);
+               }
+       }
+
+       while (idx < lgncl->lgncl_list_count) {
+               struct lnet_nid_cpt *lnc;
+               void *hdr;
+               int cpt;
+
+               lnc = genradix_ptr(&lgncl->lgncl_lnc_list, idx++);
+
+               cpt = lnet_nid_cpt_hash(&lnc->lnc_nid, LNET_CPT_NUMBER);
+
+               CDEBUG(D_NET, "nid: %s cpt: %d\n", libcfs_nidstr(&lnc->lnc_nid), cpt);
+               hdr = genlmsg_put(msg, portid, seq, &lnet_family,
+                                 NLM_F_MULTI, LNET_CMD_CPT_OF_NID);
+               if (!hdr) {
+                       NL_SET_ERR_MSG(extack, "failed to send values");
+                       genlmsg_cancel(msg, hdr);
+                       GOTO(send_error, rc = -EMSGSIZE);
+               }
+
+               if (need_hdr) {
+                       nla_put_string(msg, LNET_CPT_OF_NID_ATTR_HDR, "");
+                       need_hdr = false;
+               }
+
+               nla_put_string(msg, LNET_CPT_OF_NID_ATTR_NID,
+                              libcfs_nidstr(&lnc->lnc_nid));
+               nla_put_u32(msg, LNET_CPT_OF_NID_ATTR_CPT, cpt);
+
+               genlmsg_end(msg, hdr);
+       }
+
+       genradix_free(&lgncl->lgncl_lnc_list);
+       rc = 0;
+       lgncl->lgncl_index = idx;
+
+send_error:
+       mutex_unlock(&the_lnet.ln_api_mutex);
+
+       return lnet_nl_send_error(cb->skb, portid, seq, rc);
+}
+
+#ifndef HAVE_NETLINK_CALLBACK_START
+static int lnet_old_cpt_of_nid_show_dump(struct sk_buff *msg,
+                                        struct netlink_callback *cb)
+{
+       if (!cb->args[0]) {
+               int rc = lnet_cpt_of_nid_show_start(cb);
+
+               if (rc < 0)
+                       return rc;
+       }
+
+       return lnet_cpt_of_nid_show_dump(msg, cb);
+}
+#endif
+
 /* This is the keys for the UDSP info which is used by many
  * Netlink commands.
  */
@@ -8159,6 +8397,7 @@ static const struct genl_multicast_group lnet_mcast_grps[] = {
        { .name =       "route",        },
        { .name =       "ping",         },
        { .name =       "discover",     },
+       { .name =       "cpt-of-nid",   },
 };
 
 static const struct genl_ops lnet_genl_ops[] = {
@@ -8210,6 +8449,16 @@ static const struct genl_ops lnet_genl_ops[] = {
                .done           = lnet_ping_show_done,
                .doit           = lnet_ping_cmd,
        },
+       {
+               .cmd            = LNET_CMD_CPT_OF_NID,
+#ifdef HAVE_NETLINK_CALLBACK_START
+               .start          = lnet_cpt_of_nid_show_start,
+               .dumpit         = lnet_cpt_of_nid_show_dump,
+#else
+               .dumpit         = lnet_old_cpt_of_nid_show_dump,
+#endif
+               .done           = lnet_cpt_of_nid_show_done,
+       },
 };
 
 static struct genl_family lnet_family = {
index c759f7b..10b3b4f 100644 (file)
@@ -4193,72 +4193,6 @@ out:
        return rc;
 }
 
-static unsigned int
-lnet_nid_cpt_hash(lnet_nid_t nid, long int number, unsigned int cpt_bits)
-{
-       __u64 key = nid;
-       __u16 lnd = LNET_NETTYP(LNET_NIDNET(nid));
-       unsigned int cpt;
-
-       if (number == 1)
-               return 0;
-
-       if (lnd == KFILND || lnd == GNILND) {
-               cpt = hash_long(key, cpt_bits);
-
-               /* NB: The number of CPTs needn't be a power of 2 */
-               if (cpt >= number)
-                       cpt = (key + cpt + (cpt >> 1)) % number;
-       } else {
-               __u64 pair_bits = 0x0001000100010001LLU;
-               __u64 mask = pair_bits * 0xFF;
-               __u64 pair_sum;
-               /* For ipv4 NIDs, use (sum-by-multiplication of nid bytes) mod
-                * (number of CPTs) to match nid to a CPT.
-                */
-               pair_sum = (key & mask) + ((key >> 8) & mask);
-               pair_sum = (pair_sum * pair_bits) >> 48;
-               cpt = (unsigned int)(pair_sum) % number;
-       }
-
-       return cpt;
-}
-
-int lustre_lnet_calc_cpt_of_nid(char *nidc, long int ncpts)
-{
-       int rc = LUSTRE_CFG_RC_BAD_PARAM;
-       lnet_nid_t nid;
-       unsigned int cpt_bits;
-
-       if (!nidc) {
-               fprintf(stderr, "error:\n    msg: \"no NID provided\"\n");
-               return rc;
-       }
-
-       if (ncpts < 0) {
-               fprintf(stderr, "error:\n    msg: \"number of CPTs not provided\"\n");
-               return rc;
-       }
-
-       if (ncpts < 1) {
-               fprintf(stderr, "error:\n    msg: \"number of CPTs must be >= 1\"\n");
-               return rc;
-       }
-
-       cpt_bits = 1;
-       while ((1 << cpt_bits) < ncpts)
-               cpt_bits++;
-
-       nid = libcfs_str2nid(nidc);
-       if (nid == LNET_NID_ANY) {
-               fprintf(stderr, "error:\n    msg: \"bad NID provided %s\"\n",
-                       nidc);
-               return rc;
-       }
-
-       return (int)lnet_nid_cpt_hash(nid, ncpts, cpt_bits);
-}
-
 static int show_recovery_queue(enum lnet_health_type type, char *name,
                               int seq_no, struct cYAML **show_rc,
                               struct cYAML **err_rc)
index d33b92b..48adc14 100644 (file)
@@ -544,12 +544,6 @@ int lustre_lnet_calc_service_id(__u64 *service_id);
 int lustre_lnet_setup_mrrouting(struct cYAML **err_rc);
 
 /*
- * lustre_lnet_calc_cpt_of_nid
- *     Return the cpt number of the NID provided
- */
-int lustre_lnet_calc_cpt_of_nid(char *nidc, long int ncpts);
-
-/*
  * lustre_lnet_config_discovery
  *   Enable or disable peer discovery. Peer discovery is enabled by default.
  *
index df1ac30..2fe2347 100644 (file)
@@ -117,9 +117,9 @@ command_t cmd_list[] = {
        {"udsp", jt_udsp, 0, "udsp {add | del | help}"},
        {"setup-mrrouting", jt_setup_mrrouting, 0,
         "setup linux routing tables\n"},
-       {"cpt-of-nid", jt_calc_cpt_of_nid, 0, "Calculate the CPT associated with NID\n"
-        "\t--nid: NID to calculate the CPT of\n"
-        "\t--ncpt: Number of CPTs to consider in the calculation\n"},
+       {"cpt-of-nid", jt_calc_cpt_of_nid, 0,
+        "Calculate the CPTs associated with NIDs\n"
+        " usage:\n\tlnetctl cpt-of-nid nid[ nid ...]\n"},
        { 0, 0, 0, NULL }
 };
 
@@ -436,55 +436,6 @@ static int jt_calc_service_id(int argc, char **argv)
        return rc;
 }
 
-static int jt_calc_cpt_of_nid(int argc, char **argv)
-{
-       int rc, opt;
-       int cpt;
-       long int ncpts = -1;
-       char *nid = NULL;
-       struct cYAML *err_rc = NULL;
-       const char *const short_options = "n:c:h";
-       static const struct option long_options[] = {
-       { .name = "nid",       .has_arg = required_argument, .val = 'n' },
-       { .name = "ncpt",     .has_arg = required_argument, .val = 'c' },
-       { .name = NULL } };
-
-       rc = check_cmd(cmd_list, "", "cpt-of-nid", 0, argc, argv);
-       if (rc)
-               return rc;
-
-       while ((opt = getopt_long(argc, argv, short_options,
-                                  long_options, NULL)) != -1) {
-               switch (opt) {
-               case 'n':
-                       nid = optarg;
-                       break;
-               case 'c':
-                       rc = parse_long(optarg, &ncpts);
-                       if (rc != 0) {
-                               cYAML_build_error(-1, -1, "cpt", "get",
-                                               "cannot parse input", &err_rc);
-                               cYAML_print_tree2file(stderr, err_rc);
-                               cYAML_free_tree(err_rc);
-                               return -1;
-                       }
-                       break;
-               case '?':
-                       print_help(cmd_list, "", "cpt-of-nid");
-               default:
-                       return 0;
-               }
-       }
-
-       cpt = lustre_lnet_calc_cpt_of_nid(nid, ncpts);
-       if (cpt < 0)
-               return -1;
-
-       printf("cpt:\n    value: %d\n", cpt);
-
-       return 0;
-}
-
 static int jt_set_recovery_limit(int argc, char **argv)
 {
        long int value;
@@ -1080,6 +1031,200 @@ emitter_error:
        yaml_emitter_delete(&log);
 }
 
+static int yaml_lnet_cpt_of_nid_display(yaml_parser_t *reply)
+{
+       yaml_document_t results;
+       yaml_emitter_t output;
+       int rc;
+
+       rc = yaml_parser_load(reply, &results);
+       if (rc == 0) {
+               yaml_lnet_print_error(NLM_F_DUMP, "cpt-of-nid",
+                                     yaml_parser_get_reader_error(reply));
+               yaml_document_delete(&results);
+               return -EINVAL;
+       }
+
+       rc = yaml_emitter_initialize(&output);
+       if (rc == 1) {
+               yaml_emitter_set_output_file(&output, stdout);
+
+               rc = yaml_emitter_dump(&output, &results);
+       }
+
+       yaml_document_delete(&results);
+       if (rc == 0) {
+               yaml_emitter_log_error(&output, stderr);
+               rc = -EINVAL;
+       }
+       yaml_emitter_delete(&output);
+
+       return 1;
+}
+
+static int yaml_lnet_cpt_of_nid(int start, int end, char **nids)
+{
+       struct nl_sock *sk = NULL;
+       yaml_emitter_t request;
+       yaml_parser_t reply;
+       yaml_event_t event;
+       int i, rc;
+
+       /* Create Netlink emitter to send request to kernel */
+       sk = nl_socket_alloc();
+       if (!sk)
+               return -EOPNOTSUPP;
+
+       /* Setup parser to receive Netlink packets */
+       rc = yaml_parser_initialize(&reply);
+       if (rc == 0) {
+               nl_socket_free(sk);
+               return -EOPNOTSUPP;
+       }
+
+       rc = yaml_parser_set_input_netlink(&reply, sk, false);
+       if (rc == 0)
+               goto free_reply;
+
+       /* Create Netlink emitter to send request to kernel */
+       rc = yaml_emitter_initialize(&request);
+       if (rc == 0)
+               goto free_reply;
+
+       rc = yaml_emitter_set_output_netlink(&request, sk, LNET_GENL_NAME,
+                                            LNET_GENL_VERSION,
+                                            LNET_CMD_CPT_OF_NID, NLM_F_DUMP);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_emitter_open(&request);
+       yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_mapping_start_event_initialize(&event, NULL,
+                                           (yaml_char_t *)YAML_MAP_TAG,
+                                           1, YAML_ANY_MAPPING_STYLE);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_scalar_event_initialize(&event, NULL,
+                                    (yaml_char_t *)YAML_STR_TAG,
+                                    (yaml_char_t *)"cpt-of-nid",
+                                    strlen("cpt-of-nid"), 1, 0,
+                                    YAML_PLAIN_SCALAR_STYLE);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_mapping_start_event_initialize(&event, NULL,
+                                           (yaml_char_t *)YAML_MAP_TAG,
+                                           1, YAML_ANY_MAPPING_STYLE);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_scalar_event_initialize(&event, NULL,
+                                    (yaml_char_t *)YAML_STR_TAG,
+                                    (yaml_char_t *)"nids",
+                                    strlen("nids"), 1, 0,
+                                    YAML_PLAIN_SCALAR_STYLE);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_sequence_start_event_initialize(&event, NULL,
+                                            (yaml_char_t *)YAML_SEQ_TAG,
+                                            1, YAML_FLOW_SEQUENCE_STYLE);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       for (i = start; i < end; i++) {
+               yaml_scalar_event_initialize(&event, NULL,
+                                            (yaml_char_t *)YAML_STR_TAG,
+                                            (yaml_char_t *)nids[i],
+                                            strlen(nids[i]), 1, 0,
+                                            YAML_PLAIN_SCALAR_STYLE);
+               rc = yaml_emitter_emit(&request, &event);
+               if (rc == 0)
+                       goto emitter_error;
+       }
+
+       yaml_sequence_end_event_initialize(&event);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_mapping_end_event_initialize(&event);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_mapping_end_event_initialize(&event);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       yaml_document_end_event_initialize(&event, 0);
+       rc = yaml_emitter_emit(&request, &event);
+       if (rc == 0)
+               goto emitter_error;
+
+       rc = yaml_emitter_close(&request);
+emitter_error:
+       if (rc == 0) {
+               yaml_emitter_log_error(&request, stderr);
+               rc = -EINVAL;
+       } else {
+               rc = yaml_lnet_cpt_of_nid_display(&reply);
+       }
+       yaml_emitter_delete(&request);
+free_reply:
+       if (rc == 0) {
+               yaml_lnet_print_error(NLM_F_DUMP, "cpt-of-nid",
+                                     yaml_parser_get_reader_error(&reply));
+               rc = -EINVAL;
+       }
+
+       yaml_parser_delete(&reply);
+       nl_socket_free(sk);
+
+       return rc == 1 ? 0 : rc;
+}
+
+static int jt_calc_cpt_of_nid(int argc, char **argv)
+{
+       int rc, opt;
+       const char *const short_options = "h";
+       static const struct option long_options[] = {
+       { .name = "help", .val = 'h' },
+       { .name = NULL } };
+
+       rc = check_cmd(cmd_list, "", "cpt-of-nid", 2, argc, argv);
+       if (rc)
+               return rc;
+
+       while ((opt = getopt_long(argc, argv, short_options,
+                                  long_options, NULL)) != -1) {
+               switch (opt) {
+               case 'h':
+               case '?':
+                       print_help(cmd_list, "", "cpt-of-nid");
+               default:
+                       return 0;
+               }
+       }
+
+       rc = yaml_lnet_cpt_of_nid(optind, argc, argv);
+       if (rc == -EOPNOTSUPP)
+               printf("Operation not supported\n");
+
+       return rc;
+}
+
 static int jt_config_lnet(int argc, char **argv)
 {
        struct cYAML *err_rc = NULL;