Whamcloud - gitweb
- added LNET self test (landing b_self_test).
authorisaac <isaac>
Fri, 22 Jun 2007 06:47:52 +0000 (06:47 +0000)
committerisaac <isaac>
Fri, 22 Jun 2007 06:47:52 +0000 (06:47 +0000)
34 files changed:
lnet/ChangeLog
lnet/Makefile.in
lnet/autoMakefile.am
lnet/autoconf/lustre-lnet.m4
lnet/include/libcfs/kp30.h
lnet/include/lnet/Makefile.am
lnet/include/lnet/lnetst.h [new file with mode: 0644]
lnet/lnet/lib-md.c
lnet/selftest/.cvsignore [new file with mode: 0644]
lnet/selftest/Makefile.in [new file with mode: 0644]
lnet/selftest/autoMakefile.am [new file with mode: 0644]
lnet/selftest/brw_test.c [new file with mode: 0644]
lnet/selftest/conctl.c [new file with mode: 0644]
lnet/selftest/conrpc.c [new file with mode: 0644]
lnet/selftest/conrpc.h [new file with mode: 0644]
lnet/selftest/console.c [new file with mode: 0644]
lnet/selftest/console.h [new file with mode: 0644]
lnet/selftest/framework.c [new file with mode: 0644]
lnet/selftest/module.c [new file with mode: 0644]
lnet/selftest/ping_test.c [new file with mode: 0644]
lnet/selftest/rpc.c [new file with mode: 0644]
lnet/selftest/rpc.h [new file with mode: 0644]
lnet/selftest/selftest.h [new file with mode: 0644]
lnet/selftest/timer.c [new file with mode: 0644]
lnet/selftest/timer.h [new file with mode: 0644]
lnet/selftest/workitem.c [new file with mode: 0644]
lnet/ulnds/socklnd/dispatch.h
lnet/utils/.cvsignore
lnet/utils/Makefile.am
lnet/utils/genlib.sh [new file with mode: 0755]
lnet/utils/lnetunload
lnet/utils/lst.c [new file with mode: 0644]
lnet/utils/lstclient.c [new file with mode: 0644]
lnet/utils/parser.c

index 99aeb85..4fac91f 100644 (file)
        ptllnd    - Portals 3.3 / UNICOS/lc 1.5.x, 2.0.x
        * bug fixes
 
+Severity   : major
+Bugzilla   : 10916
+Description: added LNET self test
+Details    : landing b_self_test
+
 Severity   : minor
 Frequency  : rare
 Bugzilla   : 12227
index 87b62f2..9109302 100644 (file)
@@ -2,6 +2,7 @@ subdir-m += libcfs
 
 lnet-subdirs += lnet
 lnet-subdirs += klnds
+lnet-subdirs += selftest
 subdir-m += $(lnet-subdirs)
 
 @INCLUDE_RULES@
index a0f7408..d8d062e 100644 (file)
@@ -3,7 +3,7 @@
 # This code is issued under the GNU General Public License.
 # See the file COPYING in this distribution
 
-SUBDIRS = libcfs lnet klnds ulnds doc utils include    \
+SUBDIRS = libcfs lnet klnds ulnds selftest doc utils include   \
        autoconf
 
 sources:
index 30eda45..94d7475 100644 (file)
@@ -1431,6 +1431,8 @@ lnet/libcfs/autoMakefile
 lnet/libcfs/linux/Makefile
 lnet/lnet/Makefile
 lnet/lnet/autoMakefile
+lnet/selftest/Makefile
+lnet/selftest/autoMakefile
 lnet/ulnds/Makefile
 lnet/ulnds/autoMakefile
 lnet/ulnds/socklnd/Makefile
index 4ce9846..04aba75 100644 (file)
@@ -545,6 +545,7 @@ extern int libcfs_ioctl_popdata(void *arg, void *buf, int size);
 #define IOC_LIBCFS_TESTPROTOCOMPAT         _IOWR('e', 60, IOCTL_LIBCFS_TYPE)
 #define IOC_LIBCFS_PING                    _IOWR('e', 61, IOCTL_LIBCFS_TYPE)
 #define IOC_LIBCFS_DEBUG_PEER              _IOWR('e', 62, IOCTL_LIBCFS_TYPE)
+#define IOC_LIBCFS_LNETST                  _IOWR('e', 63, IOCTL_LIBCFS_TYPE)
 /* lnd ioctls */
 #define IOC_LIBCFS_REGISTER_MYNID          _IOWR('e', 70, IOCTL_LIBCFS_TYPE)
 #define IOC_LIBCFS_CLOSE_CONNECTION        _IOWR('e', 71, IOCTL_LIBCFS_TYPE)
index a6e5159..3328874 100644 (file)
@@ -8,4 +8,4 @@ DIST_SUBDIRS := $(SUBDIRS)
 
 EXTRA_DIST = api.h api-support.h \
        lib-lnet.h lib-types.h lnet.h lnetctl.h types.h \
-        socklnd.h ptllnd.h ptllnd_wire.h
+        socklnd.h ptllnd.h ptllnd_wire.h lnetst.h
diff --git a/lnet/include/lnet/lnetst.h b/lnet/include/lnet/lnetst.h
new file mode 100644 (file)
index 0000000..768de14
--- /dev/null
@@ -0,0 +1,446 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ * 
+ * This file is part of Lustre, http://www.lustre.org
+ */
+
+#ifndef __LNET_ST_H__
+#define __LNET_ST_H__
+
+#include <libcfs/kp30.h>
+#include <lnet/lnet.h>
+#include <lnet/lib-types.h>
+
+#define LST_NAME_SIZE           32              /* max name buffer length */
+
+#define LSTIO_DEBUG             0xC00           /* debug */
+#define LSTIO_SESSION_NEW       0xC01           /* create session */
+#define LSTIO_SESSION_END       0xC02           /* end session */
+#define LSTIO_SESSION_INFO      0xC03           /* query session */
+#define LSTIO_GROUP_ADD         0xC10           /* add group */
+#define LSTIO_GROUP_LIST        0xC11           /* list all groups in session */
+#define LSTIO_GROUP_INFO        0xC12           /* query defailt infomation of specified group */
+#define LSTIO_GROUP_DEL         0xC13           /* delete group */
+#define LSTIO_NODES_ADD         0xC14           /* add nodes to specified group */
+#define LSTIO_GROUP_UPDATE      0xC15           /* update group */
+#define LSTIO_BATCH_ADD         0xC20           /* add batch */
+#define LSTIO_BATCH_START       0xC21           /* start batch */
+#define LSTIO_BATCH_STOP        0xC22           /* stop batch */
+#define LSTIO_BATCH_DEL         0xC23           /* delete batch */
+#define LSTIO_BATCH_LIST        0xC24           /* show all batches in the session */
+#define LSTIO_BATCH_INFO        0xC25           /* show defail of specified batch */
+#define LSTIO_TEST_ADD          0xC26           /* add test (to batch) */
+#define LSTIO_BATCH_QUERY       0xC27           /* query batch status */
+#define LSTIO_STAT_QUERY        0xC30           /* get stats */
+
+typedef struct {
+        lnet_nid_t              ses_nid;                /* nid of console node */
+        __u64                   ses_stamp;              /* time stamp */
+} lst_sid_t;                                            /*** session id */
+
+#define LST_INVALID_SID         ((const lst_sid_t){.ses_nid   = LNET_NID_ANY,\
+                                                   .ses_stamp = -1})
+
+typedef struct {
+        __u64                   bat_id;                 /* unique id in session */
+} lst_bid_t;                                            /*** batch id (group of tests) */
+
+/* Status of test node */
+#define LST_NODE_ACTIVE         0x1                     /* node in this session */
+#define LST_NODE_BUSY           0x2                     /* node is taken by other session */
+#define LST_NODE_DOWN           0x4                     /* node is down */
+#define LST_NODE_UNKNOWN        0x8                     /* node not in session */
+
+typedef struct {
+        lnet_process_id_t       nde_id;                 /* id of node */
+        int                     nde_state;              /* state of node */
+} lstcon_node_ent_t;                                    /*** node entry, for list_group command */
+
+typedef struct {
+        int                     nle_nnode;              /* # of nodes */
+        int                     nle_nactive;            /* # of active nodes */
+        int                     nle_nbusy;              /* # of busy nodes */
+        int                     nle_ndown;              /* # of down nodes */
+        int                     nle_nunknown;           /* # of unknown nodes */
+} lstcon_ndlist_ent_t;                                  /*** node_list entry, for list_batch command */
+
+typedef struct {
+        int                     tse_type;               /* test type */
+        int                     tse_loop;               /* loop count */
+        int                     tse_concur;             /* concurrency of test */
+} lstcon_test_ent_t;                                    /*** test summary entry, for list_batch command */
+
+typedef struct {
+        int                     bae_state;              /* batch status */
+        int                     bae_timeout;            /* batch timeout */
+        int                     bae_ntest;              /* # of tests in the batch */
+} lstcon_batch_ent_t;                                   /*** batch summary entry, for list_batch command */
+
+typedef struct {
+        lstcon_ndlist_ent_t     tbe_cli_nle;            /* client (group) node_list entry */
+        lstcon_ndlist_ent_t     tbe_srv_nle;            /* server (group) node_list entry */
+        union {
+                lstcon_test_ent_t  tbe_test;            /* test entry */
+                lstcon_batch_ent_t tbe_batch;           /* batch entry */
+        } u;
+} lstcon_test_batch_ent_t;                              /*** test/batch verbose information entry,
+                                                         *** for list_batch command */
+
+typedef struct {
+        struct list_head        rpe_link;               /* link chain */
+        lnet_process_id_t       rpe_peer;               /* peer's id */
+        struct timeval          rpe_stamp;              /* time stamp of RPC */
+        int                     rpe_state;              /* peer's state */
+        int                     rpe_rpc_errno;          /* RPC errno */
+
+        lst_sid_t               rpe_sid;                /* peer's session id */
+        int                     rpe_fwk_errno;          /* framework errno */
+        int                     rpe_priv[4];            /* private data */
+        char                    rpe_payload[0];         /* private reply payload */
+} lstcon_rpc_ent_t;
+
+typedef struct {
+        int                     trs_rpc_stat[4];        /* RPCs stat (0: total, 1: failed, 2: finished, 4: reserved */
+        int                     trs_rpc_errno;          /* RPC errno */
+        int                     trs_fwk_stat[8];        /* framework stat */
+        int                     trs_fwk_errno;          /* errno of the first remote error */
+        void                   *trs_fwk_private;        /* private framework stat */
+} lstcon_trans_stat_t;
+
+static inline int
+lstcon_rpc_stat_total(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_rpc_stat[0] : stat->trs_rpc_stat[0];
+}
+
+static inline int
+lstcon_rpc_stat_success(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_rpc_stat[1] : stat->trs_rpc_stat[1];
+}
+
+static inline int
+lstcon_rpc_stat_failure(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_rpc_stat[2] : stat->trs_rpc_stat[2];
+}
+
+static inline int
+lstcon_sesop_stat_success(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[0] : stat->trs_fwk_stat[0];
+}
+
+static inline int
+lstcon_sesop_stat_failure(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[1] : stat->trs_fwk_stat[1];
+}
+
+static inline int
+lstcon_sesqry_stat_active(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[0] : stat->trs_fwk_stat[0];
+}
+
+static inline int
+lstcon_sesqry_stat_busy(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[1] : stat->trs_fwk_stat[1];
+}
+
+static inline int
+lstcon_sesqry_stat_unknown(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[2] : stat->trs_fwk_stat[2];
+}
+
+static inline int
+lstcon_tsbop_stat_success(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[0] : stat->trs_fwk_stat[0];
+}
+
+static inline int
+lstcon_tsbop_stat_failure(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[1] : stat->trs_fwk_stat[1];
+}
+
+static inline int
+lstcon_tsbqry_stat_idle(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[0] : stat->trs_fwk_stat[0];
+}
+
+static inline int
+lstcon_tsbqry_stat_run(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[1] : stat->trs_fwk_stat[1];
+}
+
+static inline int
+lstcon_tsbqry_stat_failure(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[2] : stat->trs_fwk_stat[2];
+}
+
+static inline int
+lstcon_statqry_stat_success(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[0] : stat->trs_fwk_stat[0];
+}
+
+static inline int
+lstcon_statqry_stat_failure(lstcon_trans_stat_t *stat, int inc)
+{
+        return inc ? ++stat->trs_fwk_stat[1] : stat->trs_fwk_stat[1];
+}
+
+/* create a session */
+typedef struct {
+        int                     lstio_ses_key;          /* IN: local key */
+        int                     lstio_ses_timeout;      /* IN: session timeout */
+        int                     lstio_ses_force;        /* IN: force create ? */
+        lst_sid_t              *lstio_ses_idp;          /* OUT: session id */
+        int                     lstio_ses_nmlen;        /* IN: name length */
+        char                   *lstio_ses_namep;        /* IN: session name */
+} lstio_session_new_args_t;
+
+/* query current session */
+typedef struct {
+        lst_sid_t              *lstio_ses_idp;          /* OUT: session id */
+        int                    *lstio_ses_keyp;         /* OUT: local key */
+        lstcon_ndlist_ent_t    *lstio_ses_ndinfo;       /* OUT: */
+        int                     lstio_ses_nmlen;        /* IN: name length */
+        char                   *lstio_ses_namep;        /* OUT: session name */
+} lstio_session_info_args_t;
+
+/* delete a session */
+typedef struct {
+        int                     lstio_ses_key;          /* IN: session key */
+} lstio_session_end_args_t;
+
+#define LST_OPC_SESSION         1
+#define LST_OPC_GROUP           2
+#define LST_OPC_NODES           3
+#define LST_OPC_BATCHCLI        4
+#define LST_OPC_BATCHSRV        5
+
+typedef struct {
+        int                     lstio_dbg_key;          /* IN: session key */
+        int                     lstio_dbg_type;         /* IN: debug sessin|batch|group|nodes list */
+        int                     lstio_dbg_flags;        /* IN: reserved debug flags */
+        int                     lstio_dbg_timeout;      /* IN: timeout of debug */
+
+        int                     lstio_dbg_nmlen;        /* IN: len of name */
+        char                   *lstio_dbg_namep;        /* IN: name of group|batch */
+        int                     lstio_dbg_count;        /* IN: # of test nodes to debug */
+        lnet_process_id_t      *lstio_dbg_idsp;         /* IN: id of test nodes */
+        struct list_head       *lstio_dbg_resultp;      /* OUT: list head of result buffer */
+} lstio_debug_args_t;
+
+typedef struct {
+        int                     lstio_grp_key;          /* IN: session key */
+        int                     lstio_grp_nmlen;        /* IN: name length */
+        char                   *lstio_grp_namep;        /* IN: group name */
+} lstio_group_add_args_t;
+
+typedef struct {
+        int                     lstio_grp_key;          /* IN: session key */
+        int                     lstio_grp_nmlen;        /* IN: name length */
+        char                   *lstio_grp_namep;        /* IN: group name */
+} lstio_group_del_args_t;
+
+#define LST_GROUP_CLEAN         1                       /* remove inactive nodes in the group */
+#define LST_GROUP_REFRESH       2                       /* refresh inactive nodes in the group */
+#define LST_GROUP_RMND          3                       /* delete nodes from the group */
+
+typedef struct {
+        int                     lstio_grp_key;          /* IN: session key */
+        int                     lstio_grp_opc;          /* IN: OPC */
+        int                     lstio_grp_args;         /* IN: arguments */
+        int                     lstio_grp_nmlen;        /* IN: name length */
+        char                   *lstio_grp_namep;        /* IN: group name */
+        int                     lstio_grp_count;        /* IN: # of nodes id */
+        lnet_process_id_t      *lstio_grp_idsp;         /* IN: array of nodes */
+        struct list_head       *lstio_grp_resultp;      /* OUT: list head of result buffer */
+} lstio_group_update_args_t;
+
+typedef struct {
+        int                     lstio_grp_key;          /* IN: session key */
+        int                     lstio_grp_nmlen;        /* IN: name length */
+        char                   *lstio_grp_namep;        /* IN: group name */
+        int                     lstio_grp_count;        /* IN: # of nodes */
+        lnet_process_id_t      *lstio_grp_idsp;         /* IN: nodes */
+        struct list_head       *lstio_grp_resultp;      /* OUT: list head of result buffer */
+} lstio_group_nodes_args_t;
+
+typedef struct {
+        int                     lstio_grp_key;          /* IN: session key */
+        int                     lstio_grp_idx;          /* IN: group idx */
+        int                     lstio_grp_nmlen;        /* IN: name len */
+        char                   *lstio_grp_namep;        /* OUT: name */
+} lstio_group_list_args_t;
+
+typedef struct {
+        int                     lstio_grp_key;          /* IN: session key */
+        int                     lstio_grp_nmlen;        /* IN: name len */
+        char                   *lstio_grp_namep;        /* IN: name */
+        lstcon_ndlist_ent_t    *lstio_grp_entp;         /* OUT: description of group */
+
+        int                    *lstio_grp_idxp;         /* IN/OUT: node index */
+        int                    *lstio_grp_ndentp;       /* IN/OUT: # of nodent */
+        lstcon_node_ent_t      *lstio_grp_dentsp;       /* OUT: nodent array */
+} lstio_group_info_args_t;
+
+#define LST_DEFAULT_BATCH       "batch"                 /* default batch name */
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: batch name */
+} lstio_batch_add_args_t;
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: batch name */
+} lstio_batch_del_args_t;
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_timeout;      /* IN: timeout for the batch */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: batch name */
+        struct list_head       *lstio_bat_resultp;      /* OUT: list head of result buffer */
+} lstio_batch_run_args_t;
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_force;        /* IN: abort unfinished test RPC */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: batch name */
+        struct list_head       *lstio_bat_resultp;      /* OUT: list head of result buffer */
+} lstio_batch_stop_args_t;
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_testidx;      /* IN: test index */
+        int                     lstio_bat_client;       /* IN: is test client? */
+        int                     lstio_bat_timeout;      /* IN: timeout for waiting */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: batch name */
+        struct list_head       *lstio_bat_resultp;      /* OUT: list head of result buffer */
+} lstio_batch_query_args_t;
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_idx;          /* IN: index */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: batch name */
+} lstio_batch_list_args_t;
+
+typedef struct {
+        int                     lstio_bat_key;          /* IN: session key */
+        int                     lstio_bat_nmlen;        /* IN: name length */
+        char                   *lstio_bat_namep;        /* IN: name */
+        int                     lstio_bat_server;       /* IN: query server or not */
+        int                     lstio_bat_testidx;      /* IN: test index */
+        lstcon_test_batch_ent_t *lstio_bat_entp;        /* OUT: batch ent */
+
+        int                    *lstio_bat_idxp;         /* IN/OUT: index of node */
+        int                    *lstio_bat_ndentp;       /* IN/OUT: # of nodent */
+        lstcon_node_ent_t      *lstio_bat_dentsp;       /* array of nodent */
+} lstio_batch_info_args_t;
+
+/* add stat in session */
+typedef struct {
+        int                     lstio_sta_key;          /* IN: session key */
+        int                     lstio_sta_timeout;      /* IN: timeout for stat requst */
+        int                     lstio_sta_nmlen;        /* IN: group name length */
+        char                   *lstio_sta_namep;        /* IN: group name */
+        int                     lstio_sta_count;        /* IN: # of pid */
+        lnet_process_id_t      *lstio_sta_idsp;         /* IN: pid */
+        struct list_head       *lstio_sta_resultp;      /* OUT: list head of result buffer */
+} lstio_stat_args_t;
+
+typedef enum {
+        LST_TEST_BULK   = 1,
+        LST_TEST_PING   = 2
+} lst_test_type_t;
+
+/* create a test in a batch */
+#define LST_MAX_CONCUR          1024                    /* Max concurrency of test */
+
+typedef struct {
+        int                     lstio_tes_key;          /* IN: session key */
+        int                     lstio_tes_bat_nmlen;    /* IN: batch name len */
+        char                   *lstio_tes_bat_name;     /* IN: batch name */
+        int                     lstio_tes_type;         /* IN: test type */
+        int                     lstio_tes_oneside;      /* IN: one sided test */
+        int                     lstio_tes_loop;         /* IN: loop count */
+        int                     lstio_tes_concur;       /* IN: concurrency */
+
+        int                     lstio_tes_dist;         /* IN: node distribution in destination groups */
+        int                     lstio_tes_span;         /* IN: node span in destination groups */
+        int                     lstio_tes_sgrp_nmlen;   /* IN: source group name length */
+        char                   *lstio_tes_sgrp_name;    /* IN: group name */
+        int                     lstio_tes_dgrp_nmlen;   /* IN: destination group name length */
+        char                   *lstio_tes_dgrp_name;    /* IN: group name */
+
+        int                     lstio_tes_param_len;    /* IN: param buffer len */
+        void                   *lstio_tes_param;        /* IN: parameter for specified test:
+                                                               lstio_bulk_param_t,
+                                                               lstio_ping_param_t,
+                                                               ... more */
+        struct list_head       *lstio_tes_resultp;      /* OUT: list head of result buffer */
+} lstio_test_args_t;
+
+typedef enum {
+        LST_BRW_READ    = 1,
+        LST_BRW_WRITE   = 2
+} lst_brw_type_t;
+
+typedef enum {
+        LST_BRW_CHECK_NONE   = 1,
+        LST_BRW_CHECK_SIMPLE = 2,
+        LST_BRW_CHECK_FULL   = 3
+} lst_brw_flags_t;
+
+typedef struct {
+        int                     blk_opc;                /* bulk operation code */
+        int                     blk_npg;                /* # pages */
+        int                     blk_time;               /* time of running the test*/
+        int                     blk_flags;              /* reserved flags */
+} lst_test_bulk_param_t, lst_bulk_param_t;
+
+typedef struct {
+        int                     png_size;               /* size of ping message */
+        int                     png_time;               /* time */
+        int                     png_loop;               /* loop */
+        int                     png_flags;              /* reserved flags */
+} lst_test_ping_param_t, lst_ping_param_t;
+
+/* more tests */
+
+typedef struct {
+        __u32 errors;
+        __u32 rpcs_sent;
+        __u32 rpcs_rcvd;
+        __u32 rpcs_dropped;
+        __u32 rpcs_expired;
+        __u64 bulk_get;
+        __u64 bulk_put;
+} srpc_counters_t;
+
+typedef struct {
+        __u32 active_tests;
+        __u32 active_batches;
+        __u32 zombie_sessions;
+        __u32 brw_errors;
+} sfw_counters_t;
+
+#endif
index 0e8524c..57d92ae 100644 (file)
@@ -44,7 +44,7 @@ lnet_md_unlink(lnet_libmd_t *md)
                                 lnet_me_unlink(me);
                 }
 
-                /* emsure all future handle lookups fail */
+                /* ensure all future handle lookups fail */
                 lnet_invalidate_handle(&md->md_lh);
         }
 
diff --git a/lnet/selftest/.cvsignore b/lnet/selftest/.cvsignore
new file mode 100644 (file)
index 0000000..5ed596b
--- /dev/null
@@ -0,0 +1,10 @@
+.deps
+Makefile
+.*.cmd
+autoMakefile.in
+autoMakefile
+*.ko
+*.mod.c
+.*.flags
+.tmp_versions
+.depend
diff --git a/lnet/selftest/Makefile.in b/lnet/selftest/Makefile.in
new file mode 100644 (file)
index 0000000..8ebef75
--- /dev/null
@@ -0,0 +1,7 @@
+MODULES := lnet_selftest
+
+lnet_selftest-objs := console.o conrpc.o conctl.o framework.o timer.o rpc.o workitem.o module.o ping_test.o brw_test.o
+
+default: all
+
+@INCLUDE_RULES@
diff --git a/lnet/selftest/autoMakefile.am b/lnet/selftest/autoMakefile.am
new file mode 100644 (file)
index 0000000..36af901
--- /dev/null
@@ -0,0 +1,23 @@
+my_sources = console.c conrpc.c conctl.c console.h conrpc.h \
+            framework.c timer.c rpc.c workitem.c module.c \
+            ping_test.c brw_test.c
+
+if LIBLUSTRE
+noinst_LIBRARIES= libselftest.a
+libselftest_a_SOURCES= $(my_sources)
+libselftest_a_CPPFLAGS = $(LLCPPFLAGS)
+libselftest_a_CFLAGS = $(LLCFLAGS)
+endif
+
+if MODULES
+
+if LINUX
+modulenet_DATA = lnet_selftest$(KMODEXT)
+endif # LINUX
+
+endif # MODULES
+
+install-data-hook: $(install_data_hook)
+
+MOSTLYCLEANFILES = @MOSTLYCLEANFILES@ selftest
+DIST_SOURCES = $(lnet_selftest-objs:%.o=%.c) console.h conrpc.h rpc.h selftest.h timer.h
diff --git a/lnet/selftest/brw_test.c b/lnet/selftest/brw_test.c
new file mode 100644 (file)
index 0000000..ee2d024
--- /dev/null
@@ -0,0 +1,397 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Copyright (C) 2001, 2002 Cluster File Systems, Inc.
+ *   Author: Isaac Huang <isaac@clusterfs.com>
+ *
+ */
+
+#include <libcfs/kp30.h>
+#include "selftest.h"
+
+
+static int brw_inject_errors = 0;
+CFS_MODULE_PARM(brw_inject_errors, "i", int, 0644,
+                "# data errors to inject randomly");
+
+static void
+brw_client_fini (sfw_test_instance_t *tsi)
+{
+        srpc_bulk_t     *bulk;
+        sfw_test_unit_t *tsu;
+
+        list_for_each_entry (tsu, &tsi->tsi_units, tsu_list) {
+                bulk = tsu->tsu_private;
+                if (bulk == NULL) continue;
+
+                srpc_free_bulk(bulk);
+                tsu->tsu_private = NULL;
+        }
+}
+
+int
+brw_client_init (sfw_test_instance_t *tsi)
+{
+        test_bulk_req_t  *breq = &tsi->tsi_u.bulk;
+        int               flags = breq->blk_flags;
+        int               npg = breq->blk_npg;
+        srpc_bulk_t      *bulk;
+        sfw_test_unit_t  *tsu;
+
+        if (npg > LNET_MAX_IOV || npg <= 0)
+                return -EINVAL;
+
+        if (breq->blk_opc != LST_BRW_READ && breq->blk_opc != LST_BRW_WRITE)
+                return -EINVAL;
+
+        if (flags != LST_BRW_CHECK_NONE &&
+            flags != LST_BRW_CHECK_FULL && flags != LST_BRW_CHECK_SIMPLE)
+                return -EINVAL;
+
+        list_for_each_entry (tsu, &tsi->tsi_units, tsu_list) {
+                bulk = srpc_alloc_bulk(npg, breq->blk_opc == LST_BRW_READ);
+                if (bulk == NULL) {
+                        brw_client_fini(tsi);
+                        return -ENOMEM;
+                }
+
+                tsu->tsu_private = bulk;
+        }
+
+        return 0;
+}
+
+#define BRW_POISON      0xbeefbeefbeefbeefULL
+#define BRW_MAGIC       0xeeb0eeb1eeb2eeb3ULL
+#define BRW_MSIZE       sizeof(__u64)
+
+int
+brw_inject_one_error (void)
+{
+        struct timeval tv;
+
+        if (brw_inject_errors <= 0) return 0;
+
+#ifndef __KERNEL__
+        gettimeofday(&tv, NULL);
+#else
+        do_gettimeofday(&tv);
+#endif
+
+        if ((tv.tv_usec & 1) == 0) return 0;
+
+        return brw_inject_errors--;
+}
+
+void
+brw_fill_page (cfs_page_t *pg, int pattern, __u64 magic)
+{
+        char *addr = cfs_page_address(pg);
+        int   i;
+
+        LASSERT (addr != NULL);
+
+        if (pattern == LST_BRW_CHECK_NONE)
+                return;
+
+        if (magic == BRW_MAGIC)
+                magic += brw_inject_one_error();
+
+        if (pattern == LST_BRW_CHECK_SIMPLE) {
+                memcpy(addr, &magic, BRW_MSIZE);
+                addr += CFS_PAGE_SIZE - BRW_MSIZE;
+                memcpy(addr, &magic, BRW_MSIZE);
+                return;
+        }
+
+        if (pattern == LST_BRW_CHECK_FULL) {
+                for (i = 0; i < CFS_PAGE_SIZE / BRW_MSIZE; i++)
+                        memcpy(addr + i * BRW_MSIZE, &magic, BRW_MSIZE);
+                return;
+        }
+
+        LBUG ();
+        return;
+}
+
+int
+brw_check_page (cfs_page_t *pg, int pattern, __u64 magic)
+{
+        char  *addr = cfs_page_address(pg);
+        __u64  data;
+        int    i;
+
+        LASSERT (addr != NULL);
+
+        if (pattern == LST_BRW_CHECK_NONE)
+                return 0;
+
+        if (pattern == LST_BRW_CHECK_SIMPLE) {
+                data = *((__u64 *) addr);
+                if (data != magic) goto bad_data;
+
+                addr += CFS_PAGE_SIZE - BRW_MSIZE;
+                data = *((__u64 *) addr);
+                if (data != magic) goto bad_data;
+
+                return 0;
+        }
+
+        if (pattern == LST_BRW_CHECK_FULL) {
+                for (i = 0; i < CFS_PAGE_SIZE / BRW_MSIZE; i++) {
+                        data = *(((__u64 *) addr) + i);
+                        if (data != magic) goto bad_data;
+                }
+
+                return 0;
+        }
+
+        LBUG ();
+
+bad_data:
+        CERROR ("Bad data in page %p: "LPU64", "LPU64" expected\n",
+                pg, data, magic);
+        return 1;
+}
+
+void
+brw_fill_bulk (srpc_bulk_t *bk, int pattern, __u64 magic)
+{
+        int         i;
+        cfs_page_t *pg;
+
+        for (i = 0; i < bk->bk_niov; i++) {
+#ifdef __KERNEL__
+                pg = bk->bk_iovs[i].kiov_page;
+#else
+                pg = bk->bk_pages[i];
+#endif
+                brw_fill_page(pg, pattern, magic);
+        }
+}
+
+int
+brw_check_bulk (srpc_bulk_t *bk, int pattern, __u64 magic)
+{
+        int         i;
+        cfs_page_t *pg;
+
+        for (i = 0; i < bk->bk_niov; i++) {
+#ifdef __KERNEL__
+                pg = bk->bk_iovs[i].kiov_page;
+#else
+                pg = bk->bk_pages[i];
+#endif
+                if (brw_check_page(pg, pattern, magic) != 0) {
+                        CERROR ("Bulk page %p (%d/%d) is corrupted!\n",
+                                pg, i, bk->bk_niov);
+                        return 1;
+                }
+        }
+
+        return 0;
+}
+
+static int
+brw_client_prep_rpc (sfw_test_unit_t *tsu,
+                     lnet_process_id_t dest, srpc_client_rpc_t **rpcpp)
+{
+        srpc_bulk_t         *bulk = tsu->tsu_private;
+        sfw_test_instance_t *tsi = tsu->tsu_instance;
+        test_bulk_req_t     *breq = &tsi->tsi_u.bulk;
+        int                  npg = breq->blk_npg;
+        int                  flags = breq->blk_flags;
+        srpc_client_rpc_t   *rpc;
+        srpc_brw_reqst_t    *req;
+        int                  rc;
+
+        LASSERT (bulk != NULL);
+        LASSERT (bulk->bk_niov == npg);
+
+        rc = sfw_create_test_rpc(tsu, dest, npg, npg * CFS_PAGE_SIZE, &rpc);
+        if (rc != 0) return rc;
+
+        memcpy(&rpc->crpc_bulk, bulk, offsetof(srpc_bulk_t, bk_iovs[npg]));
+        if (breq->blk_opc == LST_BRW_WRITE)
+                brw_fill_bulk(&rpc->crpc_bulk, flags, BRW_MAGIC);
+        else
+                brw_fill_bulk(&rpc->crpc_bulk, flags, BRW_POISON);
+
+        req = &rpc->crpc_reqstmsg.msg_body.brw_reqst;
+        req->brw_flags = flags;
+        req->brw_rw    = breq->blk_opc; 
+        req->brw_len   = npg * CFS_PAGE_SIZE;
+
+        *rpcpp = rpc;
+        return 0;
+}
+
+static void
+brw_client_done_rpc (sfw_test_unit_t *tsu, srpc_client_rpc_t *rpc)
+{
+        __u64             magic = BRW_MAGIC;
+        srpc_msg_t       *msg = &rpc->crpc_replymsg;
+        srpc_brw_reply_t *reply = &msg->msg_body.brw_reply;
+        srpc_brw_reqst_t *reqst = &rpc->crpc_reqstmsg.msg_body.brw_reqst;
+        sfw_session_t    *sn = tsu->tsu_instance->tsi_batch->bat_session;
+
+        LASSERT (sn != NULL);
+
+#ifndef __KERNEL__
+        rpc->crpc_bulk.bk_pages = NULL;
+#endif
+
+        if (rpc->crpc_status != 0) {
+                CERROR ("BRW RPC to %s failed with %d\n",
+                        libcfs_id2str(rpc->crpc_dest), rpc->crpc_status);
+                return;
+        }
+
+        if (msg->msg_magic != SRPC_MSG_MAGIC) {
+                __swab64s(&magic);
+                __swab32s(&reply->brw_status);
+        }
+
+        if (tsu->tsu_error == 0)
+                tsu->tsu_error = -reply->brw_status;
+
+        CDEBUG (reply->brw_status ? D_WARNING : D_NET,
+                "BRW RPC to %s finished with brw_status: %d\n",
+                libcfs_id2str(rpc->crpc_dest), reply->brw_status);
+
+        if (reply->brw_status != 0) {
+                atomic_inc(&sn->sn_brw_errors);
+                return;
+        }
+
+        if (reqst->brw_rw == LST_BRW_WRITE) return;
+
+        if (brw_check_bulk(&rpc->crpc_bulk, reqst->brw_flags, magic) != 0) {
+                CERROR ("Bulk data from %s is corrupted!\n",
+                        libcfs_id2str(rpc->crpc_dest));
+                tsu->tsu_error = -EBADMSG;
+                atomic_inc(&sn->sn_brw_errors);
+        }
+
+        return;
+}
+
+void
+brw_server_rpc_done (srpc_server_rpc_t *rpc)
+{
+        srpc_bulk_t *blk = rpc->srpc_bulk;
+
+        if (blk == NULL) return;
+
+        if (rpc->srpc_status != 0)
+                CERROR ("Bulk transfer %s %s has failed: %d\n",
+                        blk->bk_sink ? "from" : "to",
+                        libcfs_id2str(rpc->srpc_peer), rpc->srpc_status);
+        else
+                CDEBUG (D_NET, "Transfered %d pages bulk data %s %s\n",
+                        blk->bk_niov, blk->bk_sink ? "from" : "to",
+                        libcfs_id2str(rpc->srpc_peer));
+
+        sfw_free_pages(rpc);
+}
+
+int
+brw_bulk_ready (srpc_server_rpc_t *rpc, int status)
+{
+        __u64             magic = BRW_MAGIC;
+        srpc_brw_reply_t *reply = &rpc->srpc_replymsg.msg_body.brw_reply;
+        srpc_brw_reqst_t *reqst;
+        srpc_msg_t       *reqstmsg;
+
+        LASSERT (rpc->srpc_bulk != NULL);
+        LASSERT (rpc->srpc_reqstbuf != NULL);
+
+        reqstmsg = &rpc->srpc_reqstbuf->buf_msg;
+        reqst = &reqstmsg->msg_body.brw_reqst;
+
+        if (status != 0) {
+                CERROR ("BRW bulk %s failed for RPC from %s: %d\n",
+                        reqst->brw_rw == LST_BRW_READ ? "READ" : "WRITE",
+                        libcfs_id2str(rpc->srpc_peer), status);
+                return -EIO;
+        }
+
+        if (reqst->brw_rw == LST_BRW_READ)
+                return 0;
+
+        if (reqstmsg->msg_magic != SRPC_MSG_MAGIC)
+                __swab64s(&magic);
+
+        if (brw_check_bulk(rpc->srpc_bulk, reqst->brw_flags, magic) != 0) {
+                CERROR ("Bulk data from %s is corrupted!\n",
+                        libcfs_id2str(rpc->srpc_peer));
+                reply->brw_status = EBADMSG;
+        }
+
+        return 0;
+}
+
+int
+brw_server_handle (srpc_server_rpc_t *rpc)
+{
+        srpc_service_t   *sv = rpc->srpc_service;
+        srpc_msg_t       *replymsg = &rpc->srpc_replymsg;
+        srpc_msg_t       *reqstmsg = &rpc->srpc_reqstbuf->buf_msg;
+        srpc_brw_reply_t *reply = &replymsg->msg_body.brw_reply;
+        srpc_brw_reqst_t *reqst = &reqstmsg->msg_body.brw_reqst;
+        int               rc;
+
+        LASSERT (sv->sv_id == SRPC_SERVICE_BRW);
+
+        if (reqstmsg->msg_magic != SRPC_MSG_MAGIC) {
+                LASSERT (reqstmsg->msg_magic == __swab32(SRPC_MSG_MAGIC));
+
+                __swab32s(&reqstmsg->msg_type);
+                __swab32s(&reqst->brw_rw);
+                __swab32s(&reqst->brw_len);
+                __swab32s(&reqst->brw_flags);
+                __swab64s(&reqst->brw_rpyid);
+                __swab64s(&reqst->brw_bulkid);
+        }
+        LASSERT (reqstmsg->msg_type == srpc_service2request(sv->sv_id));
+
+        rpc->srpc_done = brw_server_rpc_done;
+
+        if ((reqst->brw_rw != LST_BRW_READ && reqst->brw_rw != LST_BRW_WRITE) ||
+            reqst->brw_len == 0 || (reqst->brw_len & ~CFS_PAGE_MASK) != 0 ||
+            reqst->brw_len / CFS_PAGE_SIZE > LNET_MAX_IOV ||
+            (reqst->brw_flags != LST_BRW_CHECK_NONE &&
+             reqst->brw_flags != LST_BRW_CHECK_FULL &&
+             reqst->brw_flags != LST_BRW_CHECK_SIMPLE)) {
+                reply->brw_status = EINVAL;
+                return 0;
+        }
+        
+        reply->brw_status = 0;
+        rc = sfw_alloc_pages(rpc, reqst->brw_len / CFS_PAGE_SIZE,
+                             reqst->brw_rw == LST_BRW_WRITE);
+        if (rc != 0) return rc;
+
+        if (reqst->brw_rw == LST_BRW_READ)
+                brw_fill_bulk(rpc->srpc_bulk, reqst->brw_flags, BRW_MAGIC);
+        else
+                brw_fill_bulk(rpc->srpc_bulk, reqst->brw_flags, BRW_POISON);
+
+        return 0;
+}
+
+sfw_test_client_ops_t brw_test_client =
+{
+        .tso_init      = brw_client_init,
+        .tso_fini      = brw_client_fini,
+        .tso_prep_rpc  = brw_client_prep_rpc,
+        .tso_done_rpc  = brw_client_done_rpc,
+};
+
+srpc_service_t brw_test_service =
+{
+        .sv_name       = "brw test",
+        .sv_handler    = brw_server_handle,
+        .sv_bulk_ready = brw_bulk_ready,
+        .sv_id         = SRPC_SERVICE_BRW,
+};
diff --git a/lnet/selftest/conctl.c b/lnet/selftest/conctl.c
new file mode 100644 (file)
index 0000000..7cb1378
--- /dev/null
@@ -0,0 +1,879 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ *
+ * This file is part of Lustre, http://www.lustre.org
+ *
+ * IOC handle in kernel 
+ */
+#ifdef __KERNEL__
+
+#include <libcfs/libcfs.h>
+#include <lnet/lib-lnet.h>
+#include <lnet/lnetst.h>
+#include "console.h"
+
+int
+lst_session_new_ioctl(lstio_session_new_args_t *args) 
+{
+        char      *name;
+        int        rc;
+
+        if (args->lstio_ses_idp   == NULL || /* address for output sid */
+            args->lstio_ses_key   == 0 || /* no key is specified */
+            args->lstio_ses_namep == NULL || /* session name */
+            args->lstio_ses_nmlen <= 0 ||
+            args->lstio_ses_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+       
+        LIBCFS_ALLOC(name, args->lstio_ses_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+        
+        if (copy_from_user(name,
+                           args->lstio_ses_namep,
+                           args->lstio_ses_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_ses_nmlen + 1);
+                return -EFAULT;
+        }
+        
+        name[args->lstio_ses_nmlen] = 0;
+        
+        rc = lstcon_session_new(name,
+                             args->lstio_ses_key,
+                             args->lstio_ses_timeout,
+                             args->lstio_ses_force,
+                             args->lstio_ses_idp);
+        
+        LIBCFS_FREE(name, args->lstio_ses_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_session_end_ioctl(lstio_session_end_args_t *args)
+{
+        if (args->lstio_ses_key != console_session.ses_key)
+                return -EACCES;
+
+        return lstcon_session_end();
+}
+
+int
+lst_session_info_ioctl(lstio_session_info_args_t *args)
+{
+        /* no checking of key */
+
+        if (args->lstio_ses_idp   == NULL || /* address for ouput sid */
+            args->lstio_ses_keyp  == NULL || /* address for ouput key */
+            args->lstio_ses_ndinfo == NULL || /* address for output ndinfo */
+            args->lstio_ses_namep == NULL || /* address for ouput name */
+            args->lstio_ses_nmlen <= 0 ||
+            args->lstio_ses_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        return lstcon_session_info(args->lstio_ses_idp,
+                                   args->lstio_ses_keyp,
+                                   args->lstio_ses_ndinfo,
+                                   args->lstio_ses_namep,
+                                   args->lstio_ses_nmlen);
+}
+
+int
+lst_debug_ioctl(lstio_debug_args_t *args)
+{
+        char   *name   = NULL;
+        int     client = 1;
+        int     rc;
+
+        if (args->lstio_dbg_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_dbg_resultp == NULL)
+                return -EINVAL;
+
+        if (args->lstio_dbg_namep != NULL && /* name of batch/group */
+            (args->lstio_dbg_nmlen <= 0 ||
+             args->lstio_dbg_nmlen > LST_NAME_SIZE))
+                return -EINVAL;
+
+        if (args->lstio_dbg_namep != NULL) {
+                LIBCFS_ALLOC(name, args->lstio_dbg_nmlen + 1);
+                if (name == NULL)
+                        return -ENOMEM;
+
+                if (copy_from_user(name, args->lstio_dbg_namep,
+                                   args->lstio_dbg_nmlen)) {
+                        LIBCFS_FREE(name, args->lstio_dbg_nmlen + 1);
+
+                        return -EFAULT;
+                }
+
+                name[args->lstio_dbg_nmlen] = 0;
+        }
+
+        rc = -EINVAL;
+
+        switch (args->lstio_dbg_type) {
+        case LST_OPC_SESSION:
+                rc = lstcon_session_debug(args->lstio_dbg_timeout,
+                                          args->lstio_dbg_resultp);
+                break;
+
+        case LST_OPC_BATCHSRV:
+                client = 0;
+        case LST_OPC_BATCHCLI:
+                if (name == NULL)
+                        goto out;
+
+                rc = lstcon_batch_debug(args->lstio_dbg_timeout,
+                                        name, client, args->lstio_dbg_resultp);
+                break;
+
+        case LST_OPC_GROUP:
+                if (name == NULL)
+                        goto out;
+
+                rc = lstcon_group_debug(args->lstio_dbg_timeout,
+                                        name, args->lstio_dbg_resultp);
+                break;
+
+        case LST_OPC_NODES:
+                if (args->lstio_dbg_count <= 0 ||
+                    args->lstio_dbg_idsp == NULL)
+                        goto out;
+
+                rc = lstcon_nodes_debug(args->lstio_dbg_timeout,
+                                        args->lstio_dbg_count,
+                                        args->lstio_dbg_idsp,
+                                        args->lstio_dbg_resultp);
+                break;
+
+        default:
+                break;
+        }
+
+out:
+        if (name != NULL)
+                LIBCFS_FREE(name, args->lstio_dbg_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_group_add_ioctl(lstio_group_add_args_t *args)
+{
+        char           *name;
+        int             rc;
+
+        if (args->lstio_grp_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_grp_namep == NULL||
+            args->lstio_grp_nmlen <= 0 || 
+            args->lstio_grp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_grp_namep,
+                           args->lstio_grp_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_grp_nmlen);
+                return -EFAULT;
+        }
+
+        name[args->lstio_grp_nmlen] = 0;
+
+        rc = lstcon_group_add(name);
+
+        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_group_del_ioctl(lstio_group_del_args_t *args)
+{
+        int     rc;
+        char   *name;
+
+        if (args->lstio_grp_key != console_session.ses_key)
+                return -EACCES;
+        
+        if (args->lstio_grp_namep == NULL ||
+            args->lstio_grp_nmlen <= 0 || 
+            args->lstio_grp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_grp_namep,
+                           args->lstio_grp_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_grp_nmlen] = 0;
+
+        rc = lstcon_group_del(name);
+
+        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_group_update_ioctl(lstio_group_update_args_t *args)
+{
+        int     rc;
+        char   *name;
+
+        if (args->lstio_grp_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_grp_resultp == NULL ||
+            args->lstio_grp_namep == NULL ||
+            args->lstio_grp_nmlen <= 0 || 
+            args->lstio_grp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_grp_namep,
+                           args->lstio_grp_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_grp_nmlen] = 0;
+
+        switch (args->lstio_grp_opc) {
+        case LST_GROUP_CLEAN:
+                rc = lstcon_group_clean(name, args->lstio_grp_args);
+                break;
+
+        case LST_GROUP_REFRESH:
+                rc = lstcon_group_refresh(name, args->lstio_grp_resultp);
+                break;
+
+        case LST_GROUP_RMND:
+                if (args->lstio_grp_count  <= 0 ||
+                    args->lstio_grp_idsp == NULL) {
+                        rc = -EINVAL;
+                        break;
+                }
+                rc = lstcon_nodes_remove(name, args->lstio_grp_count,
+                                         args->lstio_grp_idsp,
+                                         args->lstio_grp_resultp);
+                break;
+
+        default:
+                rc = -EINVAL;
+                break;
+        }
+
+        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+        
+        return rc;
+}
+
+int
+lst_nodes_add_ioctl(lstio_group_nodes_args_t *args)
+{
+        int     rc;
+        char   *name;
+
+        if (args->lstio_grp_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_grp_idsp == NULL || /* array of ids */
+            args->lstio_grp_count <= 0 ||
+            args->lstio_grp_resultp == NULL ||
+            args->lstio_grp_namep == NULL ||
+            args->lstio_grp_nmlen <= 0 || 
+            args->lstio_grp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name, args->lstio_grp_namep,
+                           args->lstio_grp_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+
+                return -EFAULT;
+        }
+
+        name[args->lstio_grp_nmlen] = 0;
+
+        rc = lstcon_nodes_add(name, args->lstio_grp_count,
+                              args->lstio_grp_idsp,
+                              args->lstio_grp_resultp);
+
+        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_group_list_ioctl(lstio_group_list_args_t *args)
+{
+        if (args->lstio_grp_key != console_session.ses_key) 
+                return -EACCES;
+
+        if (args->lstio_grp_idx   < 0 ||
+            args->lstio_grp_namep == NULL ||
+            args->lstio_grp_nmlen <= 0 ||
+            args->lstio_grp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        return lstcon_group_list(args->lstio_grp_idx,
+                              args->lstio_grp_nmlen,
+                              args->lstio_grp_namep);
+}
+
+int
+lst_group_info_ioctl(lstio_group_info_args_t *args)
+{
+        char           *name;
+        int             ndent;
+        int             index;
+        int             rc;
+
+        if (args->lstio_grp_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_grp_namep == NULL ||
+            args->lstio_grp_nmlen <= 0 ||
+            args->lstio_grp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        if (args->lstio_grp_entp  == NULL && /* output: group entry */
+            args->lstio_grp_dentsp == NULL)  /* output: node entry */
+                return -EINVAL;
+
+        if (args->lstio_grp_dentsp != NULL) { /* have node entry */
+                if (args->lstio_grp_idxp == NULL || /* node index */
+                    args->lstio_grp_ndentp == NULL) /* # of node entry */
+                        return -EINVAL;
+                                
+                if (copy_from_user(&ndent,
+                                   args->lstio_grp_ndentp, sizeof(ndent)) ||
+                    copy_from_user(&index, args->lstio_grp_idxp, sizeof(index)))
+                        return -EFAULT;
+
+                if (ndent <= 0 || index < 0)
+                        return -EINVAL;
+        }
+
+        LIBCFS_ALLOC(name, args->lstio_grp_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_grp_namep,
+                           args->lstio_grp_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_grp_nmlen] = 0;
+
+        rc = lstcon_group_info(name, args->lstio_grp_entp,
+                               &index, &ndent, args->lstio_grp_dentsp);
+
+        LIBCFS_FREE(name, args->lstio_grp_nmlen + 1);
+
+        if (rc != 0) 
+                return rc;
+
+        if (args->lstio_grp_dentsp != NULL && 
+            (copy_to_user(args->lstio_grp_idxp, &index, sizeof(index)) ||
+             copy_to_user(args->lstio_grp_ndentp, &ndent, sizeof(ndent))))
+                rc = -EFAULT;
+
+        return 0;
+}
+
+int
+lst_batch_add_ioctl(lstio_batch_add_args_t *args)
+{
+        int             rc;
+        char           *name;
+
+        if (args->lstio_bat_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_bat_namep == NULL ||
+            args->lstio_bat_nmlen <= 0 ||
+            args->lstio_bat_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_bat_namep,
+                           args->lstio_bat_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_bat_nmlen] = 0;
+
+        rc = lstcon_batch_add(name);
+
+        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_batch_run_ioctl(lstio_batch_run_args_t *args)
+{
+        int             rc;
+        char           *name;
+
+        if (args->lstio_bat_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_bat_namep == NULL ||
+            args->lstio_bat_nmlen <= 0 ||
+            args->lstio_bat_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_bat_namep,
+                           args->lstio_bat_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_bat_nmlen] = 0;
+
+        rc = lstcon_batch_run(name, args->lstio_bat_timeout,
+                              args->lstio_bat_resultp);
+
+        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_batch_stop_ioctl(lstio_batch_stop_args_t *args)
+{
+        int             rc;
+        char           *name;
+
+        if (args->lstio_bat_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_bat_resultp == NULL ||
+            args->lstio_bat_namep == NULL ||
+            args->lstio_bat_nmlen <= 0 ||
+            args->lstio_bat_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_bat_namep,
+                           args->lstio_bat_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_bat_nmlen] = 0;
+
+        rc = lstcon_batch_stop(name, args->lstio_bat_force,
+                               args->lstio_bat_resultp);
+
+        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_batch_query_ioctl(lstio_batch_query_args_t *args)
+{
+        char   *name;
+        int     rc;
+
+        if (args->lstio_bat_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_bat_resultp == NULL ||
+            args->lstio_bat_namep == NULL ||
+            args->lstio_bat_nmlen <= 0 ||
+            args->lstio_bat_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        if (args->lstio_bat_testidx < 0)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_bat_namep,
+                           args->lstio_bat_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_bat_nmlen] = 0;
+
+        rc = lstcon_test_batch_query(name,
+                                     args->lstio_bat_testidx,
+                                     args->lstio_bat_client,
+                                     args->lstio_bat_timeout,
+                                     args->lstio_bat_resultp);
+
+        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+
+        return rc;
+}
+
+int
+lst_batch_list_ioctl(lstio_batch_list_args_t *args)
+{
+        if (args->lstio_bat_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_bat_idx   < 0 ||
+            args->lstio_bat_namep == NULL ||
+            args->lstio_bat_nmlen <= 0 ||
+            args->lstio_bat_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        return lstcon_batch_list(args->lstio_bat_idx,
+                              args->lstio_bat_nmlen,
+                              args->lstio_bat_namep);
+}
+
+int
+lst_batch_info_ioctl(lstio_batch_info_args_t *args)
+{
+        char           *name;
+        int             rc;
+        int             index;
+        int             ndent;
+
+        if (args->lstio_bat_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_bat_namep == NULL || /* batch name */
+            args->lstio_bat_nmlen <= 0 ||
+            args->lstio_bat_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        if (args->lstio_bat_entp == NULL && /* output: batch entry */
+            args->lstio_bat_dentsp == NULL) /* output: node entry */
+                return -EINVAL;
+
+        if (args->lstio_bat_dentsp != NULL) { /* have node entry */
+                if (args->lstio_bat_idxp == NULL || /* node index */
+                    args->lstio_bat_ndentp == NULL) /* # of node entry */
+                        return -EINVAL;
+                                
+                if (copy_from_user(&index, args->lstio_bat_idxp, sizeof(index)) ||
+                    copy_from_user(&ndent, args->lstio_bat_ndentp, sizeof(ndent)))
+                        return -EFAULT;
+
+                if (ndent <= 0 || index < 0)
+                        return -EINVAL;
+        }
+
+        LIBCFS_ALLOC(name, args->lstio_bat_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name,
+                           args->lstio_bat_namep, args->lstio_bat_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+                return -EFAULT;
+        }
+
+        name[args->lstio_bat_nmlen] = 0;
+
+        rc = lstcon_batch_info(name,
+                            args->lstio_bat_entp, args->lstio_bat_server,
+                            args->lstio_bat_testidx, &index, &ndent,
+                            args->lstio_bat_dentsp);
+
+        LIBCFS_FREE(name, args->lstio_bat_nmlen + 1);
+
+        if (rc != 0)
+                return rc;
+
+        if (args->lstio_bat_dentsp != NULL && 
+            (copy_to_user(args->lstio_bat_idxp, &index, sizeof(index)) ||
+             copy_to_user(args->lstio_bat_ndentp, &ndent, sizeof(ndent))))
+                rc = -EFAULT;
+
+        return rc;
+}
+
+int
+lst_stat_query_ioctl(lstio_stat_args_t *args)
+{
+        int             rc;
+        char           *name;
+
+        /* TODO: not finished */
+        if (args->lstio_sta_key != console_session.ses_key)
+                return -EACCES;
+
+        if (args->lstio_sta_resultp == NULL ||
+            (args->lstio_sta_namep  == NULL &&
+             args->lstio_sta_idsp   == NULL) ||
+            args->lstio_sta_nmlen <= 0 ||
+            args->lstio_sta_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        if (args->lstio_sta_idsp != NULL &&
+            args->lstio_sta_count <= 0)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_sta_nmlen + 1);
+        if (name == NULL)
+                return -ENOMEM;
+
+        if (copy_from_user(name, args->lstio_sta_namep,
+                           args->lstio_sta_nmlen)) {
+                LIBCFS_FREE(name, args->lstio_sta_nmlen + 1);
+                return -EFAULT;
+        }
+
+        if (args->lstio_sta_idsp == NULL) {
+                rc = lstcon_group_stat(name, args->lstio_sta_timeout,
+                                       args->lstio_sta_resultp);
+        } else {
+                rc = lstcon_nodes_stat(args->lstio_sta_count,
+                                       args->lstio_sta_idsp,
+                                       args->lstio_sta_timeout,
+                                       args->lstio_sta_resultp);
+        }
+
+        LIBCFS_FREE(name, args->lstio_sta_nmlen + 1);
+
+        return rc;
+}
+
+int lst_test_add_ioctl(lstio_test_args_t *args)
+{
+        char           *name;
+        char           *srcgrp = NULL;
+        char           *dstgrp = NULL;
+        void           *param = NULL;
+        int             rc = -ENOMEM;
+
+        if (args->lstio_tes_resultp == NULL ||
+            args->lstio_tes_bat_name == NULL || /* no specified batch */
+            args->lstio_tes_bat_nmlen <= 0 ||
+            args->lstio_tes_bat_nmlen > LST_NAME_SIZE ||
+            args->lstio_tes_sgrp_name == NULL || /* no source group */
+            args->lstio_tes_sgrp_nmlen <= 0 ||
+            args->lstio_tes_sgrp_nmlen > LST_NAME_SIZE ||
+            args->lstio_tes_dgrp_name == NULL || /* no target group */
+            args->lstio_tes_dgrp_nmlen <= 0 ||
+            args->lstio_tes_dgrp_nmlen > LST_NAME_SIZE)
+                return -EINVAL;
+
+        /* have parameter, check if parameter length is valid */
+        if (args->lstio_tes_param != NULL &&
+            (args->lstio_tes_param_len <= 0 ||
+             args->lstio_tes_param_len > CFS_PAGE_SIZE - sizeof(lstcon_test_t)))
+                return -EINVAL;
+
+        LIBCFS_ALLOC(name, args->lstio_tes_bat_nmlen + 1);
+        if (name == NULL)
+                return rc;
+
+        LIBCFS_ALLOC(srcgrp, args->lstio_tes_sgrp_nmlen + 1);
+        if (srcgrp == NULL) 
+                goto out;
+
+        LIBCFS_ALLOC(dstgrp, args->lstio_tes_dgrp_nmlen + 1);
+        if (srcgrp == NULL) 
+                goto out;
+
+        if (args->lstio_tes_param != NULL) {
+                LIBCFS_ALLOC(param, args->lstio_tes_param_len);
+                if (param == NULL) 
+                        goto out;
+        }
+
+        rc = -EFAULT;
+        if (copy_from_user(name,
+                           args->lstio_tes_bat_name,
+                           args->lstio_tes_bat_nmlen) ||
+            copy_from_user(srcgrp,
+                           args->lstio_tes_sgrp_name,
+                           args->lstio_tes_sgrp_nmlen) ||
+            copy_from_user(dstgrp,
+                           args->lstio_tes_dgrp_name,
+                           args->lstio_tes_dgrp_nmlen) ||
+            copy_from_user(param, args->lstio_tes_param,
+                           args->lstio_tes_param_len))
+                goto out;
+
+        rc = lstcon_test_add(name,
+                            args->lstio_tes_type,
+                            args->lstio_tes_loop,
+                            args->lstio_tes_concur,
+                            args->lstio_tes_dist, args->lstio_tes_span,
+                            srcgrp, dstgrp, param, args->lstio_tes_param_len,
+                            args->lstio_tes_resultp);
+out:
+        if (name != NULL)
+                LIBCFS_FREE(name, args->lstio_tes_bat_nmlen + 1);
+
+        if (srcgrp != NULL)
+                LIBCFS_FREE(srcgrp, args->lstio_tes_sgrp_nmlen + 1);
+
+        if (dstgrp != NULL)
+                LIBCFS_FREE(dstgrp, args->lstio_tes_dgrp_nmlen + 1);
+
+        if (param != NULL)
+                LIBCFS_FREE(param, args->lstio_tes_param_len);
+
+        return rc;
+}
+
+int
+lstcon_ioctl_entry(unsigned int cmd, struct libcfs_ioctl_data *data)
+{
+        char   *buf;
+        int     opc = data->ioc_u32[0];
+        int     rc;
+
+        if (cmd != IOC_LIBCFS_LNETST)
+                return -EINVAL;
+
+        if (data->ioc_plen1 > CFS_PAGE_SIZE)
+                return -EINVAL;
+
+        LIBCFS_ALLOC(buf, data->ioc_plen1);
+        if (buf == NULL)
+                return -ENOMEM;
+
+        /* copy in parameter */
+        if (copy_from_user(buf, data->ioc_pbuf1, data->ioc_plen1)) {
+                LIBCFS_FREE(buf, data->ioc_plen1);
+                return -EFAULT;
+        }
+
+        mutex_down(&console_session.ses_mutex);
+
+        console_session.ses_laststamp = cfs_time_current_sec();
+
+        if (console_session.ses_shutdown) {
+                rc = -ESHUTDOWN;
+                goto out;
+        }
+
+        if (console_session.ses_expired)
+                lstcon_session_end();
+
+        if (opc != LSTIO_SESSION_NEW &&
+            console_session.ses_state == LST_SESSION_NONE) {
+                CDEBUG(D_NET, "LST no active session\n");
+                rc = -ESRCH;
+                goto out;
+        }
+
+        memset(&console_session.ses_trans_stat, 0, sizeof(lstcon_trans_stat_t));
+        
+        switch (opc) {
+                case LSTIO_SESSION_NEW:
+                        rc = lst_session_new_ioctl((lstio_session_new_args_t *)buf);
+                        break;
+                case LSTIO_SESSION_END:
+                        rc = lst_session_end_ioctl((lstio_session_end_args_t *)buf);
+                        break;
+                case LSTIO_SESSION_INFO:
+                        rc = lst_session_info_ioctl((lstio_session_info_args_t *)buf);
+                        break;
+                case LSTIO_DEBUG:
+                        rc = lst_debug_ioctl((lstio_debug_args_t *)buf);
+                        break;
+                case LSTIO_GROUP_ADD:
+                        rc = lst_group_add_ioctl((lstio_group_add_args_t *)buf);
+                        break;
+                case LSTIO_GROUP_DEL:
+                        rc = lst_group_del_ioctl((lstio_group_del_args_t *)buf);
+                        break;
+                case LSTIO_GROUP_UPDATE:
+                        rc = lst_group_update_ioctl((lstio_group_update_args_t *)buf);
+                        break;
+                case LSTIO_NODES_ADD:
+                        rc = lst_nodes_add_ioctl((lstio_group_nodes_args_t *)buf);
+                        break;
+                case LSTIO_GROUP_LIST:
+                        rc = lst_group_list_ioctl((lstio_group_list_args_t *)buf);
+                        break;
+                case LSTIO_GROUP_INFO:
+                        rc = lst_group_info_ioctl((lstio_group_info_args_t *)buf);
+                        break;
+                case LSTIO_BATCH_ADD:
+                        rc = lst_batch_add_ioctl((lstio_batch_add_args_t *)buf);
+                        break;
+                case LSTIO_BATCH_START:
+                        rc = lst_batch_run_ioctl((lstio_batch_run_args_t *)buf);
+                        break;
+                case LSTIO_BATCH_STOP:
+                        rc = lst_batch_stop_ioctl((lstio_batch_stop_args_t *)buf);
+                        break;
+                case LSTIO_BATCH_QUERY:
+                        rc = lst_batch_query_ioctl((lstio_batch_query_args_t *)buf);
+                        break;
+                case LSTIO_BATCH_LIST:
+                        rc = lst_batch_list_ioctl((lstio_batch_list_args_t *)buf);
+                        break;
+                case LSTIO_BATCH_INFO:
+                        rc = lst_batch_info_ioctl((lstio_batch_info_args_t *)buf);
+                        break;
+                case LSTIO_TEST_ADD:
+                        rc = lst_test_add_ioctl((lstio_test_args_t *)buf);
+                        break;
+                case LSTIO_STAT_QUERY:
+                        rc = lst_stat_query_ioctl((lstio_stat_args_t *)buf);
+                        break;
+                default:
+                        rc = -EINVAL;
+        }
+
+        if (copy_to_user(data->ioc_pbuf2, &console_session.ses_trans_stat,
+                         sizeof(lstcon_trans_stat_t)))
+                rc = -EFAULT;
+out:
+        mutex_up(&console_session.ses_mutex);
+
+        LIBCFS_FREE(buf, data->ioc_plen1);
+
+        return rc;
+}
+
+EXPORT_SYMBOL(lstcon_ioctl_entry);
+
+#endif
diff --git a/lnet/selftest/conrpc.c b/lnet/selftest/conrpc.c
new file mode 100644 (file)
index 0000000..028bdc1
--- /dev/null
@@ -0,0 +1,1293 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ * 
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ *
+ * This file is part of Lustre, http://www.lustre.org
+ *
+ * Console framework rpcs
+ */
+#ifdef __KERNEL__
+
+#include <libcfs/libcfs.h>
+#include <lnet/lib-lnet.h>
+#include "timer.h"
+#include "conrpc.h"
+#include "console.h"
+
+void lstcon_rpc_stat_reply(int, srpc_msg_t *,
+                           lstcon_node_t *, lstcon_trans_stat_t *);
+
+static void
+lstcon_rpc_done(srpc_client_rpc_t *rpc)
+{
+        lstcon_rpc_t *crpc = (lstcon_rpc_t *)rpc->crpc_priv;
+
+        LASSERT (!list_empty(&crpc->crp_link));
+        LASSERT (crpc != NULL && rpc == crpc->crp_rpc);
+        LASSERT (crpc->crp_posted && !crpc->crp_finished);
+
+        spin_lock(&rpc->crpc_lock);
+
+        if (crpc->crp_trans == NULL) {
+                /* orphan RPC */
+                spin_lock(&console_session.ses_rpc_lock);
+
+                /* delete from orphan rpcs list */
+                console_session.ses_rpc_pending --;
+                list_del_init(&crpc->crp_link);
+
+                spin_unlock(&console_session.ses_rpc_lock);
+
+                spin_unlock(&rpc->crpc_lock);
+
+                /* release it */
+                lstcon_rpc_put(crpc);
+                return;
+        }
+
+        /* not an orphan RPC */
+        crpc->crp_finished = 1;
+
+        if (crpc->crp_stamp == 0) {
+                /* not aborted */
+                LASSERT (crpc->crp_status == 0);
+
+                crpc->crp_stamp  = cfs_time_current();
+                crpc->crp_status = rpc->crpc_status;
+        }
+
+        /* wakeup thread waiting on the group if 
+         * it's the last rpc in the group */
+        if (atomic_dec_and_test(&crpc->crp_trans->tas_remaining))
+                cfs_waitq_signal(&crpc->crp_trans->tas_waitq);
+
+        spin_unlock(&rpc->crpc_lock);
+}
+
+int
+lstcon_rpc_init(lstcon_node_t *nd, int service,
+                int npg, int cached, lstcon_rpc_t *crpc)
+{
+
+        crpc->crp_rpc = sfw_create_rpc(nd->nd_id, service, 
+                                       npg, npg * CFS_PAGE_SIZE,
+                                       lstcon_rpc_done, (void *)crpc);
+        if (crpc->crp_rpc == NULL)
+                return -ENOMEM;
+
+        crpc->crp_trans    = NULL;
+        crpc->crp_node     = nd;
+        crpc->crp_posted   = 0;
+        crpc->crp_finished = 0;
+        crpc->crp_unpacked = 0;
+        crpc->crp_status   = 0;
+        crpc->crp_stamp    = 0;
+        crpc->crp_static   = !cached;
+        CFS_INIT_LIST_HEAD(&crpc->crp_link);
+
+        return 0;
+}
+
+int
+lstcon_rpc_prep(lstcon_node_t *nd, int service,
+                int npg, lstcon_rpc_t **crpcpp)
+{
+        lstcon_rpc_t  *crpc = NULL;
+        int            rc;
+
+        spin_lock(&console_session.ses_rpc_lock);
+
+        if (!list_empty(&console_session.ses_rpc_freelist)) {
+                crpc = list_entry(console_session.ses_rpc_freelist.next,
+                                  lstcon_rpc_t, crp_link);
+                list_del(&crpc->crp_link);
+        }
+
+        spin_unlock(&console_session.ses_rpc_lock);
+
+        if (crpc == NULL) {
+                LIBCFS_ALLOC(crpc, sizeof(*crpc));
+                if (crpc == NULL)
+                        return -ENOMEM;
+        }
+
+        rc = lstcon_rpc_init(nd, service, npg, 1, crpc);
+        if (rc == 0) {
+                *crpcpp = crpc;
+                return 0;
+        }
+
+        LIBCFS_FREE(crpc, sizeof(*crpc));
+
+        return rc;
+}
+
+void
+lstcon_rpc_put(lstcon_rpc_t *crpc)
+{
+        srpc_bulk_t *bulk = &crpc->crp_rpc->crpc_bulk;
+        int          i;
+
+        LASSERT (list_empty(&crpc->crp_link));
+
+        for (i = 0; i < bulk->bk_niov; i++) {
+                if (bulk->bk_iovs[i].kiov_page == NULL)
+                        continue;
+
+                cfs_free_page(bulk->bk_iovs[i].kiov_page);
+        }
+
+        srpc_client_rpc_decref(crpc->crp_rpc);
+
+        if (crpc->crp_static) {
+                memset(crpc, 0, sizeof(*crpc));
+                crpc->crp_static = 1;
+                return;
+        }
+
+        spin_lock(&console_session.ses_rpc_lock);
+
+        list_add(&crpc->crp_link, &console_session.ses_rpc_freelist);
+
+        spin_unlock(&console_session.ses_rpc_lock);
+}
+
+void
+lstcon_rpc_post(lstcon_rpc_t *crpc)
+{
+        lstcon_rpc_trans_t *trans = crpc->crp_trans;
+
+        LASSERT (trans != NULL);
+
+        atomic_inc(&trans->tas_remaining);
+        crpc->crp_posted = 1;
+
+        sfw_post_rpc(crpc->crp_rpc);
+}
+
+static char *
+lstcon_rpc_trans_name(int transop)
+{
+        if (transop == LST_TRANS_SESNEW)
+                return "SESNEW";
+
+        if (transop == LST_TRANS_SESEND)
+                return "SESEND";
+
+        if (transop == LST_TRANS_SESQRY)
+                return "SESQRY";
+
+        if (transop == LST_TRANS_SESPING)
+                return "SESPING";
+
+        if (transop == LST_TRANS_TSBCLIADD)
+                return "TSBCLIADD";
+
+        if (transop == LST_TRANS_TSBSRVADD)
+                return "TSBSRVADD";
+
+        if (transop == LST_TRANS_TSBRUN)
+                return "TSBRUN";
+
+        if (transop == LST_TRANS_TSBSTOP)
+                return "TSBSTOP";
+
+        if (transop == LST_TRANS_TSBCLIQRY)
+                return "TSBCLIQRY";
+
+        if (transop == LST_TRANS_TSBSRVQRY)
+                return "TSBSRVQRY";
+
+        if (transop == LST_TRANS_STATQRY)
+                return "STATQRY";
+
+        return "Unknown";
+}
+
+int
+lstcon_rpc_trans_prep(struct list_head *translist,
+                      int transop, lstcon_rpc_trans_t **transpp)
+{
+        lstcon_rpc_trans_t *trans;
+
+        if (translist != NULL) {
+                list_for_each_entry(trans, translist, tas_link) {
+                        /* Can't enqueue two private transaction on
+                         * the same object */
+                        if ((trans->tas_opc & transop) == LST_TRANS_PRIVATE)
+                                return -EPERM;
+                }
+        }
+
+        /* create a trans group */
+        LIBCFS_ALLOC(trans, sizeof(*trans));
+        if (trans == NULL)
+                return -ENOMEM;
+        
+        trans->tas_opc = transop;
+
+        if (translist == NULL)       
+                CFS_INIT_LIST_HEAD(&trans->tas_olink);
+        else
+                list_add_tail(&trans->tas_olink, translist);
+
+        list_add_tail(&trans->tas_link, &console_session.ses_trans_list);
+
+        CFS_INIT_LIST_HEAD(&trans->tas_rpcs_list);
+        atomic_set(&trans->tas_remaining, 0);
+        cfs_waitq_init(&trans->tas_waitq);
+
+        *transpp = trans;
+
+        return 0;
+}
+
+void
+lstcon_rpc_trans_addreq(lstcon_rpc_trans_t *trans, lstcon_rpc_t *crpc)
+{
+        list_add_tail(&crpc->crp_link, &trans->tas_rpcs_list);
+        crpc->crp_trans = trans;
+}
+
+void
+lstcon_rpc_trans_abort(lstcon_rpc_trans_t *trans, int error)
+{
+        srpc_client_rpc_t *rpc;
+        lstcon_rpc_t      *crpc;
+        lstcon_node_t     *nd;
+
+        list_for_each_entry (crpc, &trans->tas_rpcs_list, crp_link) {
+                rpc = crpc->crp_rpc;
+
+                spin_lock(&rpc->crpc_lock);
+
+                if (!crpc->crp_posted || crpc->crp_stamp != 0) {
+                        /* rpc done or aborted already */
+                        spin_unlock(&rpc->crpc_lock);
+                        continue;
+                }
+
+                crpc->crp_stamp  = cfs_time_current();
+                crpc->crp_status = error;
+
+                spin_unlock(&rpc->crpc_lock);
+
+                sfw_abort_rpc(rpc);
+
+                if  (error != ETIMEDOUT)
+                        continue;
+
+                nd = crpc->crp_node;
+                if (cfs_time_after(nd->nd_stamp, crpc->crp_stamp))
+                        continue;
+
+                nd->nd_stamp = crpc->crp_stamp;
+                nd->nd_state = LST_NODE_DOWN;
+        }
+}
+
+static int
+lstcon_rpc_trans_check(lstcon_rpc_trans_t *trans)
+{
+        if (console_session.ses_shutdown &&
+            !list_empty(&trans->tas_olink)) /* It's not an end session RPC */
+                return 1;
+
+        return (atomic_read(&trans->tas_remaining) == 0) ? 1: 0;
+}
+
+int
+lstcon_rpc_trans_postwait(lstcon_rpc_trans_t *trans, int timeout)
+{
+        lstcon_rpc_t  *crpc;
+        int            rc;
+
+        if (list_empty(&trans->tas_rpcs_list))
+                return 0;
+
+        if (timeout < LST_TRANS_MIN_TIMEOUT)
+                timeout = LST_TRANS_MIN_TIMEOUT;
+
+        CDEBUG(D_NET, "Transaction %s started\n",
+               lstcon_rpc_trans_name(trans->tas_opc));
+
+        /* post all requests */
+        list_for_each_entry (crpc, &trans->tas_rpcs_list, crp_link) {
+                LASSERT (!crpc->crp_posted);
+
+                lstcon_rpc_post(crpc);
+        }
+
+        mutex_up(&console_session.ses_mutex);
+
+        rc = wait_event_interruptible_timeout(trans->tas_waitq,
+                                              lstcon_rpc_trans_check(trans),
+                                              timeout * HZ);
+
+        rc = (rc > 0)? 0: ((rc < 0)? -EINTR: -ETIMEDOUT);
+
+        mutex_down(&console_session.ses_mutex);
+
+        if (console_session.ses_shutdown)
+                rc = -ESHUTDOWN;
+
+        if (rc != 0) {
+                /* treat short timeout as canceled */
+                if (rc == -ETIMEDOUT && timeout < LST_TRANS_MIN_TIMEOUT * 2)
+                        rc = -EINTR;
+
+                lstcon_rpc_trans_abort(trans, rc);
+        }
+
+        CDEBUG(D_NET, "Transaction %s stopped: %d\n",
+               lstcon_rpc_trans_name(trans->tas_opc), rc);
+
+        lstcon_rpc_trans_stat(trans, lstcon_trans_stat());
+
+        return rc;
+}
+
+int
+lstcon_rpc_get_reply(lstcon_rpc_t *crpc, srpc_msg_t **msgpp)
+{
+        lstcon_node_t        *nd  = crpc->crp_node;
+        srpc_client_rpc_t    *rpc = crpc->crp_rpc;
+        srpc_generic_reply_t *rep;
+
+        LASSERT (nd != NULL && rpc != NULL);
+        LASSERT (crpc->crp_stamp != 0);
+
+        if (crpc->crp_status != 0) {
+                *msgpp = NULL;
+                return crpc->crp_status;
+        }
+
+        *msgpp = &rpc->crpc_replymsg;
+        if (!crpc->crp_unpacked) {
+                sfw_unpack_message(*msgpp);
+                crpc->crp_unpacked = 1;
+        }
+       
+        if (cfs_time_after(nd->nd_stamp, crpc->crp_stamp))
+                return 0;
+
+        nd->nd_stamp = crpc->crp_stamp;
+        rep = &(*msgpp)->msg_body.reply;
+
+        if (rep->sid.ses_nid == LNET_NID_ANY)
+                nd->nd_state = LST_NODE_UNKNOWN;
+        else if (lstcon_session_match(rep->sid))
+                nd->nd_state = LST_NODE_ACTIVE;
+        else
+                nd->nd_state = LST_NODE_BUSY;
+
+        return 0;
+}
+
+void
+lstcon_rpc_trans_stat(lstcon_rpc_trans_t *trans, lstcon_trans_stat_t *stat)
+{
+        lstcon_rpc_t      *crpc;
+        srpc_client_rpc_t *rpc;
+        srpc_msg_t        *rep;
+        int                error;
+
+        LASSERT (stat != NULL);
+
+        memset(stat, 0, sizeof(*stat));
+
+        list_for_each_entry(crpc, &trans->tas_rpcs_list, crp_link) {
+                lstcon_rpc_stat_total(stat, 1);
+
+                rpc = crpc->crp_rpc;
+
+                LASSERT (crpc->crp_stamp != 0);
+
+                error = lstcon_rpc_get_reply(crpc, &rep);
+                if (error != 0) {
+                        lstcon_rpc_stat_failure(stat, 1);
+                        if (stat->trs_rpc_errno == 0)
+                                stat->trs_rpc_errno = -error;
+
+                        continue;
+                }
+
+                lstcon_rpc_stat_success(stat, 1);
+
+                lstcon_rpc_stat_reply(trans->tas_opc, rep,
+                                      crpc->crp_node, stat);
+        }
+
+        CDEBUG(D_NET, "transaction %s success, %d failure, %d total %d, "
+                      "RPC error(%d), Framework error(%d)\n",
+               lstcon_rpc_trans_name(trans->tas_opc),
+               lstcon_rpc_stat_success(stat, 0),
+               lstcon_rpc_stat_failure(stat, 0),
+               lstcon_rpc_stat_total(stat, 0),
+               stat->trs_rpc_errno, stat->trs_fwk_errno);
+
+        return;
+}
+
+int
+lstcon_rpc_trans_interpreter(lstcon_rpc_trans_t *trans,
+                             struct list_head *head_up,
+                             lstcon_rpc_readent_func_t readent)
+{
+        struct list_head      tmp;
+        struct list_head     *next;
+        lstcon_rpc_ent_t     *ent;
+        srpc_generic_reply_t *rep;
+        srpc_client_rpc_t    *rpc;
+        lstcon_rpc_t         *crpc;
+        srpc_msg_t           *msg;
+        lstcon_node_t        *nd;
+        cfs_duration_t        dur;
+        struct timeval        tv;
+        int                   error;
+
+        LASSERT (head_up != NULL);
+
+        next = head_up;
+
+        list_for_each_entry(crpc, &trans->tas_rpcs_list, crp_link) {
+                if (copy_from_user(&tmp, next, sizeof(struct list_head)))
+                        return -EFAULT;
+
+                if (tmp.next == head_up)
+                        return 0;
+
+                next = tmp.next;
+
+                ent = list_entry(next, lstcon_rpc_ent_t, rpe_link);
+
+                rpc = crpc->crp_rpc;
+
+                LASSERT (crpc->crp_stamp != 0);
+
+                error = lstcon_rpc_get_reply(crpc, &msg);
+
+                nd = crpc->crp_node;
+
+                dur = cfs_time_sub(crpc->crp_stamp,
+                                   console_session.ses_id.ses_stamp);
+                cfs_duration_usec(dur, &tv);
+
+                if (copy_to_user(&ent->rpe_peer,
+                                 &nd->nd_id, sizeof(lnet_process_id_t)) ||
+                    copy_to_user(&ent->rpe_stamp, &tv, sizeof(tv)) ||
+                    copy_to_user(&ent->rpe_state,
+                                 &nd->nd_state, sizeof(nd->nd_state)) ||
+                    copy_to_user(&ent->rpe_rpc_errno, &error, sizeof(error)))
+                        return -EFAULT;
+
+                if (error != 0)
+                        continue;
+
+                /* RPC is done */
+                rep = (srpc_generic_reply_t *)&msg->msg_body.reply;
+
+                if (copy_to_user(&ent->rpe_sid,
+                                 &rep->sid, sizeof(lst_sid_t)) ||
+                    copy_to_user(&ent->rpe_fwk_errno,
+                                 &rep->status, sizeof(rep->status)))
+                        return -EFAULT;
+
+                if (readent == NULL)
+                        continue;
+
+                if ((error = readent(trans->tas_opc, msg, ent)) != 0)
+                        return error;
+        }
+
+        return 0;
+}
+
+void
+lstcon_rpc_trans_destroy(lstcon_rpc_trans_t *trans)
+{
+        srpc_client_rpc_t *rpc;
+        lstcon_rpc_t      *crpc;
+        lstcon_rpc_t      *tmp;
+        int                count = 0;
+        
+        list_for_each_entry_safe(crpc, tmp,
+                                 &trans->tas_rpcs_list, crp_link) {
+                rpc = crpc->crp_rpc;
+
+                spin_lock(&rpc->crpc_lock);
+
+                /* free it if not posted or finished already */
+                if (!crpc->crp_posted || crpc->crp_finished) {
+                        spin_unlock(&rpc->crpc_lock);
+
+                        list_del_init(&crpc->crp_link);
+                        lstcon_rpc_put(crpc);
+
+                        continue;
+                }
+
+                /* rpcs can be still not callbacked (even LNetMDUnlink is called)
+                 * because huge timeout for inaccessible network, don't make
+                 * user wait for them, just put rpcs in orphan list */
+
+                LASSERT (crpc->crp_status != 0);
+
+                crpc->crp_node  = NULL;
+                crpc->crp_trans = NULL;
+                list_del(&crpc->crp_link);
+
+                spin_lock(&console_session.ses_rpc_lock);
+
+                count ++;
+                /* add to orphan list */
+                console_session.ses_rpc_pending ++;
+                list_add_tail(&crpc->crp_link, &console_session.ses_rpc_list);
+
+                spin_unlock(&console_session.ses_rpc_lock);
+
+                spin_unlock(&rpc->crpc_lock);
+
+                atomic_dec(&trans->tas_remaining);
+        }
+
+        LASSERT (atomic_read(&trans->tas_remaining) == 0);
+
+        list_del(&trans->tas_link);
+        if (!list_empty(&trans->tas_olink))
+                list_del(&trans->tas_olink);
+
+        CDEBUG(D_NET, "Transaction %s destroyed with %d pending RPCs\n",
+               lstcon_rpc_trans_name(trans->tas_opc), count);
+
+        LIBCFS_FREE(trans, sizeof(*trans));
+
+        return;
+}
+
+int
+lstcon_sesrpc_prep(lstcon_node_t *nd, int transop, lstcon_rpc_t **crpc)
+{
+        srpc_mksn_reqst_t *msrq;
+        srpc_rmsn_reqst_t *rsrq;
+        int                rc;
+
+        switch (transop) {
+        case LST_TRANS_SESNEW:
+                rc = lstcon_rpc_prep(nd, SRPC_SERVICE_MAKE_SESSION, 0, crpc);
+                if (rc != 0)
+                        return rc;
+
+                msrq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.mksn_reqst;
+                msrq->mksn_sid     = console_session.ses_id;
+                msrq->mksn_force   = console_session.ses_force;
+                strncpy(msrq->mksn_name, console_session.ses_name,
+                        strlen(console_session.ses_name));
+                break;
+
+        case LST_TRANS_SESEND:
+                rc = lstcon_rpc_prep(nd, SRPC_SERVICE_REMOVE_SESSION, 0, crpc);
+                if (rc != 0)
+                        return rc;
+
+                rsrq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.rmsn_reqst;
+                rsrq->rmsn_sid = console_session.ses_id;
+                break;
+
+        default:
+                LBUG();
+        }
+
+        return 0;
+}
+
+int
+lstcon_dbgrpc_prep(lstcon_node_t *nd, lstcon_rpc_t **crpc)
+{
+        srpc_debug_reqst_t *drq;
+        int                 rc;
+
+        rc = lstcon_rpc_prep(nd, SRPC_SERVICE_DEBUG, 0, crpc);
+        if (rc != 0)
+                return rc;
+
+        drq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.dbg_reqst;
+
+        drq->dbg_sid   = console_session.ses_id;
+        drq->dbg_flags = 0;
+        
+        return rc;
+}
+
+int
+lstcon_batrpc_prep(lstcon_node_t *nd, int transop,
+                   lstcon_tsb_hdr_t *tsb, lstcon_rpc_t **crpc)
+{
+        lstcon_batch_t     *batch;
+        srpc_batch_reqst_t *brq;
+        int                 rc;
+
+        rc = lstcon_rpc_prep(nd, SRPC_SERVICE_BATCH, 0, crpc);
+        if (rc != 0)
+                return rc;
+
+        brq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.bat_reqst;
+
+        brq->bar_sid     = console_session.ses_id;
+        brq->bar_bid     = tsb->tsb_id;
+        brq->bar_testidx = tsb->tsb_index;
+        brq->bar_opc     = transop == LST_TRANS_TSBRUN ? SRPC_BATCH_OPC_RUN :
+                           (transop == LST_TRANS_TSBSTOP ? SRPC_BATCH_OPC_STOP:
+                            SRPC_BATCH_OPC_QUERY);
+
+        if (transop != LST_TRANS_TSBRUN &&
+            transop != LST_TRANS_TSBSTOP)
+                return 0;
+
+        LASSERT (tsb->tsb_index == 0);
+
+        batch = (lstcon_batch_t *)tsb;
+        brq->bar_arg = batch->bat_arg;
+        
+        return 0;
+}
+
+int
+lstcon_statrpc_prep(lstcon_node_t *nd, lstcon_rpc_t **crpc)
+{
+        srpc_stat_reqst_t *srq;
+        int                rc;
+
+        rc = lstcon_rpc_prep(nd, SRPC_SERVICE_QUERY_STAT, 0, crpc);
+        if (rc != 0)
+                return rc;
+
+        srq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.stat_reqst;
+
+        srq->str_sid  = console_session.ses_id;
+        srq->str_type = 0; /* XXX remove it */
+
+        return 0;
+}
+
+lnet_process_id_t *
+lstcon_next_id(int idx, int nkiov, lnet_kiov_t *kiov)
+{
+        lnet_process_id_t *pid;
+        int                i;
+
+        i = idx / (CFS_PAGE_SIZE / sizeof(lnet_process_id_t));
+        
+        LASSERT (i < nkiov);
+
+        pid = (lnet_process_id_t *)cfs_page_address(kiov[i].kiov_page);
+
+        return &pid[idx % (CFS_PAGE_SIZE / sizeof(lnet_process_id_t))];
+}
+
+int
+lstcon_dstnodes_prep(lstcon_group_t *grp, int idx,
+                     int dist, int span, int nkiov, lnet_kiov_t *kiov)
+{
+        lnet_process_id_t *pid;
+        lstcon_ndlink_t   *ndl;
+        lstcon_node_t     *nd;
+        int                start;
+        int                end;
+        int                i = 0;
+
+        LASSERT (dist >= 1);
+        LASSERT (span >= 1);
+        LASSERT (grp->grp_nnode >= 1);
+
+        if (span > grp->grp_nnode)
+                return -EINVAL;
+
+        start = ((idx / dist) * span) % grp->grp_nnode;
+        end   = ((idx / dist) * span + span - 1) % grp->grp_nnode;
+
+        list_for_each_entry(ndl, &grp->grp_ndl_list, ndl_link) {
+                nd = ndl->ndl_node;
+                if (i < start) {
+                        i ++;
+                        continue;
+                }
+
+                if (i > (end >= start ? end: grp->grp_nnode))
+                        break;
+
+                pid = lstcon_next_id((i - start), nkiov, kiov);
+                *pid = nd->nd_id;
+                i++;
+        }
+
+        if (start <= end) /* done */
+                return 0;
+
+        list_for_each_entry(ndl, &grp->grp_ndl_list, ndl_link) {
+                if (i > grp->grp_nnode + end)
+                        break;
+
+                nd = ndl->ndl_node;
+                pid = lstcon_next_id((i - start), nkiov, kiov);
+                *pid = nd->nd_id;
+                i++;
+        }
+
+        return 0;
+}
+
+int
+lstcon_pingrpc_prep(lst_test_ping_param_t *param, srpc_test_reqst_t *req)
+{
+        test_ping_req_t *prq = &req->tsr_u.ping;
+        
+        prq->png_size   = param->png_size;
+        prq->png_flags  = param->png_flags;
+        /* TODO dest */
+        return 0;
+}
+
+int
+lstcon_bulkrpc_prep(lst_test_bulk_param_t *param, srpc_test_reqst_t *req)
+{
+        test_bulk_req_t *brq = &req->tsr_u.bulk;
+
+        brq->blk_opc    = param->blk_opc;
+        brq->blk_npg    = param->blk_npg;
+        brq->blk_flags  = param->blk_flags;
+
+        return 0;
+}
+
+int
+lstcon_testrpc_prep(lstcon_node_t *nd, int transop,
+                    lstcon_test_t *test, lstcon_rpc_t **crpc)
+{
+        lstcon_group_t    *sgrp = test->tes_src_grp;
+        lstcon_group_t    *dgrp = test->tes_dst_grp;
+        srpc_test_reqst_t *trq;
+        srpc_bulk_t       *bulk;
+        int                i;
+        int                n  = 0;
+        int                rc = 0;
+
+        if (transop == LST_TRANS_TSBCLIADD)
+                n = sfw_id_pages(test->tes_span);
+
+        rc = lstcon_rpc_prep(nd, SRPC_SERVICE_TEST, n, crpc);
+        if (rc != 0) 
+                return rc;
+
+        trq  = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.tes_reqst;
+
+        if (transop == LST_TRANS_TSBSRVADD) {
+                int ndist = (sgrp->grp_nnode + test->tes_dist - 1) / test->tes_dist;
+                int nspan = (dgrp->grp_nnode + test->tes_span - 1) / test->tes_span;
+                int nmax = (ndist + nspan - 1) / nspan;
+
+                trq->tsr_ndest = 0;
+                trq->tsr_loop  = nmax * test->tes_dist * test->tes_concur;
+
+        } else {
+                bulk = &(*crpc)->crp_rpc->crpc_bulk;
+
+                for (i = 0; i < n; i++) {
+                        bulk->bk_iovs[i].kiov_offset = 0;
+                        bulk->bk_iovs[i].kiov_len    = CFS_PAGE_SIZE;
+                        bulk->bk_iovs[i].kiov_page   = cfs_alloc_page(CFS_ALLOC_STD);
+
+                        if (bulk->bk_iovs[i].kiov_page != NULL) 
+                                continue;
+
+                        lstcon_rpc_put(*crpc);
+                        return -ENOMEM;
+                }
+
+                bulk->bk_sink = 0;
+
+                LASSERT (transop == LST_TRANS_TSBCLIADD);
+
+                rc = lstcon_dstnodes_prep(test->tes_dst_grp,
+                                          test->tes_cliidx++, test->tes_dist,
+                                          test->tes_span, n, &bulk->bk_iovs[0]);
+                if (rc != 0) {
+                        lstcon_rpc_put(*crpc);
+                        return rc;
+                }
+
+                trq->tsr_ndest = test->tes_span;
+                trq->tsr_loop  = test->tes_loop;
+        } 
+
+        trq->tsr_sid        = console_session.ses_id;
+        trq->tsr_bid        = test->tes_hdr.tsb_id;
+        trq->tsr_concur     = test->tes_concur;
+        trq->tsr_is_client  = (transop == LST_TRANS_TSBCLIADD) ? 1 : 0;
+        trq->tsr_stop_onerr = test->tes_stop_onerr;
+
+        switch (test->tes_type) {
+        case LST_TEST_PING:
+                trq->tsr_service = SRPC_SERVICE_PING;
+                rc = lstcon_pingrpc_prep((lst_test_ping_param_t *)&test->tes_param[0], trq);
+                break;
+        case LST_TEST_BULK:
+                trq->tsr_service = SRPC_SERVICE_BRW;
+                rc = lstcon_bulkrpc_prep((lst_test_bulk_param_t *)&test->tes_param[0], trq);
+                break;
+        default:
+                LBUG();
+                break;
+        }
+
+        return rc;
+}
+
+void
+lstcon_rpc_stat_reply(int transop, srpc_msg_t *msg,
+                      lstcon_node_t *nd, lstcon_trans_stat_t *stat)
+{
+        srpc_mksn_reply_t  *mksn_rep;
+        srpc_rmsn_reply_t  *rmsn_rep;
+        srpc_debug_reply_t *dbg_rep;
+        srpc_batch_reply_t *bat_rep;
+        srpc_test_reply_t  *test_rep;
+        srpc_stat_reply_t  *stat_rep;
+        int                 errno = 0;
+
+        switch (transop) {
+        case LST_TRANS_SESNEW:
+                mksn_rep = &msg->msg_body.mksn_reply;
+
+                if (mksn_rep->mksn_status == 0) {
+                        lstcon_sesop_stat_success(stat, 1);
+                        /* session timeout on remote node */
+                        nd->nd_timeout = mksn_rep->mksn_timeout;
+                        return;
+                }
+
+                LASSERT (mksn_rep->mksn_status == EBUSY ||
+                         mksn_rep->mksn_status == EINVAL);
+
+                lstcon_sesop_stat_failure(stat, 1);
+                errno = mksn_rep->mksn_status;
+                break;
+
+        case LST_TRANS_SESEND:
+                rmsn_rep = &msg->msg_body.rmsn_reply;
+                /* ESRCH is not an error for end session */
+                if (rmsn_rep->rmsn_status == 0 ||
+                    rmsn_rep->rmsn_status == ESRCH) {
+                        lstcon_sesop_stat_success(stat, 1);
+                        return;
+                }
+
+                LASSERT (rmsn_rep->rmsn_status == EBUSY ||
+                         rmsn_rep->rmsn_status == EINVAL);
+
+                lstcon_sesop_stat_failure(stat, 1);
+                errno = rmsn_rep->rmsn_status;
+                break;
+
+        case LST_TRANS_SESQRY:
+        case LST_TRANS_SESPING:
+                dbg_rep = &msg->msg_body.dbg_reply;
+
+                if (dbg_rep->dbg_status == ESRCH) {
+                        lstcon_sesqry_stat_unknown(stat, 1);
+                        return;
+                } 
+
+                LASSERT (dbg_rep->dbg_status == 0);
+
+                if (lstcon_session_match(dbg_rep->dbg_sid))
+                        lstcon_sesqry_stat_active(stat, 1);
+                else
+                        lstcon_sesqry_stat_busy(stat, 1);
+                return;
+
+        case LST_TRANS_TSBRUN:
+        case LST_TRANS_TSBSTOP:
+                bat_rep = &msg->msg_body.bat_reply;
+
+                if (bat_rep->bar_status == 0) {
+                        lstcon_tsbop_stat_success(stat, 1);
+                        return;
+                }
+
+                if (bat_rep->bar_status == EPERM && 
+                    transop == LST_TRANS_TSBSTOP) {
+                        lstcon_tsbop_stat_success(stat, 1);
+                        return;
+                }
+
+                lstcon_tsbop_stat_failure(stat, 1);
+                errno = bat_rep->bar_status;
+                break;
+
+        case LST_TRANS_TSBCLIQRY:
+        case LST_TRANS_TSBSRVQRY:
+                bat_rep = &msg->msg_body.bat_reply;
+
+                if (bat_rep->bar_active != 0) 
+                        lstcon_tsbqry_stat_run(stat, 1);
+                else
+                        lstcon_tsbqry_stat_idle(stat, 1);
+
+                if (bat_rep->bar_status == 0) 
+                        return;
+
+                lstcon_tsbqry_stat_failure(stat, 1);
+                errno = bat_rep->bar_status;
+                break;
+
+        case LST_TRANS_TSBCLIADD:
+        case LST_TRANS_TSBSRVADD:
+                test_rep = &msg->msg_body.tes_reply;
+
+                if (test_rep->tsr_status == 0) {
+                        lstcon_tsbop_stat_success(stat, 1);
+                        return;
+                }
+
+                lstcon_tsbop_stat_failure(stat, 1);
+                errno = test_rep->tsr_status;
+                break;
+
+        case LST_TRANS_STATQRY:
+                stat_rep = &msg->msg_body.stat_reply;
+
+                if (stat_rep->str_status == 0) {
+                        lstcon_statqry_stat_success(stat, 1);
+                        return;
+                }
+
+                lstcon_statqry_stat_failure(stat, 1);
+                errno = stat_rep->str_status;
+                break;
+
+        default:
+                LBUG();
+        }
+
+        if (stat->trs_fwk_errno == 0)
+                stat->trs_fwk_errno = errno;
+
+        return;
+}
+
+int
+lstcon_rpc_trans_ndlist(struct list_head *ndlist,
+                        struct list_head *translist, int transop, void *arg,
+                        lstcon_rpc_cond_func_t condition, lstcon_rpc_trans_t **transpp)
+{
+        lstcon_rpc_trans_t *trans;
+        lstcon_ndlink_t    *ndl;
+        lstcon_node_t      *nd;
+        lstcon_rpc_t       *rpc;
+        int                 rc;
+
+        /* Creating session RPG for list of nodes */
+
+        rc = lstcon_rpc_trans_prep(translist, transop, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction %d: %d\n", transop, rc);
+                return rc;
+        }
+
+        list_for_each_entry(ndl, ndlist, ndl_link) {
+                rc = condition == NULL ? 1 :
+                     condition(transop, ndl->ndl_node, arg);
+
+                if (rc == 0)
+                        continue;
+
+                if (rc < 0) {
+                        CDEBUG(D_NET, "Condition error while creating RPC "
+                                      " for transaction %d: %d\n", transop, rc);
+                        break;
+                }
+
+                nd = ndl->ndl_node;
+
+                switch (transop) {
+                case LST_TRANS_SESNEW:
+                case LST_TRANS_SESEND:
+                        rc = lstcon_sesrpc_prep(nd, transop, &rpc);
+                        break;
+                case LST_TRANS_SESQRY:
+                case LST_TRANS_SESPING:
+                        rc = lstcon_dbgrpc_prep(nd, &rpc);
+                        break;
+                case LST_TRANS_TSBCLIADD:
+                case LST_TRANS_TSBSRVADD:
+                        rc = lstcon_testrpc_prep(nd, transop,
+                                                 (lstcon_test_t *)arg, &rpc);
+                        break;
+                case LST_TRANS_TSBRUN:
+                case LST_TRANS_TSBSTOP:
+                case LST_TRANS_TSBCLIQRY:
+                case LST_TRANS_TSBSRVQRY:
+                        rc = lstcon_batrpc_prep(nd, transop,
+                                                (lstcon_tsb_hdr_t *)arg, &rpc);
+                        break;
+                case LST_TRANS_STATQRY:
+                        rc = lstcon_statrpc_prep(nd, &rpc);
+                        break;
+                default:
+                        rc = -EINVAL;
+                        break;
+                }
+
+                if (rc != 0) {
+                        CERROR("Failed to create RPC for transaction %s: %d\n",
+                               lstcon_rpc_trans_name(transop), rc);
+                        break;
+                }
+                                
+                lstcon_rpc_trans_addreq(trans, rpc);
+        }
+
+        if (rc == 0) {
+                *transpp = trans;
+                return 0;
+        }
+
+        lstcon_rpc_trans_destroy(trans);
+
+        return rc;
+}
+
+void
+lstcon_rpc_pinger(void *arg)
+{
+        stt_timer_t        *ptimer = (stt_timer_t *)arg;
+        lstcon_rpc_trans_t *trans;
+        lstcon_rpc_t       *crpc;
+        srpc_msg_t         *rep;
+        srpc_debug_reqst_t *drq;
+        lstcon_ndlink_t    *ndl;
+        lstcon_node_t      *nd;
+        time_t              intv;
+        int                 count = 0;
+        int                 rc;
+
+        /* RPC pinger is a special case of transaction,
+         * it's called by timer at 8 seconds interval.
+         */
+        mutex_down(&console_session.ses_mutex);
+
+        if (console_session.ses_shutdown || console_session.ses_expired) {
+                mutex_up(&console_session.ses_mutex);
+                return;
+        }
+
+        if (!console_session.ses_expired &&
+            cfs_time_current_sec() - console_session.ses_laststamp >
+            console_session.ses_timeout)
+                console_session.ses_expired = 1;
+
+        trans = console_session.ses_ping;
+
+        LASSERT (trans != NULL);
+
+        list_for_each_entry(ndl, &console_session.ses_ndl_list, ndl_link) {
+                nd = ndl->ndl_node;
+
+                if (console_session.ses_expired) {
+                        /* idle console, end session on all nodes */
+                        if (nd->nd_state != LST_NODE_ACTIVE)
+                                continue;
+
+                        rc = lstcon_sesrpc_prep(nd, LST_TRANS_SESEND, &crpc);
+                        if (rc != 0) {
+                                CERROR("Out of memory\n");
+                                break;
+                        }
+
+                        lstcon_rpc_trans_addreq(trans, crpc);
+                        lstcon_rpc_post(crpc);
+
+                        continue;
+                }
+
+                crpc = &nd->nd_ping;
+
+                if (crpc->crp_rpc != NULL) {
+                        LASSERT (crpc->crp_trans == trans);
+                        LASSERT (!list_empty(&crpc->crp_link));
+
+                        spin_lock(&crpc->crp_rpc->crpc_lock);
+
+                        LASSERT (crpc->crp_posted);
+
+                        if (!crpc->crp_finished) {
+                                /* in flight */
+                                spin_unlock(&crpc->crp_rpc->crpc_lock);
+                                continue;
+                        }
+
+                        spin_unlock(&crpc->crp_rpc->crpc_lock);
+
+                        lstcon_rpc_get_reply(crpc, &rep);
+
+                        list_del_init(&crpc->crp_link);
+                
+                        lstcon_rpc_put(crpc);
+                }
+
+                if (nd->nd_state != LST_NODE_ACTIVE)
+                        continue;
+
+                intv = cfs_duration_sec(cfs_time_sub(cfs_time_current(),
+                                                     nd->nd_stamp));
+                if (intv < nd->nd_timeout / 2)
+                        continue;
+
+                rc = lstcon_rpc_init(nd, SRPC_SERVICE_DEBUG, 0, 0, crpc);
+                if (rc != 0) {
+                        CERROR("Out of memory\n");
+                        break;
+                }
+
+                drq = &crpc->crp_rpc->crpc_reqstmsg.msg_body.dbg_reqst;
+
+                drq->dbg_sid   = console_session.ses_id;
+                drq->dbg_flags = 0;
+
+                lstcon_rpc_trans_addreq(trans, crpc);
+                lstcon_rpc_post(crpc);
+
+                count ++;
+        }
+
+        if (console_session.ses_expired) {
+                mutex_up(&console_session.ses_mutex);
+                return;
+        }
+
+        CDEBUG(D_NET, "Ping %d nodes in session\n", count);
+
+        ptimer->stt_expires = cfs_time_current_sec() + LST_PING_INTERVAL;
+        stt_add_timer(ptimer);
+
+        mutex_up(&console_session.ses_mutex);
+}
+
+int
+lstcon_rpc_pinger_start(void)
+{
+        stt_timer_t    *ptimer;
+        int             rc;
+
+        LASSERT (console_session.ses_rpc_pending == 0);
+        LASSERT (list_empty(&console_session.ses_rpc_list));
+        LASSERT (list_empty(&console_session.ses_rpc_freelist));
+
+        rc = lstcon_rpc_trans_prep(NULL, LST_TRANS_SESPING,
+                                   &console_session.ses_ping);
+        if (rc != 0) {
+                CERROR("Failed to create console pinger\n");
+                return rc;
+        }
+
+        ptimer = &console_session.ses_ping_timer;
+        ptimer->stt_expires = cfs_time_current_sec() + LST_PING_INTERVAL;
+
+        stt_add_timer(ptimer);
+
+        return 0;
+}
+
+void
+lstcon_rpc_pinger_stop(void)
+{
+        LASSERT (console_session.ses_shutdown);
+
+        stt_del_timer(&console_session.ses_ping_timer);
+
+        lstcon_rpc_trans_abort(console_session.ses_ping, -ESHUTDOWN);
+        lstcon_rpc_trans_stat(console_session.ses_ping, lstcon_trans_stat());
+        lstcon_rpc_trans_destroy(console_session.ses_ping);
+
+        memset(lstcon_trans_stat(), 0, sizeof(lstcon_trans_stat_t));
+
+        console_session.ses_ping = NULL;
+}
+
+void
+lstcon_rpc_cleanup_wait(void)
+{
+        lstcon_rpc_trans_t *trans;
+        lstcon_rpc_t       *crpc;
+        struct list_head   *pacer;
+        struct list_head    zlist;
+
+        LASSERT (console_session.ses_shutdown);
+
+        while (!list_empty(&console_session.ses_trans_list)) { 
+                list_for_each(pacer, &console_session.ses_trans_list) {
+                        trans = list_entry(pacer, lstcon_rpc_trans_t, tas_link);
+                        cfs_waitq_signal(&trans->tas_waitq);
+
+                        CDEBUG(D_NET, "Session closed, wakeup transaction %s\n",
+                               lstcon_rpc_trans_name(trans->tas_opc));
+                }
+
+                mutex_up(&console_session.ses_mutex);
+
+                CWARN("Session is shutting down, close all transactions\n");
+                cfs_pause(cfs_time_seconds(1));
+
+                mutex_down(&console_session.ses_mutex);
+        }
+
+        spin_lock(&console_session.ses_rpc_lock);
+
+        lst_wait_until(list_empty(&console_session.ses_rpc_list),
+                       console_session.ses_rpc_lock,
+                       "Network is not accessable or target is down, "
+                       "waiting for %d console rpcs to die\n",
+                       console_session.ses_rpc_pending);
+
+        list_add(&zlist, &console_session.ses_rpc_freelist);
+        list_del_init(&console_session.ses_rpc_freelist);
+
+        spin_unlock(&console_session.ses_rpc_lock);
+
+        LASSERT (console_session.ses_rpc_pending == 0);
+
+        while (!list_empty(&zlist)) {
+                crpc = list_entry(zlist.next, lstcon_rpc_t, crp_link);
+
+                list_del(&crpc->crp_link);
+                LIBCFS_FREE(crpc, sizeof(lstcon_rpc_t));
+        }
+}
+
+int
+lstcon_rpc_module_init(void)
+{
+        CFS_INIT_LIST_HEAD(&console_session.ses_ping_timer.stt_list);
+        console_session.ses_ping_timer.stt_func = lstcon_rpc_pinger;
+        console_session.ses_ping_timer.stt_data = &console_session.ses_ping_timer;
+
+        console_session.ses_ping = NULL;
+        console_session.ses_rpc_pending = 0;
+        spin_lock_init(&console_session.ses_rpc_lock);
+        CFS_INIT_LIST_HEAD(&console_session.ses_rpc_list);
+        CFS_INIT_LIST_HEAD(&console_session.ses_rpc_freelist);
+
+        return 0;
+}
+
+void
+lstcon_rpc_module_fini(void)
+{
+        LASSERT (console_session.ses_rpc_pending == 0);
+        LASSERT (list_empty(&console_session.ses_rpc_list));
+        LASSERT (list_empty(&console_session.ses_rpc_freelist));
+}
+
+#endif
diff --git a/lnet/selftest/conrpc.h b/lnet/selftest/conrpc.h
new file mode 100644 (file)
index 0000000..ba6a21b
--- /dev/null
@@ -0,0 +1,103 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ * 
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ * 
+ * This file is part of Lustre, http://www.lustre.org
+ *
+ * Console rpc
+ */
+
+#ifndef __LST_CONRPC_H__
+#define __LST_CONRPC_H__
+
+#ifdef __KERNEL__
+#include <libcfs/kp30.h>
+#include <lnet/lnet.h>
+#include <lnet/lib-types.h>
+#include <lnet/lnetst.h>
+#include "rpc.h"
+#include "selftest.h"
+
+/* Console rpc and rpc transaction */
+#define LST_TRANS_TIMEOUT       30
+#define LST_TRANS_MIN_TIMEOUT   3
+#define LST_PING_INTERVAL       8
+
+struct lstcon_rpc_trans;
+struct lstcon_tsb_hdr;
+struct lstcon_test;
+struct lstcon_node;
+
+typedef struct lstcon_rpc {
+        struct list_head        crp_link;       /* chain on rpc transaction */
+        srpc_client_rpc_t      *crp_rpc;        /* client rpc */
+        struct lstcon_node     *crp_node;       /* destination node */
+        struct lstcon_rpc_trans *crp_trans;     /* conrpc transaction */
+
+        int                     crp_posted:1;   /* rpc is posted */
+        int                     crp_finished:1; /* rpc is finished */
+        int                     crp_unpacked:1; /* reply is unpacked */
+        int                     crp_static:1;   /* not from RPC buffer */
+        int                     crp_status;     /* console rpc errors */
+        cfs_time_t              crp_stamp;      /* replied time stamp */
+} lstcon_rpc_t;
+
+typedef struct lstcon_rpc_trans {
+        struct list_head        tas_olink;      /* link chain on owner list */
+        struct list_head        tas_link;       /* link chain on global list */
+        int                     tas_opc;        /* operation code of transaction */
+        cfs_waitq_t             tas_waitq;      /* wait queue head */
+        atomic_t                tas_remaining;  /* # of un-scheduled rpcs */
+        struct list_head        tas_rpcs_list;  /* queued requests */
+} lstcon_rpc_trans_t;
+
+#define LST_TRANS_PRIVATE       0x1000
+
+#define LST_TRANS_SESNEW        (LST_TRANS_PRIVATE | 0x01)
+#define LST_TRANS_SESEND        (LST_TRANS_PRIVATE | 0x02)
+#define LST_TRANS_SESQRY        0x03
+#define LST_TRANS_SESPING       0x04
+
+#define LST_TRANS_TSBCLIADD     (LST_TRANS_PRIVATE | 0x11)
+#define LST_TRANS_TSBSRVADD     (LST_TRANS_PRIVATE | 0x12)
+#define LST_TRANS_TSBRUN        (LST_TRANS_PRIVATE | 0x13)
+#define LST_TRANS_TSBSTOP       (LST_TRANS_PRIVATE | 0x14)
+#define LST_TRANS_TSBCLIQRY     0x15
+#define LST_TRANS_TSBSRVQRY     0x16
+
+#define LST_TRANS_STATQRY       0x21
+
+typedef int (* lstcon_rpc_cond_func_t)(int, struct lstcon_node *, void *);
+typedef int (* lstcon_rpc_readent_func_t)(int, srpc_msg_t *, lstcon_rpc_ent_t *);
+
+int lstcon_sesrpc_prep(struct lstcon_node *nd, 
+                       int transop, lstcon_rpc_t **crpc);
+int lstcon_dbgrpc_prep(struct lstcon_node *nd, lstcon_rpc_t **crpc);
+int lstcon_batrpc_prep(struct lstcon_node *nd,
+                       int transop, struct lstcon_tsb_hdr *tsb, lstcon_rpc_t **crpc);
+int lstcon_testrpc_prep(struct lstcon_node *nd,
+                        int transop, struct lstcon_test *test, lstcon_rpc_t **crpc);
+int lstcon_statrpc_prep(struct lstcon_node *nd, lstcon_rpc_t **crpc);
+void lstcon_rpc_put(lstcon_rpc_t *crpc);
+int lstcon_rpc_trans_prep(struct list_head *translist,
+                          int transop, lstcon_rpc_trans_t **transpp);
+int lstcon_rpc_trans_ndlist(struct list_head *ndlist, struct list_head *translist,
+                            int transop, void *arg, lstcon_rpc_cond_func_t condition,
+                            lstcon_rpc_trans_t **transpp);
+void lstcon_rpc_trans_stat(lstcon_rpc_trans_t *trans, lstcon_trans_stat_t *stat);
+int lstcon_rpc_trans_interpreter(lstcon_rpc_trans_t *trans, struct list_head *head_up,
+                                 lstcon_rpc_readent_func_t readent);
+void lstcon_rpc_trans_abort(lstcon_rpc_trans_t *trans, int error);
+void lstcon_rpc_trans_destroy(lstcon_rpc_trans_t *trans);
+void lstcon_rpc_trans_addreq(lstcon_rpc_trans_t *trans, lstcon_rpc_t *req);
+int lstcon_rpc_trans_postwait(lstcon_rpc_trans_t *trans, int timeout);
+int lstcon_rpc_pinger_start(void);
+void lstcon_rpc_pinger_stop(void);
+void lstcon_rpc_cleanup_wait(void);
+int lstcon_rpc_module_init(void);
+void lstcon_rpc_module_fini(void);
+
+#endif
+
+#endif  
diff --git a/lnet/selftest/console.c b/lnet/selftest/console.c
new file mode 100644 (file)
index 0000000..f474c0b
--- /dev/null
@@ -0,0 +1,1979 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ * 
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ *
+ * This file is part of Lustre, http://www.lustre.org
+ *
+ * Infrastructure of LST console
+ */
+#ifdef __KERNEL__
+
+#include <libcfs/libcfs.h>
+#include <lnet/lib-lnet.h>
+#include "console.h"
+#include "conrpc.h"
+
+#define LST_NODE_STATE_COUNTER(nd, p)                   \
+do {                                                    \
+        if ((nd)->nd_state == LST_NODE_ACTIVE)          \
+                (p)->nle_nactive ++;                    \
+        else if ((nd)->nd_state == LST_NODE_BUSY)       \
+                (p)->nle_nbusy ++;                      \
+        else if ((nd)->nd_state == LST_NODE_DOWN)       \
+                (p)->nle_ndown ++;                      \
+        else                                            \
+                (p)->nle_nunknown ++;                   \
+        (p)->nle_nnode ++;                              \
+} while (0)
+
+lstcon_session_t        console_session;
+
+void
+lstcon_node_get(lstcon_node_t *nd)
+{
+        LASSERT (nd->nd_ref >= 1);
+
+        nd->nd_ref++;
+}
+
+static int
+lstcon_node_find(lnet_process_id_t id, lstcon_node_t **ndpp, int create)
+{
+        lstcon_ndlink_t *ndl;
+        unsigned int     idx = LNET_NIDADDR(id.nid) % LST_GLOBAL_HASHSIZE;
+
+        LASSERT (id.nid != LNET_NID_ANY);
+
+        list_for_each_entry(ndl, &console_session.ses_ndl_hash[idx], ndl_hlink) {
+                if (ndl->ndl_node->nd_id.nid != id.nid ||
+                    ndl->ndl_node->nd_id.pid != id.pid)
+                        continue;
+
+                lstcon_node_get(ndl->ndl_node);
+                *ndpp = ndl->ndl_node;
+                return 0;
+        }
+        
+        if (!create)
+                return -ENOENT;
+
+        LIBCFS_ALLOC(*ndpp, sizeof(lstcon_node_t) + sizeof(lstcon_ndlink_t));
+        if (*ndpp == NULL)
+                return -ENOMEM;
+
+        ndl = (lstcon_ndlink_t *)(*ndpp + 1);
+
+        ndl->ndl_node = *ndpp;
+
+        ndl->ndl_node->nd_ref   = 1;
+        ndl->ndl_node->nd_id    = id;
+        ndl->ndl_node->nd_stamp = cfs_time_current();
+        ndl->ndl_node->nd_state = LST_NODE_UNKNOWN;
+        ndl->ndl_node->nd_timeout = 0;
+        memset(&ndl->ndl_node->nd_ping, 0, sizeof(lstcon_rpc_t));
+
+        /* queued in global hash & list, no refcount is taken by
+         * global hash & list, if caller release his refcount,
+         * node will be released */
+        list_add_tail(&ndl->ndl_hlink, &console_session.ses_ndl_hash[idx]);
+        list_add_tail(&ndl->ndl_link, &console_session.ses_ndl_list);
+
+        return 0;
+}
+
+void
+lstcon_node_put(lstcon_node_t *nd)
+{
+        lstcon_ndlink_t  *ndl;
+
+        LASSERT (nd->nd_ref > 0);
+
+        if (--nd->nd_ref > 0)
+                return;
+
+        ndl = (lstcon_ndlink_t *)(nd + 1);
+
+        LASSERT (!list_empty(&ndl->ndl_link));
+        LASSERT (!list_empty(&ndl->ndl_hlink));
+
+        /* remove from session */
+        list_del(&ndl->ndl_link);
+        list_del(&ndl->ndl_hlink);
+
+        LIBCFS_FREE(nd, sizeof(lstcon_node_t) + sizeof(lstcon_ndlink_t));
+}
+
+static int
+lstcon_ndlink_find(struct list_head *hash,
+                   lnet_process_id_t id, lstcon_ndlink_t **ndlpp, int create)
+{
+        unsigned int     idx = LNET_NIDADDR(id.nid) % LST_NODE_HASHSIZE;
+        lstcon_ndlink_t *ndl;
+        lstcon_node_t   *nd;
+        int              rc;
+
+        if (id.nid == LNET_NID_ANY)
+                return -EINVAL;
+
+        /* search in hash */
+        list_for_each_entry(ndl, &hash[idx], ndl_hlink) {
+                if (ndl->ndl_node->nd_id.nid != id.nid ||
+                    ndl->ndl_node->nd_id.pid != id.pid)
+                        continue;
+
+                *ndlpp = ndl;
+                return 0;
+        }
+
+        if (create == 0)
+                return -ENOENT;
+
+        /* find or create in session hash */
+        rc = lstcon_node_find(id, &nd, (create == 1) ? 1 : 0);
+        if (rc != 0)
+                return rc;
+
+        LIBCFS_ALLOC(ndl, sizeof(lstcon_ndlink_t));
+        if (ndl == NULL) {
+                lstcon_node_put(nd);
+                return -ENOMEM;
+        }
+        
+        *ndlpp = ndl;
+
+        ndl->ndl_node = nd;
+        CFS_INIT_LIST_HEAD(&ndl->ndl_link);
+        list_add_tail(&ndl->ndl_hlink, &hash[idx]);
+
+        return  0;
+}
+
+static void
+lstcon_ndlink_release(lstcon_ndlink_t *ndl)
+{
+        LASSERT (list_empty(&ndl->ndl_link));
+        LASSERT (!list_empty(&ndl->ndl_hlink));
+
+        list_del(&ndl->ndl_hlink); /* delete from hash */
+        lstcon_node_put(ndl->ndl_node);
+
+        LIBCFS_FREE(ndl, sizeof(*ndl));
+}
+
+static int
+lstcon_group_alloc(char *name, lstcon_group_t **grpp)
+{
+        lstcon_group_t *grp;
+        int             i;
+
+        LIBCFS_ALLOC(grp, offsetof(lstcon_group_t,
+                                   grp_ndl_hash[LST_NODE_HASHSIZE]));
+        if (grp == NULL)
+                return -ENOMEM;
+
+        memset(grp, 0, offsetof(lstcon_group_t,
+                                grp_ndl_hash[LST_NODE_HASHSIZE]));
+
+        grp->grp_ref = 1;
+        if (name != NULL)
+                strcpy(grp->grp_name, name);
+
+        CFS_INIT_LIST_HEAD(&grp->grp_link);
+        CFS_INIT_LIST_HEAD(&grp->grp_ndl_list);
+        CFS_INIT_LIST_HEAD(&grp->grp_trans_list);
+
+        for (i = 0; i < LST_NODE_HASHSIZE; i++)
+                CFS_INIT_LIST_HEAD(&grp->grp_ndl_hash[i]);
+
+        *grpp = grp;
+
+        return 0;
+}
+
+static void
+lstcon_group_addref(lstcon_group_t *grp)
+{
+        grp->grp_ref ++;
+}
+
+static void lstcon_group_ndlink_release(lstcon_group_t *, lstcon_ndlink_t *);
+
+static void
+lstcon_group_drain(lstcon_group_t *grp, int keep)
+{
+        lstcon_ndlink_t *ndl;
+        lstcon_ndlink_t *tmp;
+
+        list_for_each_entry_safe(ndl, tmp, &grp->grp_ndl_list, ndl_link) {
+                if ((ndl->ndl_node->nd_state & keep) == 0)
+                        lstcon_group_ndlink_release(grp, ndl);
+        }
+}
+
+static void
+lstcon_group_decref(lstcon_group_t *grp)
+{
+        int     i;
+
+        if (--grp->grp_ref > 0)
+                return;
+
+        if (!list_empty(&grp->grp_link))
+                list_del(&grp->grp_link);
+
+        lstcon_group_drain(grp, 0);
+
+        for (i = 0; i < LST_NODE_HASHSIZE; i++) {
+                LASSERT (list_empty(&grp->grp_ndl_hash[i]));
+        }
+
+        LIBCFS_FREE(grp, offsetof(lstcon_group_t,
+                                  grp_ndl_hash[LST_NODE_HASHSIZE]));
+}
+
+static int
+lstcon_group_find(char *name, lstcon_group_t **grpp)
+{
+        lstcon_group_t   *grp;
+
+        list_for_each_entry(grp, &console_session.ses_grp_list, grp_link) {
+                if (strncmp(grp->grp_name, name, LST_NAME_SIZE) != 0)
+                        continue;
+
+                lstcon_group_addref(grp);  /* +1 ref for caller */
+                *grpp = grp;
+                return 0;
+        }
+
+        return -ENOENT;
+}
+
+static void
+lstcon_group_put(lstcon_group_t *grp)
+{
+        lstcon_group_decref(grp);
+}
+
+static int
+lstcon_group_ndlink_find(lstcon_group_t *grp, lnet_process_id_t id,
+                         lstcon_ndlink_t **ndlpp, int create)
+{
+        int     rc;
+        
+        rc = lstcon_ndlink_find(&grp->grp_ndl_hash[0], id, ndlpp, create);
+        if (rc != 0)
+                return rc;
+
+        if (!list_empty(&(*ndlpp)->ndl_link))
+                return 0;
+
+        list_add_tail(&(*ndlpp)->ndl_link, &grp->grp_ndl_list);
+        grp->grp_nnode ++;
+
+        return 0;
+}
+
+static void
+lstcon_group_ndlink_release(lstcon_group_t *grp, lstcon_ndlink_t *ndl)
+{
+        list_del_init(&ndl->ndl_link);
+        lstcon_ndlink_release(ndl);
+        grp->grp_nnode --;
+}
+
+static void
+lstcon_group_ndlink_move(lstcon_group_t *old,
+                         lstcon_group_t *new, lstcon_ndlink_t *ndl)
+{
+        unsigned int idx = LNET_NIDADDR(ndl->ndl_node->nd_id.nid) %
+                           LST_NODE_HASHSIZE;
+
+        list_del(&ndl->ndl_hlink);
+        list_del(&ndl->ndl_link);
+        old->grp_nnode --;
+
+        list_add_tail(&ndl->ndl_hlink, &new->grp_ndl_hash[idx]);
+        list_add_tail(&ndl->ndl_link, &new->grp_ndl_list);
+        new->grp_nnode ++;
+
+        return;
+}
+
+static void
+lstcon_group_move(lstcon_group_t *old, lstcon_group_t *new)
+{
+        lstcon_ndlink_t *ndl;
+
+        while (!list_empty(&old->grp_ndl_list)) {
+                ndl = list_entry(old->grp_ndl_list.next,
+                                 lstcon_ndlink_t, ndl_link);
+                lstcon_group_ndlink_move(old, new, ndl);
+        }
+}
+
+int
+lstcon_sesrpc_condition(int transop, lstcon_node_t *nd, void *arg)
+{
+        lstcon_group_t *grp = (lstcon_group_t *)arg;
+
+        switch (transop) {
+        case LST_TRANS_SESNEW:
+                if (nd->nd_state == LST_NODE_ACTIVE)
+                        return 0;
+                break;
+
+        case LST_TRANS_SESEND:
+                if (nd->nd_state != LST_NODE_ACTIVE)
+                        return 0;
+
+                if (grp != NULL && nd->nd_ref > 1)
+                        return 0;
+                break;
+
+        case LST_TRANS_SESQRY:
+                break;
+
+        default:
+                LBUG();
+        }
+
+        return 1;
+}
+
+int
+lstcon_sesrpc_readent(int transop, srpc_msg_t *msg,
+                      lstcon_rpc_ent_t *ent_up)
+{
+        srpc_debug_reply_t *rep;
+
+        switch (transop) {
+        case LST_TRANS_SESNEW:
+        case LST_TRANS_SESEND:
+                return 0;
+
+        case LST_TRANS_SESQRY:
+                rep = &msg->msg_body.dbg_reply;
+
+                if (copy_to_user(&ent_up->rpe_priv[0],
+                                 &rep->dbg_timeout, sizeof(int)) ||
+                    copy_to_user(&ent_up->rpe_payload[0],
+                                 &rep->dbg_name, LST_NAME_SIZE))
+                        return -EFAULT;
+
+                return 0;
+
+        default:
+                LBUG();
+        }
+
+        return 0;
+}
+
+static int
+lstcon_group_nodes_add(lstcon_group_t *grp, int count,
+                       lnet_process_id_t *ids_up, struct list_head *result_up)
+{
+        lstcon_rpc_trans_t      *trans;
+        lstcon_ndlink_t         *ndl;
+        lstcon_group_t          *tmp;
+        lnet_process_id_t        id;
+        int                      i;
+        int                      rc;
+
+        rc = lstcon_group_alloc(NULL, &tmp);
+        if (rc != 0) {
+                CERROR("Out of memory\n");
+                return -ENOMEM;
+        }
+
+        for (i = 0 ; i < count; i++) {
+                if (copy_from_user(&id, &ids_up[i], sizeof(id))) {
+                        rc = -EFAULT;
+                        break;
+                }
+
+                /* skip if it's in this group already */
+                rc = lstcon_group_ndlink_find(grp, id, &ndl, 0);
+                if (rc == 0)
+                        continue;
+
+                /* add to tmp group */
+                rc = lstcon_group_ndlink_find(tmp, id, &ndl, 1);
+                if (rc != 0) {
+                        CERROR("Can't create ndlink, out of memory\n");
+                        break;
+                }
+        }
+
+        if (rc != 0) {
+                lstcon_group_put(tmp);
+                return rc;
+        }
+
+        rc = lstcon_rpc_trans_ndlist(&tmp->grp_ndl_list,
+                                     &tmp->grp_trans_list, LST_TRANS_SESNEW,
+                                     tmp, lstcon_sesrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                lstcon_group_put(tmp);
+                return rc;
+        }
+
+        /* post all RPCs */
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+        
+        rc = lstcon_rpc_trans_interpreter(trans, result_up,
+                                          lstcon_sesrpc_readent);
+        /* destroy all RPGs */
+        lstcon_rpc_trans_destroy(trans);
+
+        lstcon_group_move(tmp, grp);
+        lstcon_group_put(tmp);
+
+        return rc;
+}
+
+static int
+lstcon_group_nodes_remove(lstcon_group_t *grp,
+                          int count, lnet_process_id_t *ids_up,
+                          struct list_head *result_up)
+{
+        lstcon_rpc_trans_t     *trans;
+        lstcon_ndlink_t        *ndl;
+        lstcon_group_t         *tmp;
+        lnet_process_id_t       id;
+        int                     rc;
+        int                     i;
+
+        /* End session and remove node from the group */
+
+        rc = lstcon_group_alloc(NULL, &tmp);
+        if (rc != 0) {
+                CERROR("Out of memory\n");
+                return -ENOMEM;
+        }
+
+        for (i = 0; i < count; i++) {
+                if (copy_from_user(&id, &ids_up[i], sizeof(id))) {
+                        rc = -EFAULT;
+                        goto error;
+                }
+                
+                /* move node to tmp group */
+                if (lstcon_group_ndlink_find(grp, id, &ndl, 0) == 0)
+                        lstcon_group_ndlink_move(grp, tmp, ndl);
+        }
+
+        rc = lstcon_rpc_trans_ndlist(&tmp->grp_ndl_list,
+                                     &tmp->grp_trans_list, LST_TRANS_SESEND,
+                                     tmp, lstcon_sesrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                goto error;
+        }
+
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+
+        rc = lstcon_rpc_trans_interpreter(trans, result_up, NULL);
+
+        lstcon_rpc_trans_destroy(trans);
+        /* release nodes anyway, because we can't rollback status */
+        lstcon_group_put(tmp);
+
+        return rc;
+error:
+        lstcon_group_move(tmp, grp);
+        lstcon_group_put(tmp);
+
+        return rc;
+}
+
+int
+lstcon_group_add(char *name)
+{
+        lstcon_group_t *grp;
+        int             rc;
+
+        rc = (lstcon_group_find(name, &grp) == 0)? -EEXIST: 0;
+        if (rc != 0) {
+                /* find a group with same name */
+                lstcon_group_put(grp);
+                return rc;
+        }
+
+        rc = lstcon_group_alloc(name, &grp);
+        if (rc != 0) {
+                CERROR("Can't allocate descriptor for group %s\n", name);
+                return -ENOMEM;
+        }
+
+        list_add_tail(&grp->grp_link, &console_session.ses_grp_list);
+
+        return rc;
+}
+
+int
+lstcon_nodes_add(char *name, int count,
+                 lnet_process_id_t *ids_up, struct list_head *result_up)
+{
+        lstcon_group_t         *grp;
+        int                     rc;
+
+        LASSERT (count > 0);
+        LASSERT (ids_up != NULL);
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group %s\n", name);
+                return rc;
+        }
+
+        if (grp->grp_ref > 2) {
+                /* referred by other threads or test */
+                CDEBUG(D_NET, "Group %s is busy\n", name);
+                lstcon_group_put(grp);
+
+                return -EBUSY;
+        }
+
+        rc = lstcon_group_nodes_add(grp, count, ids_up, result_up);
+
+        lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_group_del(char *name)
+{
+        lstcon_rpc_trans_t *trans;
+        lstcon_group_t     *grp;
+        int                 rc;
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group: %s\n", name);
+                return rc;
+        }
+
+        if (grp->grp_ref > 2) {
+                /* referred by others threads or test */
+                CDEBUG(D_NET, "Group %s is busy\n", name);
+                lstcon_group_put(grp);
+                return -EBUSY;
+        }
+
+        rc = lstcon_rpc_trans_ndlist(&grp->grp_ndl_list,
+                                     &grp->grp_trans_list, LST_TRANS_SESEND,
+                                     grp, lstcon_sesrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                lstcon_group_put(grp);
+                return rc;
+        }
+
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+
+        lstcon_rpc_trans_destroy(trans);
+
+        lstcon_group_put(grp);
+        /* -ref for session, it's destroyed,
+         * status can't be rolled back, destroy group anway */
+        lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_group_clean(char *name, int args)
+{
+        lstcon_group_t *grp = NULL;
+        int             rc;
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group %s\n", name);
+                return rc;
+        }
+
+        if (grp->grp_ref > 2) {
+                /* referred by test */
+                CDEBUG(D_NET, "Group %s is busy\n", name);
+                lstcon_group_put(grp);
+                return -EBUSY;
+        }
+
+        args = (LST_NODE_ACTIVE | LST_NODE_BUSY |
+                LST_NODE_DOWN | LST_NODE_UNKNOWN) & ~args;
+
+        lstcon_group_drain(grp, args);
+
+        lstcon_group_put(grp);
+        /* release empty group */
+        if (list_empty(&grp->grp_ndl_list))
+                lstcon_group_put(grp);
+
+        return 0;
+}
+
+int
+lstcon_nodes_remove(char *name, int count,
+                    lnet_process_id_t *ids_up, struct list_head *result_up)
+{
+        lstcon_group_t *grp = NULL;
+        int             rc;
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group: %s\n", name);
+                return rc;
+        }
+
+        if (grp->grp_ref > 2) {
+                /* referred by test */
+                CDEBUG(D_NET, "Group %s is busy\n", name);
+                lstcon_group_put(grp);
+                return -EBUSY;
+        }
+
+        rc = lstcon_group_nodes_remove(grp, count, ids_up, result_up);
+
+        lstcon_group_put(grp);
+        /* release empty group */
+        if (list_empty(&grp->grp_ndl_list))
+                lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_group_refresh(char *name, struct list_head *result_up)
+{
+        lstcon_rpc_trans_t      *trans;
+        lstcon_group_t          *grp;
+        int                      rc;
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group: %s\n", name);
+                return rc;
+        }
+
+        if (grp->grp_ref > 2) {
+                /* referred by test */
+                CDEBUG(D_NET, "Group %s is busy\n", name);
+                lstcon_group_put(grp);
+                return -EBUSY;
+        }
+
+        /* re-invite all inactive nodes int the group */
+        rc = lstcon_rpc_trans_ndlist(&grp->grp_ndl_list,
+                                     &grp->grp_trans_list, LST_TRANS_SESNEW,
+                                     grp, lstcon_sesrpc_condition, &trans);
+        if (rc != 0) {
+                /* local error, return */
+                CDEBUG(D_NET, "Can't create transaction: %d\n", rc);
+                lstcon_group_put(grp);
+                return rc;
+        }
+
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+
+        rc = lstcon_rpc_trans_interpreter(trans, result_up, NULL);
+
+        lstcon_rpc_trans_destroy(trans);
+        /* -ref for me */
+        lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_group_list(int index, int len, char *name_up)
+{
+        lstcon_group_t *grp;
+
+        LASSERT (index >= 0);
+        LASSERT (name_up != NULL);
+
+        list_for_each_entry(grp, &console_session.ses_grp_list, grp_link) {
+                if (index-- == 0) {
+                        return copy_to_user(name_up, grp->grp_name, len) ?
+                               -EFAULT : 0;
+                }
+        }
+
+        return -ENOENT;
+}
+
+static int
+lstcon_nodes_getent(struct list_head *head, int *index_p,
+                    int *count_p, lstcon_node_ent_t *dents_up)
+{
+        lstcon_ndlink_t  *ndl;
+        lstcon_node_t    *nd;
+        int               count = 0;
+        int               index = 0;
+
+        LASSERT (index_p != NULL && count_p != NULL);
+        LASSERT (dents_up != NULL);
+        LASSERT (*index_p >= 0);
+        LASSERT (*count_p > 0);
+
+        list_for_each_entry(ndl, head, ndl_link) {
+                if (index++ < *index_p)
+                        continue;
+
+                if (count >= *count_p)
+                        break;
+
+                nd = ndl->ndl_node;
+                if (copy_to_user(&dents_up[count].nde_id,
+                                 &nd->nd_id, sizeof(nd->nd_id)) ||
+                    copy_to_user(&dents_up[count].nde_state,
+                                 &nd->nd_state, sizeof(nd->nd_state)))
+                        return -EFAULT;
+
+                count ++;
+        }
+
+        if (index <= *index_p)
+                return -ENOENT;
+
+        *count_p = count;
+        *index_p = index;
+
+        return 0;
+}
+
+int
+lstcon_group_info(char *name, lstcon_ndlist_ent_t *gents_p,
+                  int *index_p, int *count_p, lstcon_node_ent_t *dents_up)
+{
+        lstcon_ndlist_ent_t *gentp;
+        lstcon_group_t      *grp;
+        lstcon_ndlink_t     *ndl;
+        int                  rc;
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group %s\n", name);
+                return rc;
+        }
+
+        if (dents_up != 0) {
+                /* verbose query */
+                rc = lstcon_nodes_getent(&grp->grp_ndl_list,
+                                         index_p, count_p, dents_up);
+                lstcon_group_put(grp);
+
+                return rc;
+        }
+
+        /* non-verbose query */
+        LIBCFS_ALLOC(gentp, sizeof(lstcon_ndlist_ent_t));
+        if (gentp == NULL) {
+                CERROR("Can't allocate ndlist_ent\n");
+                lstcon_group_put(grp);
+
+                return -ENOMEM;
+        }
+
+        memset(gentp, 0, sizeof(lstcon_ndlist_ent_t));
+
+        list_for_each_entry(ndl, &grp->grp_ndl_list, ndl_link)
+                LST_NODE_STATE_COUNTER(ndl->ndl_node, gentp);
+
+        rc = copy_to_user(gents_p, gentp,
+                          sizeof(lstcon_ndlist_ent_t)) ? -EFAULT: 0;
+
+        LIBCFS_FREE(gentp, sizeof(lstcon_ndlist_ent_t));
+                
+        lstcon_group_put(grp);
+
+        return 0;
+}
+
+int
+lstcon_batch_find(char *name, lstcon_batch_t **batpp)
+{
+        lstcon_batch_t   *bat;
+
+        list_for_each_entry(bat, &console_session.ses_bat_list, bat_link) {
+                if (strncmp(bat->bat_name, name, LST_NAME_SIZE) == 0) {
+                        *batpp = bat;
+                        return 0;
+                }
+        }
+
+        return -ENOENT;
+}
+
+int
+lstcon_batch_add(char *name)
+{
+        lstcon_batch_t   *bat;
+        int               i;
+        int               rc;
+
+        rc = (lstcon_batch_find(name, &bat) == 0)? -EEXIST: 0;
+        if (rc != 0) {
+                CDEBUG(D_NET, "Batch %s already exists\n", name);
+                return rc;
+        }
+
+        LIBCFS_ALLOC(bat, sizeof(lstcon_batch_t));
+        if (bat == NULL) {
+                CERROR("Can't allocate descriptor for batch %s\n", name);
+                return -ENOMEM;
+        }
+
+        LIBCFS_ALLOC(bat->bat_cli_hash,
+                     sizeof(struct list_head) * LST_NODE_HASHSIZE);
+        if (bat->bat_cli_hash == NULL) {
+                CERROR("Can't allocate hash for batch %s\n", name);
+                LIBCFS_FREE(bat, sizeof(lstcon_batch_t));
+
+                return -ENOMEM;
+        }
+
+        LIBCFS_ALLOC(bat->bat_srv_hash,
+                     sizeof(struct list_head) * LST_NODE_HASHSIZE);
+        if (bat->bat_srv_hash == NULL) {
+                CERROR("Can't allocate hash for batch %s\n", name);
+                LIBCFS_FREE(bat->bat_cli_hash, LST_NODE_HASHSIZE);
+                LIBCFS_FREE(bat, sizeof(lstcon_batch_t));
+
+                return -ENOMEM;
+        }
+
+        strcpy(bat->bat_name, name);
+        bat->bat_hdr.tsb_index = 0;
+        bat->bat_hdr.tsb_id.bat_id = ++console_session.ses_id_cookie;
+
+        bat->bat_ntest = 0;
+        bat->bat_state = LST_BATCH_IDLE;
+
+        CFS_INIT_LIST_HEAD(&bat->bat_cli_list);
+        CFS_INIT_LIST_HEAD(&bat->bat_srv_list);
+        CFS_INIT_LIST_HEAD(&bat->bat_test_list);
+        CFS_INIT_LIST_HEAD(&bat->bat_trans_list);
+
+        for (i = 0; i < LST_NODE_HASHSIZE; i++) {
+                CFS_INIT_LIST_HEAD(&bat->bat_cli_hash[i]);
+                CFS_INIT_LIST_HEAD(&bat->bat_srv_hash[i]);
+        }
+
+        list_add_tail(&bat->bat_link, &console_session.ses_bat_list);
+
+        return rc;
+}
+
+int
+lstcon_batch_list(int index, int len, char *name_up)
+{
+        lstcon_batch_t    *bat;
+
+        LASSERT (name_up != NULL);
+        LASSERT (index >= 0);
+
+        list_for_each_entry(bat, &console_session.ses_bat_list, bat_link) {
+                if (index-- == 0) {
+                        return copy_to_user(name_up,bat->bat_name, len) ?
+                               -EFAULT: 0;
+                }
+        }
+
+        return -ENOENT;
+}
+
+int
+lstcon_batch_info(char *name, lstcon_test_batch_ent_t *ent_up, int server,
+                  int testidx, int *index_p, int *ndent_p,
+                  lstcon_node_ent_t *dents_up)
+{
+        lstcon_test_batch_ent_t *entp;
+        struct list_head        *clilst;
+        struct list_head        *srvlst;
+        lstcon_test_t           *test = NULL;
+        lstcon_batch_t          *bat;
+        lstcon_ndlink_t         *ndl;
+        int                      rc;
+
+        rc = lstcon_batch_find(name, &bat);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find batch %s\n", name);
+                return -ENOENT;
+        }
+
+        if (testidx > 0) {
+                /* query test, test index start from 1 */
+                list_for_each_entry(test, &bat->bat_test_list, tes_link) {
+                        if (testidx-- == 1)
+                                break;
+                }
+
+                if (testidx > 0) {
+                        CDEBUG(D_NET, "Can't find specified test in batch\n");
+                        return -ENOENT;
+                }
+        }
+
+        clilst = (test == NULL) ? &bat->bat_cli_list :
+                                  &test->tes_src_grp->grp_ndl_list;
+        srvlst = (test == NULL) ? &bat->bat_srv_list :
+                                  &test->tes_dst_grp->grp_ndl_list;
+
+        if (dents_up != NULL) {
+                rc = lstcon_nodes_getent((server ? srvlst: clilst),
+                                         index_p, ndent_p, dents_up);
+                return rc;
+        }
+
+        /* non-verbose query */
+        LIBCFS_ALLOC(entp, sizeof(lstcon_test_batch_ent_t));
+        if (entp == NULL)
+                return -ENOMEM;
+
+        memset(entp, 0, sizeof(lstcon_test_batch_ent_t));
+
+        if (test == NULL) {
+                entp->u.tbe_batch.bae_ntest = bat->bat_ntest;
+                entp->u.tbe_batch.bae_state = bat->bat_state;
+
+        } else {
+
+                entp->u.tbe_test.tse_type   = test->tes_type;
+                entp->u.tbe_test.tse_loop   = test->tes_loop;
+                entp->u.tbe_test.tse_concur = test->tes_concur;
+        }
+
+        list_for_each_entry(ndl, clilst, ndl_link)
+                LST_NODE_STATE_COUNTER(ndl->ndl_node, &entp->tbe_cli_nle);
+
+        list_for_each_entry(ndl, srvlst, ndl_link)
+                LST_NODE_STATE_COUNTER(ndl->ndl_node, &entp->tbe_srv_nle);
+
+        rc = copy_to_user(ent_up, entp,
+                          sizeof(lstcon_test_batch_ent_t)) ? -EFAULT : 0;
+
+        LIBCFS_FREE(entp, sizeof(lstcon_test_batch_ent_t));
+
+        return rc;
+}
+
+int
+lstcon_batrpc_condition(int transop, lstcon_node_t *nd, void *arg)
+{
+        switch (transop) {
+        case LST_TRANS_TSBRUN:
+                if (nd->nd_state != LST_NODE_ACTIVE)
+                        return -ENETDOWN;
+                break;
+
+        case LST_TRANS_TSBSTOP:
+                if (nd->nd_state != LST_NODE_ACTIVE)
+                        return 0;
+                break;
+
+        case LST_TRANS_TSBCLIQRY:
+        case LST_TRANS_TSBSRVQRY:
+                break;
+        }
+
+        return 1;
+}
+
+static int
+lstcon_batch_op(lstcon_batch_t *bat, int transop, struct list_head *result_up)
+{
+        lstcon_rpc_trans_t *trans;
+        int                 rc;
+
+        rc = lstcon_rpc_trans_ndlist(&bat->bat_cli_list,
+                                     &bat->bat_trans_list, transop,
+                                     bat, lstcon_batrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                return rc;
+        }
+
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+
+        rc = lstcon_rpc_trans_interpreter(trans, result_up, NULL);
+
+        lstcon_rpc_trans_destroy(trans);
+
+        return rc;
+}
+
+int
+lstcon_batch_run(char *name, int timeout, struct list_head *result_up)
+{
+        lstcon_batch_t *bat;
+        int             rc;
+
+        if (lstcon_batch_find(name, &bat) != 0) {
+                CDEBUG(D_NET, "Can't find batch %s\n", name);
+                return -ENOENT;
+        }
+
+        bat->bat_arg = timeout;
+
+        rc = lstcon_batch_op(bat, LST_TRANS_TSBRUN, result_up);
+
+        /* mark batch as running if it's started in any node */
+        if (lstcon_tsbop_stat_success(lstcon_trans_stat(), 0) != 0)
+                bat->bat_state = LST_BATCH_RUNNING;
+
+        return rc;
+}
+
+int
+lstcon_batch_stop(char *name, int force, struct list_head *result_up)
+{
+        lstcon_batch_t *bat;
+        int             rc;
+
+        if (lstcon_batch_find(name, &bat) != 0) {
+                CDEBUG(D_NET, "Can't find batch %s\n", name);
+                return -ENOENT;
+        }
+
+        bat->bat_arg = force;
+
+        rc = lstcon_batch_op(bat, LST_TRANS_TSBSTOP, result_up);
+        
+        /* mark batch as stopped if all RPCs finished */
+        if (lstcon_tsbop_stat_failure(lstcon_trans_stat(), 0) == 0)
+                bat->bat_state = LST_BATCH_IDLE;
+
+        return rc;
+}
+
+static void
+lstcon_batch_destroy(lstcon_batch_t *bat)
+{
+        lstcon_ndlink_t    *ndl;
+        lstcon_test_t      *test;
+        int                 i;
+
+        list_del(&bat->bat_link);
+
+        while (!list_empty(&bat->bat_test_list)) {
+                test = list_entry(bat->bat_test_list.next,
+                                  lstcon_test_t, tes_link);
+                LASSERT (list_empty(&test->tes_trans_list));
+
+                list_del(&test->tes_link);
+
+                lstcon_group_put(test->tes_src_grp);
+                lstcon_group_put(test->tes_dst_grp);
+
+                LIBCFS_FREE(test, offsetof(lstcon_test_t,
+                                           tes_param[test->tes_paramlen]));
+        }
+
+        LASSERT (list_empty(&bat->bat_trans_list));
+
+        while (!list_empty(&bat->bat_cli_list)) {
+                ndl = list_entry(bat->bat_cli_list.next,
+                                 lstcon_ndlink_t, ndl_link);
+                list_del_init(&ndl->ndl_link);
+
+                lstcon_ndlink_release(ndl);
+        }
+
+        while (!list_empty(&bat->bat_srv_list)) {
+                ndl = list_entry(bat->bat_srv_list.next,
+                                 lstcon_ndlink_t, ndl_link);
+                list_del_init(&ndl->ndl_link);
+
+                lstcon_ndlink_release(ndl);
+        }
+
+        for (i = 0; i < LST_NODE_HASHSIZE; i++) {
+                LASSERT (list_empty(&bat->bat_cli_hash[i]));
+                LASSERT (list_empty(&bat->bat_srv_hash[i]));
+        }
+
+        LIBCFS_FREE(bat->bat_cli_hash,
+                    sizeof(struct list_head) * LST_NODE_HASHSIZE);
+        LIBCFS_FREE(bat->bat_srv_hash,
+                    sizeof(struct list_head) * LST_NODE_HASHSIZE);
+        LIBCFS_FREE(bat, sizeof(lstcon_batch_t));
+}
+
+int
+lstcon_testrpc_condition(int transop, lstcon_node_t *nd, void *arg)
+{
+        lstcon_test_t    *test;
+        lstcon_batch_t   *batch;
+        lstcon_ndlink_t  *ndl;
+        struct list_head *hash;
+        struct list_head *head;
+
+        test = (lstcon_test_t *)arg;
+        LASSERT (test != NULL);
+
+        batch = test->tes_batch;
+        LASSERT (batch != NULL);
+
+        if (test->tes_oneside &&
+            transop == LST_TRANS_TSBSRVADD)
+                return 0;
+
+        if (nd->nd_state != LST_NODE_ACTIVE)
+                return -ENETDOWN;
+
+        if (transop == LST_TRANS_TSBCLIADD) {
+                hash = batch->bat_cli_hash;
+                head = &batch->bat_cli_list;
+        
+        } else {
+                LASSERT (transop == LST_TRANS_TSBSRVADD);
+
+                hash = batch->bat_srv_hash;
+                head = &batch->bat_srv_list;
+        }
+
+        LASSERT (nd->nd_id.nid != LNET_NID_ANY);
+
+        if (lstcon_ndlink_find(hash, nd->nd_id, &ndl, 1) != 0)
+                return -ENOMEM;
+
+        if (list_empty(&ndl->ndl_link))
+                list_add_tail(&ndl->ndl_link, head);
+
+        return 1;
+}
+
+static int
+lstcon_test_nodes_add(lstcon_test_t *test, struct list_head *result_up)
+{
+        lstcon_rpc_trans_t     *trans;
+        lstcon_group_t         *grp;
+        int                     transop;
+        int                     rc;
+
+        LASSERT (test->tes_src_grp != NULL);
+        LASSERT (test->tes_dst_grp != NULL);
+
+        transop = LST_TRANS_TSBSRVADD;
+        grp  = test->tes_dst_grp;
+again:
+        rc = lstcon_rpc_trans_ndlist(&grp->grp_ndl_list,
+                                     &test->tes_trans_list, transop,
+                                     test, lstcon_testrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                return rc;
+        }
+
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+
+        if (lstcon_trans_stat()->trs_rpc_errno != 0 ||
+            lstcon_trans_stat()->trs_fwk_errno != 0) {
+                lstcon_rpc_trans_interpreter(trans, result_up, NULL);
+
+                lstcon_rpc_trans_destroy(trans);
+                /* return if any error */
+                CDEBUG(D_NET, "Failed to add test %s, "
+                              "RPC error %d, framework error %d\n",
+                       transop == LST_TRANS_TSBCLIADD ? "client" : "server",
+                       lstcon_trans_stat()->trs_rpc_errno,
+                       lstcon_trans_stat()->trs_fwk_errno);
+
+                return rc;
+        }
+
+        lstcon_rpc_trans_destroy(trans);
+
+        if (transop == LST_TRANS_TSBCLIADD)
+                return rc;
+
+        transop = LST_TRANS_TSBCLIADD;
+        grp = test->tes_src_grp;
+        test->tes_cliidx = 0;
+
+        /* requests to test clients */
+        goto again;
+}
+
+int
+lstcon_test_add(char *name, int type, int loop, int concur,
+                int dist, int span, char *src_name, char * dst_name,
+                void *param, int paramlen, struct list_head *result_up)
+                
+{
+        lstcon_group_t  *src_grp = NULL;
+        lstcon_group_t  *dst_grp = NULL;
+        lstcon_test_t   *test    = NULL;
+        lstcon_batch_t  *batch;
+        int              rc;
+
+        rc = lstcon_batch_find(name, &batch);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find batch %s\n", name);
+                return rc;
+        }
+
+        if (batch->bat_state != LST_BATCH_IDLE) {
+                CDEBUG(D_NET, "Can't change running batch %s\n", name);
+                return rc;
+        }
+        
+        rc = lstcon_group_find(src_name, &src_grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group %s\n", src_name);
+                goto out;
+        }
+
+        rc = lstcon_group_find(dst_name, &dst_grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group %s\n", dst_name);
+                goto out;
+        }
+
+        LIBCFS_ALLOC(test, offsetof(lstcon_test_t, tes_param[paramlen]));
+        if (!test) {
+                CERROR("Can't allocate test descriptor\n");
+                rc = -ENOMEM;
+
+                goto out;
+        }
+
+        memset(test, 0, offsetof(lstcon_test_t, tes_param[paramlen]));
+        test->tes_hdr.tsb_id    = batch->bat_hdr.tsb_id;
+        test->tes_batch         = batch;
+        test->tes_type          = type;
+        test->tes_oneside       = 0; /* TODO */
+        test->tes_loop          = loop;
+        test->tes_concur        = concur;
+        test->tes_stop_onerr    = 1; /* TODO */
+        test->tes_span          = span;
+        test->tes_dist          = dist;
+        test->tes_cliidx        = 0; /* just used for creating RPC */
+        test->tes_src_grp       = src_grp;
+        test->tes_dst_grp       = dst_grp;
+        CFS_INIT_LIST_HEAD(&test->tes_trans_list);
+
+        if (param != NULL) {
+                test->tes_paramlen = paramlen;
+                memcpy(&test->tes_param[0], param, paramlen);
+        }
+
+        rc = lstcon_test_nodes_add(test, result_up);
+
+        if (rc != 0)
+                goto out;
+
+        if (lstcon_trans_stat()->trs_rpc_errno != 0 ||
+            lstcon_trans_stat()->trs_fwk_errno != 0)
+                CDEBUG(D_NET, "Failed to add test %d to batch %s", type, name);
+
+        /* add to test list anyway, so user can check what's going on */
+        list_add_tail(&test->tes_link, &batch->bat_test_list);
+
+        batch->bat_ntest ++;
+        test->tes_hdr.tsb_index = batch->bat_ntest;
+
+        /*  hold groups so nobody can change them */
+        return rc;
+out:
+        if (test != NULL)
+                LIBCFS_FREE(test, offsetof(lstcon_test_t, tes_param[paramlen]));
+
+        if (dst_grp != NULL)
+                lstcon_group_put(dst_grp);
+
+        if (src_grp != NULL)
+                lstcon_group_put(src_grp);
+
+        return rc;
+}
+
+int
+lstcon_test_find(lstcon_batch_t *batch, int idx, lstcon_test_t **testpp)
+{
+        lstcon_test_t *test;
+
+        list_for_each_entry(test, &batch->bat_test_list, tes_link) {
+                if (idx == test->tes_hdr.tsb_index) {
+                        *testpp = test;
+                        return 0;
+                }
+        }
+
+        return -ENOENT;
+}
+
+int
+lstcon_tsbrpc_readent(int transop, srpc_msg_t *msg,
+                      lstcon_rpc_ent_t *ent_up)
+{
+        srpc_batch_reply_t *rep = &msg->msg_body.bat_reply;
+
+        LASSERT (transop == LST_TRANS_TSBCLIQRY ||
+                 transop == LST_TRANS_TSBSRVQRY);
+
+        /* positive errno, framework error code */
+        if (copy_to_user(&ent_up->rpe_priv[0],
+                         &rep->bar_active, sizeof(rep->bar_active)))
+                return -EFAULT;
+
+        return 0;
+}
+
+int
+lstcon_test_batch_query(char *name, int testidx, int client,
+                        int timeout, struct list_head *result_up)
+{
+        lstcon_rpc_trans_t *trans;
+        struct list_head   *translist;
+        struct list_head   *ndlist;
+        lstcon_tsb_hdr_t   *hdr;
+        lstcon_batch_t     *batch;
+        lstcon_test_t      *test = NULL;
+        int                 transop;
+        int                 rc;
+
+        rc = lstcon_batch_find(name, &batch);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find batch: %s\n", name);
+                return rc;
+        }
+
+        if (testidx == 0) {
+                translist = &batch->bat_trans_list;
+                ndlist    = &batch->bat_cli_list;
+                hdr       = &batch->bat_hdr;
+
+        } else {
+                /* query specified test only */
+                rc = lstcon_test_find(batch, testidx, &test);
+                if (rc != 0) {
+                        CDEBUG(D_NET, "Can't find test: %d\n", testidx);
+                        return rc;
+                }
+        
+                translist = &test->tes_trans_list;
+                ndlist    = &test->tes_src_grp->grp_ndl_list;
+                hdr       = &test->tes_hdr;
+        } 
+
+        transop = client ? LST_TRANS_TSBCLIQRY : LST_TRANS_TSBSRVQRY;
+
+        rc = lstcon_rpc_trans_ndlist(ndlist, translist, transop, hdr,
+                                     lstcon_batrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                return rc;
+        }
+
+        lstcon_rpc_trans_postwait(trans, timeout);
+
+        if (testidx == 0 && /* query a batch, not a test */
+            lstcon_rpc_stat_failure(lstcon_trans_stat(), 0) == 0 &&
+            lstcon_tsbqry_stat_run(lstcon_trans_stat(), 0) == 0) {
+                /* all RPCs finished, and no active test */
+                batch->bat_state = LST_BATCH_IDLE;
+        }
+
+        rc = lstcon_rpc_trans_interpreter(trans, result_up,
+                                          lstcon_tsbrpc_readent);
+        lstcon_rpc_trans_destroy(trans);
+
+        return rc;
+}
+
+int
+lstcon_statrpc_readent(int transop, srpc_msg_t *msg,
+                       lstcon_rpc_ent_t *ent_up)
+{
+        srpc_stat_reply_t *rep = &msg->msg_body.stat_reply;
+        sfw_counters_t    *sfwk_stat;
+        srpc_counters_t   *srpc_stat;
+        lnet_counters_t   *lnet_stat;
+        
+        if (rep->str_status != 0)
+                return 0;
+
+        sfwk_stat = (sfw_counters_t *)&ent_up->rpe_payload[0];
+        srpc_stat = (srpc_counters_t *)((char *)sfwk_stat + sizeof(*sfwk_stat));
+        lnet_stat = (lnet_counters_t *)((char *)srpc_stat + sizeof(*srpc_stat));
+
+        if (copy_to_user(sfwk_stat, &rep->str_fw, sizeof(*sfwk_stat)) ||
+            copy_to_user(srpc_stat, &rep->str_rpc, sizeof(*srpc_stat)) ||
+            copy_to_user(lnet_stat, &rep->str_lnet, sizeof(*lnet_stat)))
+                return -EFAULT;
+
+        return 0;
+}
+
+int
+lstcon_ndlist_stat(struct list_head *ndlist,
+                   int timeout, struct list_head *result_up)
+{
+        struct list_head    head;
+        lstcon_rpc_trans_t *trans;
+        int                 rc;
+
+        CFS_INIT_LIST_HEAD(&head);
+
+        rc = lstcon_rpc_trans_ndlist(ndlist, &head,
+                                     LST_TRANS_STATQRY, NULL, NULL, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                return rc;
+        }
+
+        timeout = (timeout > LST_TRANS_MIN_TIMEOUT) ? timeout :
+                                                      LST_TRANS_MIN_TIMEOUT;
+        lstcon_rpc_trans_postwait(trans, timeout);
+
+        rc = lstcon_rpc_trans_interpreter(trans, result_up,
+                                          lstcon_statrpc_readent);
+        lstcon_rpc_trans_destroy(trans);
+
+        return rc;
+}
+
+int
+lstcon_group_stat(char *grp_name, int timeout, struct list_head *result_up)
+{
+        lstcon_group_t     *grp;
+        int                 rc;
+
+        rc = lstcon_group_find(grp_name, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Can't find group %s\n", grp_name);
+                return rc;
+        }
+
+        rc = lstcon_ndlist_stat(&grp->grp_ndl_list, timeout, result_up);
+
+        lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_nodes_stat(int count, lnet_process_id_t *ids_up,
+                  int timeout, struct list_head *result_up)
+{
+        lstcon_ndlink_t         *ndl;
+        lstcon_group_t          *tmp;
+        lnet_process_id_t        id;
+        int                      i;
+        int                      rc;
+
+        rc = lstcon_group_alloc(NULL, &tmp);
+        if (rc != 0) {
+                CERROR("Out of memory\n");
+                return -ENOMEM;
+        }
+
+        for (i = 0 ; i < count; i++) {
+                if (copy_from_user(&id, &ids_up[i], sizeof(id))) {
+                        rc = -EFAULT;
+                        break;
+                }
+
+                /* add to tmp group */
+                rc = lstcon_group_ndlink_find(tmp, id, &ndl, 2);
+                if (rc != 0) {
+                        CDEBUG((rc == -ENOMEM) ? D_ERROR : D_NET,
+                               "Failed to find or create %s: %d\n",
+                               libcfs_id2str(id), rc);
+                        break;
+                }
+        }
+
+        if (rc != 0) {
+                lstcon_group_put(tmp);
+                return rc;
+        }
+
+        rc = lstcon_ndlist_stat(&tmp->grp_ndl_list, timeout, result_up);
+
+        lstcon_group_put(tmp);
+
+        return rc;
+}
+
+int
+lstcon_debug_ndlist(struct list_head *ndlist,
+                    struct list_head *translist,
+                    int timeout, struct list_head *result_up)
+{
+        lstcon_rpc_trans_t *trans;
+        int                 rc;
+
+        rc = lstcon_rpc_trans_ndlist(ndlist, translist, LST_TRANS_SESQRY,
+                                     NULL, lstcon_sesrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                return rc;
+        }
+
+        timeout = (timeout > LST_TRANS_MIN_TIMEOUT) ? timeout :
+                                                      LST_TRANS_MIN_TIMEOUT;
+
+        lstcon_rpc_trans_postwait(trans, timeout);
+
+        rc = lstcon_rpc_trans_interpreter(trans, result_up,
+                                          lstcon_sesrpc_readent);
+        lstcon_rpc_trans_destroy(trans);
+
+        return rc;
+}
+
+int
+lstcon_session_debug(int timeout, struct list_head *result_up)
+{
+        return lstcon_debug_ndlist(&console_session.ses_ndl_list,
+                                   NULL, timeout, result_up);
+}
+
+int
+lstcon_batch_debug(int timeout, char *name,
+                   int client, struct list_head *result_up)
+{
+        lstcon_batch_t *bat;
+        int             rc;
+
+        rc = lstcon_batch_find(name, &bat);
+        if (rc != 0)
+                return -ENOENT;
+
+        rc = lstcon_debug_ndlist(client ? &bat->bat_cli_list :
+                                          &bat->bat_srv_list,
+                                 NULL, timeout, result_up);
+
+        return rc;
+}
+
+int
+lstcon_group_debug(int timeout, char *name,
+                   struct list_head *result_up)
+{
+        lstcon_group_t *grp;
+        int             rc;
+
+        rc = lstcon_group_find(name, &grp);
+        if (rc != 0)
+                return -ENOENT;
+
+        rc = lstcon_debug_ndlist(&grp->grp_ndl_list, NULL,
+                                 timeout, result_up);
+        lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_nodes_debug(int timeout,
+                   int count, lnet_process_id_t *ids_up, 
+                   struct list_head *result_up)
+{
+        lnet_process_id_t  id;
+        lstcon_ndlink_t   *ndl;
+        lstcon_group_t    *grp;
+        int                i;
+        int                rc;
+
+        rc = lstcon_group_alloc(NULL, &grp);
+        if (rc != 0) {
+                CDEBUG(D_NET, "Out of memory\n");
+                return rc;
+        }
+
+        for (i = 0; i < count; i++) {
+                if (copy_from_user(&id, &ids_up[i], sizeof(id))) {
+                        rc = -EFAULT;
+                        break;
+                }
+
+                /* node is added to tmp group */
+                rc = lstcon_group_ndlink_find(grp, id, &ndl, 1);
+                if (rc != 0) {
+                        CERROR("Can't create node link\n");
+                        break;
+                }
+        }
+
+        if (rc != 0) {
+                lstcon_group_put(grp);
+                return rc;
+        }
+
+        rc = lstcon_debug_ndlist(&grp->grp_ndl_list, NULL,
+                                 timeout, result_up);
+
+        lstcon_group_put(grp);
+
+        return rc;
+}
+
+int
+lstcon_session_match(lst_sid_t sid)
+{
+        return (console_session.ses_id.ses_nid   == sid.ses_nid &&
+                console_session.ses_id.ses_stamp == sid.ses_stamp) ?  1: 0;
+}
+
+static void
+lstcon_new_session_id(lst_sid_t *sid)
+{
+        lnet_process_id_t      id;
+
+        LASSERT (console_session.ses_state == LST_SESSION_NONE);
+
+        LNetGetId(1, &id);
+        sid->ses_nid   = id.nid;
+        sid->ses_stamp = cfs_time_current();
+}
+
+extern srpc_service_t lstcon_acceptor_service;
+
+int
+lstcon_session_new(char *name, int key,
+                   int timeout,int force, lst_sid_t *sid_up)
+{
+        int     rc = 0;
+        int     i;
+
+        if (console_session.ses_state != LST_SESSION_NONE) {
+                /* session exists */
+                if (!force) {
+                        CERROR("Session %s already exists\n",
+                               console_session.ses_name);
+                        return -EEXIST;
+                }
+
+                rc = lstcon_session_end();
+
+                /* lstcon_session_end() only return local error */
+                if  (rc != 0)
+                        return rc;
+        }
+
+        for (i = 0; i < LST_GLOBAL_HASHSIZE; i++) {
+                LASSERT (list_empty(&console_session.ses_ndl_hash[i]));
+        }
+
+        rc = lstcon_batch_add(LST_DEFAULT_BATCH);
+        if (rc != 0)
+                return rc;
+
+        rc = lstcon_rpc_pinger_start();
+        if (rc != 0) {
+                lstcon_batch_t *bat;
+
+                lstcon_batch_find(LST_DEFAULT_BATCH, &bat);
+                lstcon_batch_destroy(bat);
+
+                return rc;
+        }
+
+        lstcon_new_session_id(&console_session.ses_id);
+
+        console_session.ses_key     = key;
+        console_session.ses_state   = LST_SESSION_ACTIVE;
+        console_session.ses_force   = !!force;
+        console_session.ses_timeout = (timeout <= 0)? LST_CONSOLE_TIMEOUT:
+                                                      timeout;
+        strcpy(console_session.ses_name, name);
+
+        if (copy_to_user(sid_up, &console_session.ses_id,
+                         sizeof(lst_sid_t)) == 0)
+                return rc;
+
+        lstcon_session_end();
+
+        return -EFAULT;
+}
+
+int
+lstcon_session_info(lst_sid_t *sid_up, int *key_up,
+                    lstcon_ndlist_ent_t *ndinfo_up, char *name_up, int len)
+{
+        lstcon_ndlist_ent_t *entp;
+        lstcon_ndlink_t     *ndl;
+        int                  rc = 0;
+        
+        if (console_session.ses_state != LST_SESSION_ACTIVE)
+                return -ESRCH;
+
+        LIBCFS_ALLOC(entp, sizeof(*entp));
+        if (entp == NULL)
+                return -ENOMEM;
+
+        memset(entp, 0, sizeof(*entp));
+
+        list_for_each_entry(ndl, &console_session.ses_ndl_list, ndl_link)
+                LST_NODE_STATE_COUNTER(ndl->ndl_node, entp);
+
+        if (copy_to_user(sid_up, &console_session.ses_id, sizeof(lst_sid_t)) ||
+            copy_to_user(key_up, &console_session.ses_key, sizeof(int)) ||
+            copy_to_user(ndinfo_up, entp, sizeof(*entp)) ||
+            copy_to_user(name_up, console_session.ses_name, len))
+                rc = -EFAULT;
+
+        LIBCFS_FREE(entp, sizeof(*entp));
+
+        return rc;
+}
+
+int
+lstcon_session_end()
+{
+        lstcon_rpc_trans_t *trans;
+        lstcon_group_t     *grp;
+        lstcon_batch_t     *bat;
+        int                 rc = 0;
+
+        LASSERT (console_session.ses_state == LST_SESSION_ACTIVE);
+
+        rc = lstcon_rpc_trans_ndlist(&console_session.ses_ndl_list, 
+                                     NULL, LST_TRANS_SESEND, NULL,
+                                     lstcon_sesrpc_condition, &trans);
+        if (rc != 0) {
+                CERROR("Can't create transaction: %d\n", rc);
+                return rc;
+        }
+
+        console_session.ses_shutdown = 1;
+
+        lstcon_rpc_pinger_stop();
+
+        lstcon_rpc_trans_postwait(trans, LST_TRANS_TIMEOUT);
+
+        lstcon_rpc_trans_destroy(trans);
+        /* User can do nothing even rpc failed, so go on */
+
+        /* waiting for orphan rpcs to die */
+        lstcon_rpc_cleanup_wait();
+
+        console_session.ses_id    = LST_INVALID_SID;
+        console_session.ses_state = LST_SESSION_NONE;
+        console_session.ses_key   = 0;
+        console_session.ses_force = 0;
+
+        /* destroy all batches */
+        while (!list_empty(&console_session.ses_bat_list)) {
+                bat = list_entry(console_session.ses_bat_list.next,
+                                 lstcon_batch_t, bat_link);
+
+                lstcon_batch_destroy(bat);
+        }
+
+        /* destroy all groups */
+        while (!list_empty(&console_session.ses_grp_list)) {
+                grp = list_entry(console_session.ses_grp_list.next,
+                                 lstcon_group_t, grp_link);
+                LASSERT (grp->grp_ref == 1);
+
+                lstcon_group_put(grp);
+        }
+
+        /* all nodes should be released */
+        LASSERT (list_empty(&console_session.ses_ndl_list));
+
+        console_session.ses_shutdown = 0;
+        console_session.ses_expired  = 0;
+
+        return rc;
+}
+
+static int
+lstcon_acceptor_handle (srpc_server_rpc_t *rpc)
+{
+        srpc_msg_t        *rep  = &rpc->srpc_replymsg;
+        srpc_msg_t        *req  = &rpc->srpc_reqstbuf->buf_msg;
+        srpc_join_reqst_t *jreq = &req->msg_body.join_reqst;
+        srpc_join_reply_t *jrep = &rep->msg_body.join_reply;
+        lstcon_group_t    *grp  = NULL;
+        lstcon_ndlink_t   *ndl;
+        int                rc   = 0;
+
+        sfw_unpack_message(req);
+
+        mutex_down(&console_session.ses_mutex);
+
+        jrep->join_sid = console_session.ses_id;
+
+        if (console_session.ses_id.ses_nid == LNET_NID_ANY) {
+                jrep->join_status = ESRCH;
+                goto out;
+        }
+
+        if (jreq->join_sid.ses_nid != LNET_NID_ANY &&
+             !lstcon_session_match(jreq->join_sid)) {
+                jrep->join_status = EBUSY;
+                goto out;
+        }
+
+        if (lstcon_group_find(jreq->join_group, &grp) != 0) {
+                rc = lstcon_group_alloc(jreq->join_group, &grp);
+                if (rc != 0) {
+                        CERROR("Out of memory\n");
+                        goto out;
+                }
+
+                list_add_tail(&grp->grp_link,
+                              &console_session.ses_grp_list);
+                lstcon_group_addref(grp);
+        }
+
+        if (grp->grp_ref > 2) {
+                /* Group in using */
+                jrep->join_status = EBUSY;
+                goto out;
+        }
+
+        rc = lstcon_group_ndlink_find(grp, rpc->srpc_peer, &ndl, 0);
+        if (rc == 0) {
+                jrep->join_status = EEXIST;
+                goto out;
+        }
+
+        rc = lstcon_group_ndlink_find(grp, rpc->srpc_peer, &ndl, 1);
+        if (rc != 0) {
+                CERROR("Out of memory\n");
+                goto out;
+        }
+
+        ndl->ndl_node->nd_state   = LST_NODE_ACTIVE;
+        ndl->ndl_node->nd_timeout = console_session.ses_timeout;
+
+        strcpy(jrep->join_session, console_session.ses_name);
+        jrep->join_timeout = console_session.ses_timeout;
+        jrep->join_status  = 0;
+
+out:
+        if (grp != NULL)
+                lstcon_group_put(grp);
+
+        mutex_up(&console_session.ses_mutex);
+
+        return rc;
+}
+
+srpc_service_t lstcon_acceptor_service =
+{
+        .sv_name        = "join session",
+        .sv_handler     = lstcon_acceptor_handle,
+        .sv_bulk_ready  = NULL,
+        .sv_id          = SRPC_SERVICE_JOIN,
+        .sv_concur      = SFW_SERVICE_CONCURRENCY,
+};
+
+extern int lstcon_ioctl_entry(unsigned int cmd, struct libcfs_ioctl_data *data);
+
+DECLARE_IOCTL_HANDLER(lstcon_ioctl_handler, lstcon_ioctl_entry);
+
+/* initialize console */
+int
+lstcon_console_init(void)
+{
+        int     i;
+        int     n;
+        int     rc;
+
+        memset(&console_session, 0, sizeof(lstcon_session_t));
+
+        console_session.ses_id      = LST_INVALID_SID;
+        console_session.ses_state   = LST_SESSION_NONE;
+        console_session.ses_timeout = 0;
+        console_session.ses_force   = 0;
+        console_session.ses_expired = 0;
+        console_session.ses_laststamp = cfs_time_current_sec();   
+
+        init_mutex(&console_session.ses_mutex);
+
+        CFS_INIT_LIST_HEAD(&console_session.ses_ndl_list);
+        CFS_INIT_LIST_HEAD(&console_session.ses_grp_list);
+        CFS_INIT_LIST_HEAD(&console_session.ses_bat_list);
+        CFS_INIT_LIST_HEAD(&console_session.ses_trans_list);
+
+        LIBCFS_ALLOC(console_session.ses_ndl_hash,
+                     sizeof(struct list_head) * LST_GLOBAL_HASHSIZE);
+        if (console_session.ses_ndl_hash == NULL)
+                return -ENOMEM;
+
+        for (i = 0; i < LST_GLOBAL_HASHSIZE; i++)
+                CFS_INIT_LIST_HEAD(&console_session.ses_ndl_hash[i]);
+
+        rc = srpc_add_service(&lstcon_acceptor_service);
+        LASSERT (rc != -EBUSY);
+        if (rc != 0) {
+                LIBCFS_FREE(console_session.ses_ndl_hash,
+                            sizeof(struct list_head) * LST_GLOBAL_HASHSIZE);
+                return rc;
+        }
+
+        n = srpc_service_add_buffers(&lstcon_acceptor_service, SFW_POST_BUFFERS);
+        if (n != SFW_POST_BUFFERS) {
+                rc = -ENOMEM;
+                goto out;
+        }
+
+        rc = libcfs_register_ioctl(&lstcon_ioctl_handler);
+
+        if (rc == 0) {
+                lstcon_rpc_module_init();
+                return 0;
+        }
+
+out:
+        srpc_shutdown_service(&lstcon_acceptor_service);
+        srpc_remove_service(&lstcon_acceptor_service);
+
+        LIBCFS_FREE(console_session.ses_ndl_hash,
+                    sizeof(struct list_head) * LST_GLOBAL_HASHSIZE);
+
+        srpc_wait_service_shutdown(&lstcon_acceptor_service);
+
+        return rc;
+}
+
+int
+lstcon_console_fini(void)
+{
+        int     i;
+
+        mutex_down(&console_session.ses_mutex);
+
+        libcfs_deregister_ioctl(&lstcon_ioctl_handler);
+
+        srpc_shutdown_service(&lstcon_acceptor_service);
+        srpc_remove_service(&lstcon_acceptor_service);
+
+        if (console_session.ses_state != LST_SESSION_NONE) 
+                lstcon_session_end();
+
+        lstcon_rpc_module_fini();
+
+        mutex_up(&console_session.ses_mutex);
+
+        LASSERT (list_empty(&console_session.ses_ndl_list));
+        LASSERT (list_empty(&console_session.ses_grp_list));
+        LASSERT (list_empty(&console_session.ses_bat_list));
+        LASSERT (list_empty(&console_session.ses_trans_list));
+
+        for (i = 0; i < LST_NODE_HASHSIZE; i++) {
+                LASSERT (list_empty(&console_session.ses_ndl_hash[i]));
+        }
+
+        LIBCFS_FREE(console_session.ses_ndl_hash,
+                    sizeof(struct list_head) * LST_GLOBAL_HASHSIZE);
+
+        srpc_wait_service_shutdown(&lstcon_acceptor_service);
+
+        return 0;
+}
+
+#endif
diff --git a/lnet/selftest/console.h b/lnet/selftest/console.h
new file mode 100644 (file)
index 0000000..cc72930
--- /dev/null
@@ -0,0 +1,190 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ * 
+ * This file is part of Lustre, http://www.lustre.org
+ *
+ * kernel structure for LST console
+ */
+
+#ifndef __LST_CONSOLE_H__
+#define __LST_CONSOLE_H__
+
+#ifdef __KERNEL__
+
+#include <libcfs/kp30.h>
+#include <lnet/lnet.h>
+#include <lnet/lib-types.h>
+#include <lnet/lnetst.h>
+#include "selftest.h"
+#include "conrpc.h"
+
+typedef struct lstcon_node {
+        lnet_process_id_t       nd_id;          /* id of the node */
+        int                     nd_ref;         /* reference count */
+        int                     nd_state;       /* state of the node */
+        int                     nd_timeout;     /* session timeout */
+        cfs_time_t              nd_stamp;       /* timestamp of last replied RPC */
+        struct lstcon_rpc       nd_ping;        /* ping rpc */
+} lstcon_node_t;                                /*** node descriptor */
+
+typedef struct {
+        struct list_head        ndl_link;       /* chain on list */
+        struct list_head        ndl_hlink;      /* chain on hash */
+        lstcon_node_t          *ndl_node;       /* pointer to node */
+} lstcon_ndlink_t;                              /*** node link descriptor */
+
+typedef struct {
+        struct list_head        grp_link;       /* chain on global group list */
+        int                     grp_ref;        /* reference count */
+        int                     grp_nnode;      /* # of nodes */
+        char                    grp_name[LST_NAME_SIZE]; /* group name */
+
+        struct list_head        grp_trans_list; /* transaction list */
+        struct list_head        grp_ndl_list;   /* nodes list */
+        struct list_head        grp_ndl_hash[0];/* hash table for nodes */
+} lstcon_group_t;                               /*** (alias of nodes) group descriptor */
+
+#define LST_BATCH_IDLE          0xB0            /* idle batch */
+#define LST_BATCH_RUNNING       0xB1            /* running batch */
+
+typedef struct lstcon_tsb_hdr {
+        lst_bid_t               tsb_id;         /* batch ID */
+        int                     tsb_index;      /* test index */
+} lstcon_tsb_hdr_t;
+
+typedef struct {
+        lstcon_tsb_hdr_t        bat_hdr;        /* test_batch header */
+        struct list_head        bat_link;       /* chain on session's batches list */
+        int                     bat_ntest;      /* # of test */
+        int                     bat_state;      /* state of the batch */
+        int                     bat_arg;        /* parameter for run|stop, timeout for run, force for stop */
+        char                    bat_name[LST_NAME_SIZE]; /* name of batch */
+
+        struct list_head        bat_test_list;  /* list head of tests (lstcon_test_t) */
+        struct list_head        bat_trans_list; /* list head of transaction */
+        struct list_head        bat_cli_list;   /* list head of client nodes (lstcon_node_t) */
+        struct list_head       *bat_cli_hash;   /* hash table of client nodes */ 
+        struct list_head        bat_srv_list;   /* list head of server nodes */
+        struct list_head       *bat_srv_hash;   /* hash table of server nodes */
+} lstcon_batch_t;                                  /*** (tests ) batch descritptor */
+
+typedef struct lstcon_test {
+        lstcon_tsb_hdr_t        tes_hdr;        /* test batch header */
+        struct list_head        tes_link;       /* chain on batch's tests list */
+        lstcon_batch_t         *tes_batch;      /* pointer to batch */
+
+        int                     tes_type;       /* type of the test, i.e: bulk, ping */
+        int                     tes_stop_onerr; /* stop on error */
+        int                     tes_oneside;    /* one-sided test */
+        int                     tes_concur;     /* concurrency */
+        int                     tes_loop;       /* loop count */
+        int                     tes_dist;       /* nodes distribution of target group */
+        int                     tes_span;       /* nodes span of target group */
+        int                     tes_cliidx;     /* client index, used for RPC creating */
+
+        struct list_head        tes_trans_list; /* transaction list */
+        lstcon_group_t         *tes_src_grp;    /* group run the test */
+        lstcon_group_t         *tes_dst_grp;    /* target group */
+
+        int                     tes_paramlen;   /* test parameter length */
+        char                    tes_param[0];   /* test parameter */
+} lstcon_test_t;                                   /*** a single test descriptor */
+
+#define LST_GLOBAL_HASHSIZE     503             /* global nodes hash table size */
+#define LST_NODE_HASHSIZE       239             /* node hash table (for batch or group) */
+
+#define LST_SESSION_NONE        0x0             /* no session */
+#define LST_SESSION_ACTIVE      0x1             /* working session */
+
+#define LST_CONSOLE_TIMEOUT     300             /* default console timeout */
+
+typedef struct {
+        struct semaphore        ses_mutex;      /* lock for session, only one thread can enter session */
+        lst_sid_t               ses_id;         /* global session id */
+        int                     ses_key;        /* local session key */
+        int                     ses_state;      /* state of session */
+        int                     ses_timeout;    /* timeout in seconds */
+        time_t                  ses_laststamp;  /* last operation stamp (seconds) */
+        int                     ses_force:1;    /* force creating */
+        int                     ses_shutdown:1; /* session is shutting down */
+        int                     ses_expired:1;  /* console is timedout */
+        __u64                   ses_id_cookie;  /* batch id cookie */
+        char                    ses_name[LST_NAME_SIZE];  /* session name */
+        lstcon_rpc_trans_t     *ses_ping;       /* session pinger */
+        stt_timer_t             ses_ping_timer; /* timer for pinger */
+        lstcon_trans_stat_t     ses_trans_stat; /* transaction stats */
+
+        struct list_head        ses_trans_list; /* global list of transaction */
+        struct list_head        ses_grp_list;   /* global list of groups */
+        struct list_head        ses_bat_list;   /* global list of batches */
+        struct list_head        ses_ndl_list;   /* global list of nodes */
+        struct list_head       *ses_ndl_hash;   /* hash table of nodes */
+
+        spinlock_t              ses_rpc_lock;   /* serialize */
+        int                     ses_rpc_pending; /* number of pending RPCs */
+        struct list_head        ses_rpc_list;   /* list head of RPCs */
+        struct list_head        ses_rpc_freelist; /* idle console rpc */
+} lstcon_session_t;                             /*** session descriptor */
+
+extern lstcon_session_t         console_session;
+static inline lstcon_trans_stat_t *
+lstcon_trans_stat(void)
+{
+        return &console_session.ses_trans_stat;
+}
+
+static inline struct list_head *
+lstcon_id2hash (lnet_process_id_t id, struct list_head *hash)
+{
+        unsigned int idx = LNET_NIDADDR(id.nid) % LST_NODE_HASHSIZE;
+
+        return &hash[idx];
+}
+
+extern int lstcon_session_match(lst_sid_t sid);
+extern int lstcon_session_new(char *name, int key,
+                              int timeout, int flags, lst_sid_t *sid_up);
+extern int lstcon_session_info(lst_sid_t *sid_up, int *key,
+                               lstcon_ndlist_ent_t *entp, char *name_up, int len);
+extern int lstcon_session_end(void);
+extern int lstcon_session_debug(int timeout, struct list_head *result_up);
+extern int lstcon_batch_debug(int timeout, char *name, 
+                              int client, struct list_head *result_up);
+extern int lstcon_group_debug(int timeout, char *name,
+                              struct list_head *result_up);
+extern int lstcon_nodes_debug(int timeout, int nnd, lnet_process_id_t *nds_up,
+                              struct list_head *result_up);
+extern int lstcon_group_add(char *name);
+extern int lstcon_group_del(char *name);
+extern int lstcon_group_clean(char *name, int args);
+extern int lstcon_group_refresh(char *name, struct list_head *result_up);
+extern int lstcon_nodes_add(char *name, int nnd, lnet_process_id_t *nds_up,
+                            struct list_head *result_up);
+extern int lstcon_nodes_remove(char *name, int nnd, lnet_process_id_t *nds_up,
+                               struct list_head *result_up);
+extern int lstcon_group_info(char *name, lstcon_ndlist_ent_t *gent_up, 
+                             int *index_p, int *ndent_p, lstcon_node_ent_t *ndents_up);
+extern int lstcon_group_list(int idx, int len, char *name_up);
+extern int lstcon_batch_add(char *name);
+extern int lstcon_batch_run(char *name, int timeout, struct list_head *result_up);
+extern int lstcon_batch_stop(char *name, int force, struct list_head *result_up);
+extern int lstcon_test_batch_query(char *name, int testidx,
+                                   int client, int timeout,
+                                   struct list_head *result_up);
+extern int lstcon_batch_del(char *name);
+extern int lstcon_batch_list(int idx, int namelen, char *name_up);
+extern int lstcon_batch_info(char *name, lstcon_test_batch_ent_t *ent_up,
+                             int server, int testidx, int *index_p,
+                             int *ndent_p, lstcon_node_ent_t *dents_up);
+extern int lstcon_group_stat(char *grp_name, int timeout,
+                             struct list_head *result_up);
+extern int lstcon_nodes_stat(int count, lnet_process_id_t *ids_up,
+                             int timeout, struct list_head *result_up);
+extern int lstcon_test_add(char *name, int type, int loop, int concur,
+                           int dist, int span, char *src_name, char * dst_name,
+                           void *param, int paramlen, struct list_head *result_up);
+#endif
+
+#endif  
diff --git a/lnet/selftest/framework.c b/lnet/selftest/framework.c
new file mode 100644 (file)
index 0000000..6a2487b
--- /dev/null
@@ -0,0 +1,1661 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Copyright (C) 2001, 2002 Cluster File Systems, Inc.
+ *   Authors: Isaac Huang <isaac@clusterfs.com>
+ *            Liang Zhen  <liangzhen@clusterfs.com>
+ */
+
+#define DEBUG_SUBSYSTEM S_LNET
+
+#include <libcfs/kp30.h>
+#include <libcfs/libcfs.h>
+#include <lnet/lib-lnet.h>
+
+#include "selftest.h"
+
+
+static int session_timeout = 100;
+CFS_MODULE_PARM(session_timeout, "i", int, 0444,
+                "test session timeout in seconds (100 by default, 0 == never)");
+
+#define SFW_TEST_CONCURRENCY     128
+#define SFW_TEST_RPC_TIMEOUT     64
+#define SFW_CLIENT_RPC_TIMEOUT   64  /* in seconds */
+#define SFW_EXTRA_TEST_BUFFERS   8 /* tolerate buggy peers with extra buffers */
+
+#define sfw_test_buffers(tsi)    ((tsi)->tsi_loop + SFW_EXTRA_TEST_BUFFERS)
+
+#define sfw_unpack_id(id)               \
+do {                                    \
+        __swab64s(&(id).nid);           \
+        __swab32s(&(id).pid);           \
+} while (0)
+
+#define sfw_unpack_sid(sid)             \
+do {                                    \
+        __swab64s(&(sid).ses_nid);      \
+        __swab64s(&(sid).ses_stamp);    \
+} while (0)
+
+#define sfw_unpack_fw_counters(fc)        \
+do {                                      \
+        __swab32s(&(fc).brw_errors);      \
+        __swab32s(&(fc).active_tests);    \
+        __swab32s(&(fc).active_batches);  \
+        __swab32s(&(fc).zombie_sessions); \
+} while (0)
+
+#define sfw_unpack_rpc_counters(rc)     \
+do {                                    \
+        __swab32s(&(rc).errors);        \
+        __swab32s(&(rc).rpcs_sent);     \
+        __swab32s(&(rc).rpcs_rcvd);     \
+        __swab32s(&(rc).rpcs_dropped);  \
+        __swab32s(&(rc).rpcs_expired);  \
+        __swab64s(&(rc).bulk_get);      \
+        __swab64s(&(rc).bulk_put);      \
+} while (0)
+
+#define sfw_unpack_lnet_counters(lc)    \
+do {                                    \
+        __swab32s(&(lc).errors);        \
+        __swab32s(&(lc).msgs_max);      \
+        __swab32s(&(lc).msgs_alloc);    \
+        __swab32s(&(lc).send_count);    \
+        __swab32s(&(lc).recv_count);    \
+        __swab32s(&(lc).drop_count);    \
+        __swab32s(&(lc).route_count);   \
+        __swab64s(&(lc).send_length);   \
+        __swab64s(&(lc).recv_length);   \
+        __swab64s(&(lc).drop_length);   \
+        __swab64s(&(lc).route_length);  \
+} while (0)
+
+#define sfw_test_active(t)      (atomic_read(&(t)->tsi_nactive) != 0)
+#define sfw_batch_active(b)     (atomic_read(&(b)->bat_nactive) != 0)
+
+struct smoketest_framework {
+        struct list_head   fw_zombie_rpcs;     /* RPCs to be recycled */
+        struct list_head   fw_zombie_sessions; /* stopping sessions */
+        struct list_head   fw_tests;           /* registered test cases */
+        atomic_t           fw_nzombies;        /* # zombie sessions */
+        spinlock_t         fw_lock;            /* serialise */
+        sfw_session_t     *fw_session;         /* _the_ session */
+        int                fw_shuttingdown;    /* shutdown in progress */
+        srpc_server_rpc_t *fw_active_srpc;     /* running RPC */
+} sfw_data;
+
+/* forward ref's */
+int sfw_stop_batch (sfw_batch_t *tsb, int force);
+void sfw_destroy_session (sfw_session_t *sn);
+
+static inline sfw_test_case_t *
+sfw_find_test_case(int id)
+{
+        sfw_test_case_t *tsc;
+
+        LASSERT (id <= SRPC_SERVICE_MAX_ID);
+        LASSERT (id > SRPC_FRAMEWORK_SERVICE_MAX_ID);
+
+        list_for_each_entry (tsc, &sfw_data.fw_tests, tsc_list) {
+                if (tsc->tsc_srv_service->sv_id == id)
+                        return tsc;
+        }
+
+        return NULL;
+}
+
+static int
+sfw_register_test (srpc_service_t *service, sfw_test_client_ops_t *cliops)
+{
+        sfw_test_case_t *tsc;
+
+        if (sfw_find_test_case(service->sv_id) != NULL) {
+                CERROR ("Failed to register test %s (%d)\n",
+                        service->sv_name, service->sv_id);
+                return -EEXIST;
+        }
+
+        LIBCFS_ALLOC(tsc, sizeof(sfw_test_case_t));
+        if (tsc == NULL)
+                return -ENOMEM;
+
+        memset(tsc, 0, sizeof(sfw_test_case_t));
+        tsc->tsc_cli_ops     = cliops;
+        tsc->tsc_srv_service = service;
+
+        list_add_tail(&tsc->tsc_list, &sfw_data.fw_tests);
+        return 0;
+}
+
+void
+sfw_add_session_timer (void)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+        stt_timer_t   *timer = &sn->sn_timer;
+
+        LASSERT (!sfw_data.fw_shuttingdown);
+
+        if (sn == NULL || sn->sn_timeout == 0)
+                return;
+
+        LASSERT (!sn->sn_timer_active);
+
+        sn->sn_timer_active = 1;
+        timer->stt_expires = cfs_time_add(sn->sn_timeout,
+                                          cfs_time_current_sec());
+        stt_add_timer(timer);
+        return;
+}
+
+int
+sfw_del_session_timer (void)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+
+        if (sn == NULL || !sn->sn_timer_active)
+                return 0;
+
+        LASSERT (sn->sn_timeout != 0);
+
+        if (stt_del_timer(&sn->sn_timer)) { /* timer defused */
+                sn->sn_timer_active = 0;
+                return 0;
+        }
+
+#ifndef __KERNEL__
+        /* Racing is impossible in single-threaded userland selftest */
+        LBUG();
+#endif
+        return EBUSY; /* racing with sfw_session_expired() */
+}
+
+/* called with sfw_data.fw_lock held */
+static void
+sfw_deactivate_session (void)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+        int            nactive = 0;
+        sfw_batch_t   *tsb;
+
+        if (sn == NULL)
+                return;
+
+        LASSERT (!sn->sn_timer_active);
+
+        sfw_data.fw_session = NULL;
+        atomic_inc(&sfw_data.fw_nzombies);
+        list_add(&sn->sn_list, &sfw_data.fw_zombie_sessions);
+
+        list_for_each_entry (tsb, &sn->sn_batches, bat_list) {
+                if (sfw_batch_active(tsb)) {
+                        nactive++;
+                        sfw_stop_batch(tsb, 1);
+                }
+        }
+
+        if (nactive != 0)
+                return;   /* wait for active batches to stop */
+
+        list_del_init(&sn->sn_list);
+        spin_unlock(&sfw_data.fw_lock);
+
+        sfw_destroy_session(sn);
+
+        spin_lock(&sfw_data.fw_lock);
+        return;
+}
+
+#ifndef __KERNEL__
+
+int
+sfw_session_removed (void)
+{
+        return (sfw_data.fw_session == NULL) ? 1 : 0;
+}
+
+#endif
+
+void
+sfw_session_expired (void *data)
+{
+        sfw_session_t *sn = data;
+
+        spin_lock(&sfw_data.fw_lock);
+
+        LASSERT (sn->sn_timer_active);
+        LASSERT (sn == sfw_data.fw_session);
+
+        CWARN ("Session expired! sid: %s-"LPU64", name: %s\n",
+               libcfs_nid2str(sn->sn_id.ses_nid),
+               sn->sn_id.ses_stamp, &sn->sn_name[0]);
+
+        sn->sn_timer_active = 0;
+        sfw_deactivate_session();
+
+        spin_unlock(&sfw_data.fw_lock);
+        return;
+}
+
+static inline void
+sfw_init_session (sfw_session_t *sn, lst_sid_t sid, const char *name)
+{
+        stt_timer_t *timer = &sn->sn_timer;
+
+        memset(sn, 0, sizeof(sfw_session_t));
+        CFS_INIT_LIST_HEAD(&sn->sn_list);
+        CFS_INIT_LIST_HEAD(&sn->sn_batches);
+        atomic_set(&sn->sn_brw_errors, 0);
+        strncpy(&sn->sn_name[0], name, LST_NAME_SIZE);
+
+        sn->sn_timer_active = 0;
+        sn->sn_id           = sid;
+        sn->sn_timeout      = session_timeout;
+
+        timer->stt_data = sn;
+        timer->stt_func = sfw_session_expired;
+        CFS_INIT_LIST_HEAD(&timer->stt_list);
+}
+
+/* completion handler for incoming framework RPCs */
+void
+sfw_server_rpc_done (srpc_server_rpc_t *rpc)
+{
+        srpc_service_t *sv = rpc->srpc_service;
+        int             status = rpc->srpc_status;
+
+        CDEBUG (D_NET,
+                "Incoming framework RPC done: "
+                "service %s, peer %s, status %s:%d\n",
+                sv->sv_name, libcfs_id2str(rpc->srpc_peer),
+                swi_state2str(rpc->srpc_wi.wi_state),
+                status);
+
+        if (rpc->srpc_bulk != NULL)
+                sfw_free_pages(rpc);
+        return;
+}
+
+void
+sfw_client_rpc_fini (srpc_client_rpc_t *rpc)
+{
+        LASSERT (rpc->crpc_bulk.bk_niov == 0);
+        LASSERT (list_empty(&rpc->crpc_list));
+        LASSERT (atomic_read(&rpc->crpc_refcount) == 0);
+#ifndef __KERNEL__
+        LASSERT (rpc->crpc_bulk.bk_pages == NULL);
+#endif
+
+        CDEBUG (D_NET,
+                "Outgoing framework RPC done: "
+                "service %d, peer %s, status %s:%d:%d\n",
+                rpc->crpc_service, libcfs_id2str(rpc->crpc_dest),
+                swi_state2str(rpc->crpc_wi.wi_state),
+                rpc->crpc_aborted, rpc->crpc_status);
+
+        spin_lock(&sfw_data.fw_lock);
+
+        /* my callers must finish all RPCs before shutting me down */
+        LASSERT (!sfw_data.fw_shuttingdown);
+        list_add(&rpc->crpc_list, &sfw_data.fw_zombie_rpcs);
+
+        spin_unlock(&sfw_data.fw_lock);
+        return;
+}
+
+sfw_batch_t *
+sfw_find_batch (lst_bid_t bid)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+        sfw_batch_t   *bat;
+
+        LASSERT (sn != NULL);
+
+        list_for_each_entry (bat, &sn->sn_batches, bat_list) {
+                if (bat->bat_id.bat_id == bid.bat_id)
+                        return bat;
+        }
+
+        return NULL;
+}
+
+sfw_batch_t *
+sfw_bid2batch (lst_bid_t bid)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+        sfw_batch_t   *bat;
+
+        LASSERT (sn != NULL);
+
+        bat = sfw_find_batch(bid);
+        if (bat != NULL)
+                return bat;
+
+        LIBCFS_ALLOC(bat, sizeof(sfw_batch_t));
+        if (bat == NULL) 
+                return NULL;
+
+        bat->bat_error    = 0;
+        bat->bat_session  = sn;
+        bat->bat_id       = bid;
+        atomic_set(&bat->bat_nactive, 0);
+        CFS_INIT_LIST_HEAD(&bat->bat_tests);
+
+        list_add_tail(&bat->bat_list, &sn->sn_batches);
+        return bat;
+}
+
+int
+sfw_get_stats (srpc_stat_reqst_t *request, srpc_stat_reply_t *reply)
+{
+        sfw_session_t  *sn = sfw_data.fw_session;
+        sfw_counters_t *cnt = &reply->str_fw;
+        sfw_batch_t    *bat;
+
+        reply->str_sid = (sn == NULL) ? LST_INVALID_SID : sn->sn_id;
+
+        if (request->str_sid.ses_nid == LNET_NID_ANY) {
+                reply->str_status = EINVAL;
+                return 0;
+        }
+
+        if (sn == NULL || !sfw_sid_equal(request->str_sid, sn->sn_id)) {
+                reply->str_status = ESRCH;
+                return 0;
+        }
+
+        LNET_LOCK();
+        reply->str_lnet = the_lnet.ln_counters;
+        LNET_UNLOCK();
+
+        srpc_get_counters(&reply->str_rpc);
+
+        cnt->brw_errors      = atomic_read(&sn->sn_brw_errors);
+        cnt->zombie_sessions = atomic_read(&sfw_data.fw_nzombies);
+
+        cnt->active_tests = cnt->active_batches = 0;
+        list_for_each_entry (bat, &sn->sn_batches, bat_list) {
+                int n = atomic_read(&bat->bat_nactive);
+
+                if (n > 0) {
+                        cnt->active_batches++;
+                        cnt->active_tests += n;
+                }
+        }
+
+        reply->str_status = 0;
+        return 0;
+}
+
+int
+sfw_make_session (srpc_mksn_reqst_t *request, srpc_mksn_reply_t *reply)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+
+        if (request->mksn_sid.ses_nid == LNET_NID_ANY) {
+                reply->mksn_sid = (sn == NULL) ? LST_INVALID_SID : sn->sn_id;
+                reply->mksn_status = EINVAL;
+                return 0;
+        }
+
+        if (sn != NULL && !request->mksn_force) {
+                reply->mksn_sid    = sn->sn_id;
+                reply->mksn_status = EBUSY;
+                strncpy(&reply->mksn_name[0], &sn->sn_name[0], LST_NAME_SIZE);
+                return 0;
+        }
+        
+        LIBCFS_ALLOC(sn, sizeof(sfw_session_t));
+        if (sn == NULL) {
+                CERROR ("Dropping RPC (mksn) under memory pressure.\n");
+                return -ENOMEM;
+        }
+
+        sfw_init_session(sn, request->mksn_sid, &request->mksn_name[0]);
+
+        spin_lock(&sfw_data.fw_lock);
+
+        sfw_deactivate_session();
+        LASSERT (sfw_data.fw_session == NULL);
+        sfw_data.fw_session = sn;
+
+        spin_unlock(&sfw_data.fw_lock);
+
+        reply->mksn_status  = 0;
+        reply->mksn_sid     = sn->sn_id;
+        reply->mksn_timeout = sn->sn_timeout;
+        return 0;
+}
+
+int
+sfw_remove_session (srpc_rmsn_reqst_t *request, srpc_rmsn_reply_t *reply)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+
+        reply->rmsn_sid = (sn == NULL) ? LST_INVALID_SID : sn->sn_id;
+
+        if (request->rmsn_sid.ses_nid == LNET_NID_ANY) {
+                reply->rmsn_status = EINVAL;
+                return 0;
+        }
+
+        if (sn == NULL || !sfw_sid_equal(request->rmsn_sid, sn->sn_id)) {
+                reply->rmsn_status = (sn == NULL) ? ESRCH : EBUSY;
+                return 0;
+        }
+
+        spin_lock(&sfw_data.fw_lock);
+        sfw_deactivate_session();
+        spin_unlock(&sfw_data.fw_lock);
+
+        reply->rmsn_status = 0;
+        reply->rmsn_sid    = LST_INVALID_SID;
+        LASSERT (sfw_data.fw_session == NULL);
+        return 0;
+}
+
+int
+sfw_debug_session (srpc_debug_reqst_t *request, srpc_debug_reply_t *reply)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+
+        if (sn == NULL) {
+                reply->dbg_status = ESRCH;
+                reply->dbg_sid    = LST_INVALID_SID;
+                return 0;
+        } 
+
+        reply->dbg_status  = 0;
+        reply->dbg_sid     = sn->sn_id;      
+        reply->dbg_timeout = sn->sn_timeout;
+        strncpy(reply->dbg_name, &sn->sn_name[0], LST_NAME_SIZE);
+
+        return 0;
+}
+
+void
+sfw_test_rpc_fini (srpc_client_rpc_t *rpc)
+{
+        sfw_test_unit_t     *tsu = rpc->crpc_priv;
+        sfw_test_instance_t *tsi = tsu->tsu_instance;
+
+        /* Called with hold of tsi->tsi_lock */
+        LASSERT (list_empty(&rpc->crpc_list));
+        list_add(&rpc->crpc_list, &tsi->tsi_free_rpcs);
+}
+
+int
+sfw_load_test (sfw_test_instance_t *tsi)
+{
+        sfw_test_case_t *tsc = sfw_find_test_case(tsi->tsi_service);
+        int              nrequired = sfw_test_buffers(tsi);
+        int              nposted;
+
+        LASSERT (tsc != NULL);
+
+        if (tsi->tsi_is_client) {
+                tsi->tsi_ops = tsc->tsc_cli_ops;
+                return 0;
+        }
+
+        nposted = srpc_service_add_buffers(tsc->tsc_srv_service, nrequired);
+        if (nposted != nrequired) {
+                CWARN ("Failed to reserve enough buffers: "
+                       "service %s, %d needed, %d reserved\n",
+                       tsc->tsc_srv_service->sv_name, nrequired, nposted);
+                srpc_service_remove_buffers(tsc->tsc_srv_service, nposted);
+                return -ENOMEM;
+        }
+
+        CDEBUG (D_NET, "Reserved %d buffers for test %s\n",
+                nposted, tsc->tsc_srv_service->sv_name);
+        return 0;
+}
+
+void
+sfw_unload_test (sfw_test_instance_t *tsi)
+{
+        sfw_test_case_t *tsc = sfw_find_test_case(tsi->tsi_service);
+
+        LASSERT (tsc != NULL);
+
+        if (!tsi->tsi_is_client)
+                srpc_service_remove_buffers(tsc->tsc_srv_service,
+                                            sfw_test_buffers(tsi));
+        return;
+}
+
+void
+sfw_destroy_test_instance (sfw_test_instance_t *tsi)
+{
+        srpc_client_rpc_t *rpc;
+        sfw_test_unit_t   *tsu;
+
+        if (!tsi->tsi_is_client)
+                goto clean;
+
+        tsi->tsi_ops->tso_fini(tsi);
+
+        LASSERT (!tsi->tsi_stopping);
+        LASSERT (list_empty(&tsi->tsi_active_rpcs));
+        LASSERT (!sfw_test_active(tsi));
+
+        while (!list_empty(&tsi->tsi_units)) {
+                tsu = list_entry(tsi->tsi_units.next,
+                                 sfw_test_unit_t, tsu_list);
+                list_del(&tsu->tsu_list);
+                LIBCFS_FREE(tsu, sizeof(*tsu));
+        }
+
+        while (!list_empty(&tsi->tsi_free_rpcs)) {
+                rpc = list_entry(tsi->tsi_free_rpcs.next,
+                                 srpc_client_rpc_t, crpc_list);
+                list_del(&rpc->crpc_list);
+                LIBCFS_FREE(rpc, srpc_client_rpc_size(rpc));
+        }
+
+clean:
+        sfw_unload_test(tsi);
+        LIBCFS_FREE(tsi, sizeof(*tsi));
+        return;
+}
+
+void
+sfw_destroy_batch (sfw_batch_t *tsb)
+{
+        sfw_test_instance_t *tsi;
+
+        LASSERT (!sfw_batch_active(tsb));
+        LASSERT (list_empty(&tsb->bat_list));
+
+        while (!list_empty(&tsb->bat_tests)) {
+                tsi = list_entry(tsb->bat_tests.next,
+                                 sfw_test_instance_t, tsi_list);
+                list_del_init(&tsi->tsi_list);
+                sfw_destroy_test_instance(tsi);
+        }
+
+        LIBCFS_FREE(tsb, sizeof(sfw_batch_t));
+        return;
+}
+
+void
+sfw_destroy_session (sfw_session_t *sn)
+{
+        sfw_batch_t *batch;
+
+        LASSERT (list_empty(&sn->sn_list));
+        LASSERT (sn != sfw_data.fw_session);
+
+        while (!list_empty(&sn->sn_batches)) {
+                batch = list_entry(sn->sn_batches.next,
+                                   sfw_batch_t, bat_list);
+                list_del_init(&batch->bat_list);
+                sfw_destroy_batch(batch);
+        }
+
+        LIBCFS_FREE(sn, sizeof(*sn));
+        atomic_dec(&sfw_data.fw_nzombies);
+        return;
+}
+
+void
+sfw_unpack_test_req (srpc_msg_t *msg)
+{
+        srpc_test_reqst_t *req = &msg->msg_body.tes_reqst;
+
+        LASSERT (msg->msg_type == SRPC_MSG_TEST_REQST);
+        LASSERT (req->tsr_is_client);
+
+        if (msg->msg_magic == SRPC_MSG_MAGIC)
+                return; /* no flipping needed */
+
+        LASSERT (msg->msg_magic == __swab32(SRPC_MSG_MAGIC));
+
+        if (req->tsr_service == SRPC_SERVICE_BRW) {
+                test_bulk_req_t *bulk = &req->tsr_u.bulk;
+
+                __swab32s(&bulk->blk_opc);
+                __swab32s(&bulk->blk_npg);
+                __swab32s(&bulk->blk_flags);
+                return;
+        }
+
+        if (req->tsr_service == SRPC_SERVICE_PING) {
+                test_ping_req_t *ping = &req->tsr_u.ping;
+
+                __swab32s(&ping->png_size);
+                __swab32s(&ping->png_flags);
+                return;
+        }
+
+        LBUG ();
+        return;
+}
+
+int
+sfw_add_test_instance (sfw_batch_t *tsb, srpc_server_rpc_t *rpc)
+{
+        srpc_msg_t          *msg = &rpc->srpc_reqstbuf->buf_msg;
+        srpc_test_reqst_t   *req = &msg->msg_body.tes_reqst;
+        srpc_bulk_t         *bk = rpc->srpc_bulk;
+        int                  ndest = req->tsr_ndest;
+        sfw_test_unit_t     *tsu;
+        sfw_test_instance_t *tsi;
+        int                  i;
+        int                  rc;
+
+        LIBCFS_ALLOC(tsi, sizeof(*tsi));
+        if (tsi == NULL) {
+                CERROR ("Can't allocate test instance for batch: "LPU64"\n",
+                        tsb->bat_id.bat_id);
+                return -ENOMEM;
+        }
+
+        memset(tsi, 0, sizeof(*tsi));
+        spin_lock_init(&tsi->tsi_lock);
+        atomic_set(&tsi->tsi_nactive, 0);
+        CFS_INIT_LIST_HEAD(&tsi->tsi_units);
+        CFS_INIT_LIST_HEAD(&tsi->tsi_free_rpcs);
+        CFS_INIT_LIST_HEAD(&tsi->tsi_active_rpcs);
+
+        tsi->tsi_stopping   = 0;
+        tsi->tsi_batch      = tsb;
+        tsi->tsi_loop       = req->tsr_loop;
+        tsi->tsi_concur     = req->tsr_concur;
+        tsi->tsi_service    = req->tsr_service;
+        tsi->tsi_is_client  = !!(req->tsr_is_client);
+        tsi->tsi_stop_onerr = !!(req->tsr_stop_onerr);
+
+        rc = sfw_load_test(tsi);
+        if (rc != 0) {
+                LIBCFS_FREE(tsi, sizeof(*tsi));
+                return rc;
+        }
+
+        LASSERT (!sfw_batch_active(tsb));
+
+        if (!tsi->tsi_is_client) {
+                /* it's test server, just add it to tsb */
+                list_add_tail(&tsi->tsi_list, &tsb->bat_tests);
+                return 0;
+        }
+
+        LASSERT (bk != NULL);
+#ifndef __KERNEL__
+        LASSERT (bk->bk_pages != NULL);
+#endif
+        LASSERT (bk->bk_niov * SFW_ID_PER_PAGE >= ndest);
+        LASSERT (bk->bk_len >= sizeof(lnet_process_id_t) * ndest);
+
+        sfw_unpack_test_req(msg);
+        memcpy(&tsi->tsi_u, &req->tsr_u, sizeof(tsi->tsi_u));
+
+        for (i = 0; i < ndest; i++) {
+                lnet_process_id_t *dests;
+                lnet_process_id_t  id;
+                int                j;
+
+#ifdef __KERNEL__
+                dests = cfs_page_address(bk->bk_iovs[i / SFW_ID_PER_PAGE].kiov_page);
+                LASSERT (dests != NULL);  /* my pages are within KVM always */
+#else
+                dests = cfs_page_address(bk->bk_pages[i / SFW_ID_PER_PAGE]);
+#endif
+                id = dests[i % SFW_ID_PER_PAGE];
+                if (msg->msg_magic != SRPC_MSG_MAGIC)
+                        sfw_unpack_id(id);
+
+                for (j = 0; j < tsi->tsi_concur; j++) {
+                        LIBCFS_ALLOC(tsu, sizeof(sfw_test_unit_t));
+                        if (tsu == NULL) {
+                                rc = -ENOMEM;
+                                CERROR ("Can't allocate tsu for %d\n",
+                                        tsi->tsi_service);
+                                goto error;
+                        }
+
+                        tsu->tsu_dest     = id;
+                        tsu->tsu_instance = tsi;
+                        tsu->tsu_private  = NULL;
+                        list_add_tail(&tsu->tsu_list, &tsi->tsi_units);
+                }
+        }
+
+        rc = tsi->tsi_ops->tso_init(tsi);
+        if (rc == 0) {
+                list_add_tail(&tsi->tsi_list, &tsb->bat_tests);
+                return 0;
+        }
+
+error:
+        LASSERT (rc != 0);
+        sfw_destroy_test_instance(tsi);
+        return rc;
+}
+
+static void
+sfw_test_unit_done (sfw_test_unit_t *tsu)
+{
+        sfw_test_instance_t *tsi = tsu->tsu_instance;
+        sfw_batch_t         *tsb = tsi->tsi_batch;
+        sfw_session_t       *sn = tsb->bat_session;
+
+        LASSERT (sfw_test_active(tsi));
+
+        if (!atomic_dec_and_test(&tsi->tsi_nactive))
+                return;
+        
+        /* the test instance is done */
+        spin_lock(&tsi->tsi_lock);
+
+        tsi->tsi_stopping = 0;
+
+        spin_unlock(&tsi->tsi_lock);
+
+        spin_lock(&sfw_data.fw_lock);
+
+        if (!atomic_dec_and_test(&tsb->bat_nactive) || /* tsb still active */
+            sn == sfw_data.fw_session) {               /* sn also active */
+                spin_unlock(&sfw_data.fw_lock);
+                return;
+        }
+        
+        LASSERT (!list_empty(&sn->sn_list)); /* I'm a zombie! */
+
+        list_for_each_entry (tsb, &sn->sn_batches, bat_list) {
+                if (sfw_batch_active(tsb)) {
+                        spin_unlock(&sfw_data.fw_lock);
+                        return;
+                }
+        }
+
+        list_del_init(&sn->sn_list);
+        spin_unlock(&sfw_data.fw_lock);
+
+        sfw_destroy_session(sn);
+        return;
+}
+
+void
+sfw_test_rpc_done (srpc_client_rpc_t *rpc)
+{
+        sfw_test_unit_t     *tsu = rpc->crpc_priv;
+        sfw_test_instance_t *tsi = tsu->tsu_instance;
+        int                  done = 0;
+
+        if (rpc->crpc_status != 0 && tsu->tsu_error == 0 &&
+            (rpc->crpc_status != -EINTR || !tsi->tsi_stopping))
+                tsu->tsu_error = rpc->crpc_status;
+
+        tsi->tsi_ops->tso_done_rpc(tsu, rpc);
+                      
+        spin_lock(&tsi->tsi_lock);
+
+        LASSERT (sfw_test_active(tsi));
+        LASSERT (!list_empty(&rpc->crpc_list));
+
+        list_del_init(&rpc->crpc_list);
+
+        /* batch is stopping or loop is done or get error */
+        if (tsi->tsi_stopping ||
+            tsu->tsu_loop == 0 ||
+            (tsu->tsu_error != 0 && tsi->tsi_stop_onerr))
+                done = 1;
+
+        /* dec ref for poster */
+        srpc_client_rpc_decref(rpc);
+
+        spin_unlock(&tsi->tsi_lock);
+
+        if (!done) {
+                swi_schedule_workitem(&tsu->tsu_worker);
+                return;
+        }
+
+        sfw_test_unit_done(tsu);
+        return;
+}
+
+int
+sfw_create_test_rpc (sfw_test_unit_t *tsu, lnet_process_id_t peer,
+                     int nblk, int blklen, srpc_client_rpc_t **rpcpp)
+{
+        srpc_client_rpc_t   *rpc = NULL;
+        sfw_test_instance_t *tsi = tsu->tsu_instance;
+        
+        spin_lock(&tsi->tsi_lock);
+
+        LASSERT (sfw_test_active(tsi));
+
+        if (!list_empty(&tsi->tsi_free_rpcs)) {
+                /* pick request from buffer */
+                rpc = list_entry(tsi->tsi_free_rpcs.next,
+                                 srpc_client_rpc_t, crpc_list);
+                LASSERT (nblk == rpc->crpc_bulk.bk_niov);
+                list_del_init(&rpc->crpc_list);
+
+                srpc_init_client_rpc(rpc, peer, tsi->tsi_service, nblk,
+                                     blklen, sfw_test_rpc_done,
+                                     sfw_test_rpc_fini, tsu);
+        }
+
+        spin_unlock(&tsi->tsi_lock);
+        
+        if (rpc == NULL)
+                rpc = srpc_create_client_rpc(peer, tsi->tsi_service, nblk,
+                                             blklen, sfw_test_rpc_done, 
+                                             sfw_test_rpc_fini, tsu);
+        if (rpc == NULL) {
+                CERROR ("Can't create rpc for test %d\n", tsi->tsi_service);
+                return -ENOMEM;
+        }
+
+        *rpcpp = rpc;
+        return 0;
+}
+
+int
+sfw_run_test (swi_workitem_t *wi)
+{
+        sfw_test_unit_t     *tsu = wi->wi_data;
+        sfw_test_instance_t *tsi = tsu->tsu_instance;
+        srpc_client_rpc_t   *rpc = NULL;
+
+        LASSERT (wi == &tsu->tsu_worker);
+
+        tsu->tsu_error = tsi->tsi_ops->tso_prep_rpc(tsu, tsu->tsu_dest, &rpc);
+        if (tsu->tsu_error != 0) {
+                LASSERT (rpc == NULL);
+                goto test_done;
+        }
+
+        LASSERT (rpc != NULL);
+
+        spin_lock(&tsi->tsi_lock);
+
+        if (tsi->tsi_stopping) {
+                list_add(&rpc->crpc_list, &tsi->tsi_free_rpcs);
+                spin_unlock(&tsi->tsi_lock);
+                goto test_done;
+        }
+
+        if (tsu->tsu_loop > 0)
+                tsu->tsu_loop--;
+
+        list_add_tail(&rpc->crpc_list, &tsi->tsi_active_rpcs);
+        spin_unlock(&tsi->tsi_lock);
+
+        rpc->crpc_timeout = SFW_TEST_RPC_TIMEOUT;
+
+        spin_lock(&rpc->crpc_lock);
+        srpc_post_rpc(rpc);
+        spin_unlock(&rpc->crpc_lock);
+        return 0;
+
+test_done:
+        /*
+         * No one can schedule me now since:
+         * - previous RPC, if any, has done and
+         * - no new RPC is initiated.
+         * - my batch is still active; no one can run it again now.
+         * Cancel pending schedules and prevent future schedule attempts:
+         */
+        swi_kill_workitem(wi);
+        sfw_test_unit_done(tsu);
+        return 1;
+}
+
+int
+sfw_run_batch (sfw_batch_t *tsb)
+{
+        swi_workitem_t      *wi;
+        sfw_test_unit_t     *tsu;
+        sfw_test_instance_t *tsi;
+
+        if (sfw_batch_active(tsb)) {
+                CDEBUG (D_NET, "Can't start active batch: "LPU64" (%d)\n",
+                        tsb->bat_id.bat_id, atomic_read(&tsb->bat_nactive));
+                return -EPERM;
+        }
+
+        list_for_each_entry (tsi, &tsb->bat_tests, tsi_list) {
+                if (!tsi->tsi_is_client) /* skip server instances */
+                        continue;
+
+                LASSERT (!tsi->tsi_stopping);
+                LASSERT (!sfw_test_active(tsi));
+
+                atomic_inc(&tsb->bat_nactive);
+
+                list_for_each_entry (tsu, &tsi->tsi_units, tsu_list) {
+                        atomic_inc(&tsi->tsi_nactive);
+
+                        tsu->tsu_error = 0;
+                        tsu->tsu_loop  = tsi->tsi_loop;
+
+                        wi = &tsu->tsu_worker;
+                        swi_init_workitem(wi, tsu, sfw_run_test);
+                        swi_schedule_workitem(wi);
+                }
+        }
+
+        return 0;
+}
+
+int
+sfw_stop_batch (sfw_batch_t *tsb, int force)
+{
+        sfw_test_instance_t *tsi;
+        srpc_client_rpc_t   *rpc;
+
+        if (!sfw_batch_active(tsb))
+                return -EPERM;
+
+        list_for_each_entry (tsi, &tsb->bat_tests, tsi_list) {
+                spin_lock(&tsi->tsi_lock);
+
+                if (!tsi->tsi_is_client ||
+                    !sfw_test_active(tsi) || tsi->tsi_stopping) {
+                        spin_unlock(&tsi->tsi_lock);
+                        continue;
+                }
+
+                tsi->tsi_stopping = 1;
+
+                if (!force) {
+                        spin_unlock(&tsi->tsi_lock);
+                        continue;
+                }
+
+                /* abort launched rpcs in the test */
+                list_for_each_entry (rpc, &tsi->tsi_active_rpcs, crpc_list) {
+                        spin_lock(&rpc->crpc_lock);
+
+                        srpc_abort_rpc(rpc, -EINTR);
+
+                        spin_unlock(&rpc->crpc_lock);
+                }
+
+                spin_unlock(&tsi->tsi_lock);
+        }
+
+        return 0;
+}
+
+int
+sfw_query_batch (sfw_batch_t *tsb, int testidx, srpc_batch_reply_t *reply)
+{
+        sfw_test_instance_t *tsi;
+
+        if (testidx < 0)
+                return -EINVAL;
+
+        if (testidx == 0) {
+                reply->bar_active = atomic_read(&tsb->bat_nactive);
+                return 0;
+        }
+
+        list_for_each_entry (tsi, &tsb->bat_tests, tsi_list) {
+                if (testidx-- > 1)
+                        continue;
+
+                reply->bar_active = atomic_read(&tsi->tsi_nactive);
+                return 0;
+        }
+
+        return -ENOENT;
+}
+
+void
+sfw_free_pages (srpc_server_rpc_t *rpc)
+{
+        srpc_free_bulk(rpc->srpc_bulk);
+        rpc->srpc_bulk = NULL;
+}
+
+int
+sfw_alloc_pages (srpc_server_rpc_t *rpc, int npages, int sink)
+{
+        LASSERT (rpc->srpc_bulk == NULL);
+        LASSERT (npages > 0 && npages <= LNET_MAX_IOV);
+
+        rpc->srpc_bulk = srpc_alloc_bulk(npages, sink);
+        if (rpc->srpc_bulk == NULL) return -ENOMEM;
+
+        return 0;
+}
+
+int
+sfw_add_test (srpc_server_rpc_t *rpc)
+{
+        sfw_session_t     *sn = sfw_data.fw_session;
+        srpc_test_reply_t *reply = &rpc->srpc_replymsg.msg_body.tes_reply;
+        srpc_test_reqst_t *request;
+        int                rc;
+        sfw_batch_t       *bat;
+
+        request = &rpc->srpc_reqstbuf->buf_msg.msg_body.tes_reqst;
+        reply->tsr_sid = (sn == NULL) ? LST_INVALID_SID : sn->sn_id;
+
+        if (request->tsr_loop == 0 ||
+            request->tsr_concur == 0 ||
+            request->tsr_sid.ses_nid == LNET_NID_ANY ||
+            request->tsr_ndest > SFW_MAX_NDESTS ||
+            (request->tsr_is_client && request->tsr_ndest == 0) ||
+            request->tsr_concur > SFW_MAX_CONCUR ||
+            request->tsr_service > SRPC_SERVICE_MAX_ID ||
+            request->tsr_service <= SRPC_FRAMEWORK_SERVICE_MAX_ID) {
+                reply->tsr_status = EINVAL;
+                return 0;
+        }
+
+        if (sn == NULL || !sfw_sid_equal(request->tsr_sid, sn->sn_id) ||
+            sfw_find_test_case(request->tsr_service) == NULL) {
+                reply->tsr_status = ENOENT;
+                return 0;
+        }
+
+        bat = sfw_bid2batch(request->tsr_bid);
+        if (bat == NULL) {
+                CERROR ("Dropping RPC (%s) from %s under memory pressure.\n",
+                        rpc->srpc_service->sv_name,
+                        libcfs_id2str(rpc->srpc_peer));
+                return -ENOMEM;
+        }
+
+        if (sfw_batch_active(bat)) {
+                reply->tsr_status = EBUSY;
+                return 0;
+        }
+
+        if (request->tsr_is_client && rpc->srpc_bulk == NULL) {
+                /* rpc will be resumed later in sfw_bulk_ready */
+                return sfw_alloc_pages(rpc,
+                                       sfw_id_pages(request->tsr_ndest), 1);
+        }
+
+        rc = sfw_add_test_instance(bat, rpc);
+        CDEBUG (rc == 0 ? D_NET : D_WARNING,
+                "%s test: sv %d %s, loop %d, concur %d, ndest %d\n",
+                rc == 0 ? "Added" : "Failed to add", request->tsr_service,
+                request->tsr_is_client ? "client" : "server",
+                request->tsr_loop, request->tsr_concur, request->tsr_ndest);
+
+        reply->tsr_status = (rc < 0) ? -rc : rc;
+        return 0;
+}
+
+int
+sfw_control_batch (srpc_batch_reqst_t *request, srpc_batch_reply_t *reply)
+{
+        sfw_session_t *sn = sfw_data.fw_session;
+        int            rc = 0;
+        sfw_batch_t   *bat;
+
+        reply->bar_sid = (sn == NULL) ? LST_INVALID_SID : sn->sn_id;
+
+        if (sn == NULL || !sfw_sid_equal(request->bar_sid, sn->sn_id)) {
+                reply->bar_status = ESRCH;
+                return 0;
+        }
+
+        bat = sfw_find_batch(request->bar_bid);
+        if (bat == NULL) {
+                reply->bar_status = ENOENT;
+                return 0;
+        }
+
+        switch (request->bar_opc) {
+        case SRPC_BATCH_OPC_RUN:
+                rc = sfw_run_batch(bat);
+                break;
+
+        case SRPC_BATCH_OPC_STOP:
+                rc = sfw_stop_batch(bat, request->bar_arg);
+                break;
+
+        case SRPC_BATCH_OPC_QUERY:
+                rc = sfw_query_batch(bat, request->bar_testidx, reply);
+                break;
+
+        default:
+                return -EINVAL; /* drop it */
+        }
+
+        reply->bar_status = (rc < 0) ? -rc : rc;
+        return 0;
+}
+
+int
+sfw_handle_server_rpc (srpc_server_rpc_t *rpc)
+{
+        srpc_service_t *sv = rpc->srpc_service;
+        srpc_msg_t     *reply = &rpc->srpc_replymsg;
+        srpc_msg_t     *request = &rpc->srpc_reqstbuf->buf_msg;
+        int             rc = 0;
+
+        LASSERT (sfw_data.fw_active_srpc == NULL);
+        LASSERT (sv->sv_id <= SRPC_FRAMEWORK_SERVICE_MAX_ID);
+
+        spin_lock(&sfw_data.fw_lock);
+
+        if (sfw_data.fw_shuttingdown) {
+                spin_unlock(&sfw_data.fw_lock);
+                return -ESHUTDOWN;
+        }
+
+        /* Remove timer to avoid racing with it or expiring active session */
+        if (sfw_del_session_timer() != 0) {
+                CERROR ("Dropping RPC (%s) from %s: racing with expiry timer.",
+                        sv->sv_name, libcfs_id2str(rpc->srpc_peer));
+                spin_unlock(&sfw_data.fw_lock);
+                return -EAGAIN;
+        }
+
+        sfw_data.fw_active_srpc = rpc;
+        spin_unlock(&sfw_data.fw_lock);
+
+        sfw_unpack_message(request);
+        LASSERT (request->msg_type == srpc_service2request(sv->sv_id));
+
+        switch(sv->sv_id) {
+        default:
+                LBUG ();
+        case SRPC_SERVICE_TEST:
+                rc = sfw_add_test(rpc);
+                break;
+
+        case SRPC_SERVICE_BATCH:
+                rc = sfw_control_batch(&request->msg_body.bat_reqst,
+                                       &reply->msg_body.bat_reply);
+                break;
+
+        case SRPC_SERVICE_QUERY_STAT:
+                rc = sfw_get_stats(&request->msg_body.stat_reqst,
+                                   &reply->msg_body.stat_reply);
+                break;
+
+        case SRPC_SERVICE_DEBUG:
+                rc = sfw_debug_session(&request->msg_body.dbg_reqst,
+                                       &reply->msg_body.dbg_reply);
+                break;
+
+        case SRPC_SERVICE_MAKE_SESSION:
+                rc = sfw_make_session(&request->msg_body.mksn_reqst,
+                                      &reply->msg_body.mksn_reply);
+                break;
+
+        case SRPC_SERVICE_REMOVE_SESSION:
+                rc = sfw_remove_session(&request->msg_body.rmsn_reqst,
+                                        &reply->msg_body.rmsn_reply);
+                break;
+        }
+
+        rpc->srpc_done = sfw_server_rpc_done;
+        spin_lock(&sfw_data.fw_lock);
+
+#ifdef __KERNEL__
+        if (!sfw_data.fw_shuttingdown)
+                sfw_add_session_timer();
+#else
+        LASSERT (!sfw_data.fw_shuttingdown);
+        sfw_add_session_timer();
+#endif
+
+        sfw_data.fw_active_srpc = NULL;
+        spin_unlock(&sfw_data.fw_lock);
+        return rc;
+}
+
+int
+sfw_bulk_ready (srpc_server_rpc_t *rpc, int status)
+{
+        srpc_service_t *sv = rpc->srpc_service;
+        int             rc;
+
+        LASSERT (rpc->srpc_bulk != NULL);
+        LASSERT (sv->sv_id == SRPC_SERVICE_TEST);
+        LASSERT (sfw_data.fw_active_srpc == NULL);
+        LASSERT (rpc->srpc_reqstbuf->buf_msg.msg_body.tes_reqst.tsr_is_client);
+
+        spin_lock(&sfw_data.fw_lock);
+
+        if (status != 0) {
+                CERROR ("Bulk transfer failed for RPC: "
+                        "service %s, peer %s, status %d\n",
+                        sv->sv_name, libcfs_id2str(rpc->srpc_peer), status);
+                spin_unlock(&sfw_data.fw_lock);
+                return -EIO;
+        }
+
+        if (sfw_data.fw_shuttingdown) {
+                spin_unlock(&sfw_data.fw_lock);
+                return -ESHUTDOWN;
+        }
+
+        if (sfw_del_session_timer() != 0) {
+                CERROR ("Dropping RPC (%s) from %s: racing with expiry timer",
+                        sv->sv_name, libcfs_id2str(rpc->srpc_peer));
+                spin_unlock(&sfw_data.fw_lock);
+                return -EAGAIN;
+        }
+
+        sfw_data.fw_active_srpc = rpc;
+        spin_unlock(&sfw_data.fw_lock);
+
+        rc = sfw_add_test(rpc);
+
+        spin_lock(&sfw_data.fw_lock);
+
+#ifdef __KERNEL__
+        if (!sfw_data.fw_shuttingdown)
+                sfw_add_session_timer();
+#else
+        LASSERT (!sfw_data.fw_shuttingdown);
+        sfw_add_session_timer();
+#endif
+
+        sfw_data.fw_active_srpc = NULL;
+        spin_unlock(&sfw_data.fw_lock);
+        return rc;
+}
+
+srpc_client_rpc_t *
+sfw_create_rpc (lnet_process_id_t peer, int service,
+                int nbulkiov, int bulklen,
+                void (*done) (srpc_client_rpc_t *), void *priv)
+{
+        srpc_client_rpc_t *rpc;
+
+        spin_lock(&sfw_data.fw_lock);
+
+        LASSERT (!sfw_data.fw_shuttingdown);
+        LASSERT (service <= SRPC_FRAMEWORK_SERVICE_MAX_ID);
+
+        if (nbulkiov == 0 && !list_empty(&sfw_data.fw_zombie_rpcs)) {
+                rpc = list_entry(sfw_data.fw_zombie_rpcs.next,
+                                 srpc_client_rpc_t, crpc_list);
+                list_del(&rpc->crpc_list);
+                spin_unlock(&sfw_data.fw_lock);
+
+                srpc_init_client_rpc(rpc, peer, service, 0, 0,
+                                     done, sfw_client_rpc_fini, priv);
+                return rpc;
+        }
+
+        spin_unlock(&sfw_data.fw_lock);
+
+        rpc = srpc_create_client_rpc(peer, service, nbulkiov, bulklen, done,
+                                     nbulkiov != 0 ? NULL : sfw_client_rpc_fini,
+                                     priv);
+        return rpc;
+}
+
+void
+sfw_unpack_message (srpc_msg_t *msg)
+{
+        if (msg->msg_magic == SRPC_MSG_MAGIC)
+                return; /* no flipping needed */
+
+        LASSERT (msg->msg_magic == __swab32(SRPC_MSG_MAGIC));
+
+        __swab32s(&msg->msg_type);
+
+        if (msg->msg_type == SRPC_MSG_STAT_REQST) {
+                srpc_stat_reqst_t *req = &msg->msg_body.stat_reqst;
+
+                __swab32s(&req->str_type);
+                __swab64s(&req->str_rpyid);
+                sfw_unpack_sid(req->str_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_STAT_REPLY) {
+                srpc_stat_reply_t *rep = &msg->msg_body.stat_reply;
+
+                __swab32s(&rep->str_status);
+                sfw_unpack_sid(rep->str_sid);
+                sfw_unpack_fw_counters(rep->str_fw);
+                sfw_unpack_rpc_counters(rep->str_rpc);
+                sfw_unpack_lnet_counters(rep->str_lnet);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_MKSN_REQST) {
+                srpc_mksn_reqst_t *req = &msg->msg_body.mksn_reqst;
+
+                __swab64s(&req->mksn_rpyid);
+                __swab32s(&req->mksn_force);
+                sfw_unpack_sid(req->mksn_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_MKSN_REPLY) {
+                srpc_mksn_reply_t *rep = &msg->msg_body.mksn_reply;
+
+                __swab32s(&rep->mksn_status);
+                __swab32s(&rep->mksn_timeout);
+                sfw_unpack_sid(rep->mksn_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_RMSN_REQST) {
+                srpc_rmsn_reqst_t *req = &msg->msg_body.rmsn_reqst;
+
+                __swab64s(&req->rmsn_rpyid);
+                sfw_unpack_sid(req->rmsn_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_RMSN_REPLY) {
+                srpc_rmsn_reply_t *rep = &msg->msg_body.rmsn_reply;
+
+                __swab32s(&rep->rmsn_status);
+                sfw_unpack_sid(rep->rmsn_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_DEBUG_REQST) {
+                srpc_debug_reqst_t *req = &msg->msg_body.dbg_reqst;
+
+                __swab64s(&req->dbg_rpyid);
+                __swab32s(&req->dbg_flags);
+                sfw_unpack_sid(req->dbg_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_DEBUG_REPLY) {
+                srpc_debug_reply_t *rep = &msg->msg_body.dbg_reply;
+
+                __swab32s(&rep->dbg_nbatch);
+                __swab32s(&rep->dbg_timeout);
+                sfw_unpack_sid(rep->dbg_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_BATCH_REQST) {
+                srpc_batch_reqst_t *req = &msg->msg_body.bat_reqst;
+
+                __swab32s(&req->bar_opc);
+                __swab64s(&req->bar_rpyid);
+                __swab32s(&req->bar_testidx);
+                __swab32s(&req->bar_arg);
+                sfw_unpack_sid(req->bar_sid);
+                __swab64s(&req->bar_bid.bat_id);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_BATCH_REPLY) {
+                srpc_batch_reply_t *rep = &msg->msg_body.bat_reply;
+
+                __swab32s(&rep->bar_status);
+                sfw_unpack_sid(rep->bar_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_TEST_REQST) {
+                srpc_test_reqst_t *req = &msg->msg_body.tes_reqst;
+
+                __swab64s(&req->tsr_rpyid);
+                __swab64s(&req->tsr_bulkid);
+                __swab32s(&req->tsr_loop);
+                __swab32s(&req->tsr_ndest);
+                __swab32s(&req->tsr_concur);
+                __swab32s(&req->tsr_service);
+                sfw_unpack_sid(req->tsr_sid);
+                __swab64s(&req->tsr_bid.bat_id);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_TEST_REPLY) {
+                srpc_test_reply_t *rep = &msg->msg_body.tes_reply;
+
+                __swab32s(&rep->tsr_status);
+                sfw_unpack_sid(rep->tsr_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_JOIN_REQST) {
+                srpc_join_reqst_t *req = &msg->msg_body.join_reqst;
+
+                __swab64s(&req->join_rpyid);
+                sfw_unpack_sid(req->join_sid);
+                return;
+        }
+
+        if (msg->msg_type == SRPC_MSG_JOIN_REPLY) {
+                srpc_join_reply_t *rep = &msg->msg_body.join_reply;
+
+                __swab32s(&rep->join_status);
+                __swab32s(&rep->join_timeout);
+                sfw_unpack_sid(rep->join_sid);
+                return;
+        }
+
+        LBUG ();
+        return;
+}
+
+void
+sfw_abort_rpc (srpc_client_rpc_t *rpc)
+{
+        LASSERT (atomic_read(&rpc->crpc_refcount) > 0);
+        LASSERT (rpc->crpc_service <= SRPC_FRAMEWORK_SERVICE_MAX_ID);
+
+        spin_lock(&rpc->crpc_lock);
+        srpc_abort_rpc(rpc, -EINTR);
+        spin_unlock(&rpc->crpc_lock);
+        return;
+}
+
+void
+sfw_post_rpc (srpc_client_rpc_t *rpc)
+{
+        spin_lock(&rpc->crpc_lock);
+
+        LASSERT (!rpc->crpc_closed);
+        LASSERT (!rpc->crpc_aborted);
+        LASSERT (list_empty(&rpc->crpc_list));
+        LASSERT (!sfw_data.fw_shuttingdown);
+
+        rpc->crpc_timeout = SFW_CLIENT_RPC_TIMEOUT;
+        srpc_post_rpc(rpc);
+
+        spin_unlock(&rpc->crpc_lock);
+        return;
+}
+
+static srpc_service_t sfw_services[] = 
+{
+        {
+                .sv_name = "debug",
+                .sv_id   = SRPC_SERVICE_DEBUG,
+        },
+        {
+                .sv_name = "query stats",
+                .sv_id   = SRPC_SERVICE_QUERY_STAT,
+        },
+        {
+                .sv_name = "make sessin",
+                .sv_id   = SRPC_SERVICE_MAKE_SESSION,
+        },
+        {
+                .sv_name = "remove session",
+                .sv_id   = SRPC_SERVICE_REMOVE_SESSION,
+        },
+        {
+                .sv_name = "batch service",
+                .sv_id   = SRPC_SERVICE_BATCH,
+        },
+        {
+                .sv_name = "test service",
+                .sv_id   = SRPC_SERVICE_TEST,
+        },
+        {       .sv_name = NULL, }
+};
+
+extern sfw_test_client_ops_t ping_test_client;
+extern srpc_service_t        ping_test_service;
+
+extern sfw_test_client_ops_t brw_test_client;
+extern srpc_service_t        brw_test_service;
+
+int
+sfw_startup (void)
+{
+        int              i;
+        int              rc;
+        int              error;
+        srpc_service_t  *sv;
+        sfw_test_case_t *tsc;
+
+        if (session_timeout < 0) {
+                CERROR ("Session timeout must be non-negative: %d\n",
+                        session_timeout);
+                return -EINVAL;
+        }
+
+        if (session_timeout == 0)
+                CWARN ("Zero session_timeout specified "
+                       "- test sessions never timeout.\n");
+
+        memset(&sfw_data, 0, sizeof(struct smoketest_framework));
+
+        sfw_data.fw_session     = NULL;
+        sfw_data.fw_active_srpc = NULL;
+        spin_lock_init(&sfw_data.fw_lock);
+        atomic_set(&sfw_data.fw_nzombies, 0);
+        CFS_INIT_LIST_HEAD(&sfw_data.fw_tests);
+        CFS_INIT_LIST_HEAD(&sfw_data.fw_zombie_rpcs);
+        CFS_INIT_LIST_HEAD(&sfw_data.fw_zombie_sessions);
+
+        rc = sfw_register_test(&brw_test_service, &brw_test_client);
+        LASSERT (rc == 0);
+        rc = sfw_register_test(&ping_test_service, &ping_test_client);
+        LASSERT (rc == 0);
+
+        error = 0;
+        list_for_each_entry (tsc, &sfw_data.fw_tests, tsc_list) {
+                sv = tsc->tsc_srv_service;
+                sv->sv_concur = SFW_TEST_CONCURRENCY;
+
+                rc = srpc_add_service(sv);
+                LASSERT (rc != -EBUSY);
+                if (rc != 0) {
+                        CWARN ("Failed to add %s service: %d\n",
+                               sv->sv_name, rc);
+                        error = rc;
+                }
+        }
+
+        for (i = 0; ; i++) {
+                sv = &sfw_services[i];
+                if (sv->sv_name == NULL) break;
+
+                sv->sv_bulk_ready = NULL;
+                sv->sv_handler    = sfw_handle_server_rpc;
+                sv->sv_concur     = SFW_SERVICE_CONCURRENCY;
+                if (sv->sv_id == SRPC_SERVICE_TEST)
+                        sv->sv_bulk_ready = sfw_bulk_ready;
+
+                rc = srpc_add_service(sv);
+                LASSERT (rc != -EBUSY);
+                if (rc != 0) {
+                        CWARN ("Failed to add %s service: %d\n",
+                               sv->sv_name, rc);
+                        error = rc;
+                }
+
+                /* about to sfw_shutdown, no need to add buffer */
+                if (error) continue; 
+
+                rc = srpc_service_add_buffers(sv, SFW_POST_BUFFERS);
+                if (rc != SFW_POST_BUFFERS) {
+                        CWARN ("Failed to reserve enough buffers: "
+                               "service %s, %d needed, %d reserved\n",
+                               sv->sv_name, SFW_POST_BUFFERS, rc);
+                        error = -ENOMEM;
+                }
+        }
+
+        if (error != 0)
+                sfw_shutdown();
+        return error;
+}
+
+void
+sfw_shutdown (void)
+{
+        srpc_service_t  *sv;
+        sfw_test_case_t *tsc;
+        int              i;
+
+        spin_lock(&sfw_data.fw_lock);
+
+        sfw_data.fw_shuttingdown = 1;
+#ifdef __KERNEL__
+        lst_wait_until(sfw_data.fw_active_srpc == NULL, sfw_data.fw_lock,
+                       "waiting for active RPC to finish.\n");
+#else
+        LASSERT (sfw_data.fw_active_srpc == NULL);
+#endif
+
+        if (sfw_del_session_timer() != 0)
+                lst_wait_until(sfw_data.fw_session == NULL, sfw_data.fw_lock,
+                               "waiting for session timer to explode.\n");
+
+        sfw_deactivate_session();
+        lst_wait_until(atomic_read(&sfw_data.fw_nzombies) == 0,
+                       sfw_data.fw_lock,
+                       "waiting for %d zombie sessions to die.\n",
+                       atomic_read(&sfw_data.fw_nzombies));
+
+        spin_unlock(&sfw_data.fw_lock);
+
+        for (i = 0; ; i++) {
+                sv = &sfw_services[i];
+                if (sv->sv_name == NULL)
+                        break;
+
+                srpc_shutdown_service(sv);
+                srpc_remove_service(sv);
+        }
+
+        list_for_each_entry (tsc, &sfw_data.fw_tests, tsc_list) {
+                sv = tsc->tsc_srv_service;
+                srpc_shutdown_service(sv);
+                srpc_remove_service(sv);
+        }
+
+        while (!list_empty(&sfw_data.fw_zombie_rpcs)) {
+                srpc_client_rpc_t *rpc;
+
+                rpc = list_entry(sfw_data.fw_zombie_rpcs.next, 
+                                 srpc_client_rpc_t, crpc_list);
+                list_del(&rpc->crpc_list);
+
+                LIBCFS_FREE(rpc, srpc_client_rpc_size(rpc));
+        }
+
+        for (i = 0; ; i++) {
+                sv = &sfw_services[i];
+                if (sv->sv_name == NULL)
+                        break;
+
+                srpc_wait_service_shutdown(sv);
+        }
+
+        while (!list_empty(&sfw_data.fw_tests)) {
+                tsc = list_entry(sfw_data.fw_tests.next,
+                                 sfw_test_case_t, tsc_list);
+                
+                srpc_wait_service_shutdown(tsc->tsc_srv_service);
+
+                list_del(&tsc->tsc_list);
+                LIBCFS_FREE(tsc, sizeof(*tsc));
+        }
+
+        return;
+}
diff --git a/lnet/selftest/module.c b/lnet/selftest/module.c
new file mode 100644 (file)
index 0000000..5986acd
--- /dev/null
@@ -0,0 +1,106 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ */
+#define DEBUG_SUBSYSTEM S_LNET
+
+#include "selftest.h"
+
+
+#define LST_INIT_NONE           0
+#define LST_INIT_RPC            1
+#define LST_INIT_FW             2
+#define LST_INIT_CONSOLE        3
+
+extern int lstcon_console_init(void);
+extern int lstcon_console_fini(void);
+
+static int lst_init_step = LST_INIT_NONE;
+
+void
+lnet_selftest_fini (void)
+{
+        switch (lst_init_step) {
+#ifdef __KERNEL__
+                case LST_INIT_CONSOLE:
+                        lstcon_console_fini();
+#endif
+                case LST_INIT_FW:
+                        sfw_shutdown();
+                case LST_INIT_RPC:
+                        srpc_shutdown();
+                case LST_INIT_NONE:
+                        break;
+                default:
+                        LBUG();
+        }
+        return;
+}
+
+int
+lnet_selftest_init (void)
+{
+        int    rc;
+
+        rc = srpc_startup();
+        if (rc != 0) {
+                CERROR("LST can't startup rpc\n");
+                goto error;
+        }
+        lst_init_step = LST_INIT_RPC;
+
+        rc = sfw_startup();
+        if (rc != 0) {
+                CERROR("LST can't startup framework\n");
+                goto error;
+        }
+        lst_init_step = LST_INIT_FW;
+
+#ifdef __KERNEL__
+        rc = lstcon_console_init();
+        if (rc != 0) {
+                CERROR("LST can't startup console\n");
+                goto error;
+        }
+        lst_init_step = LST_INIT_CONSOLE;  
+#endif
+
+        return 0;
+error:
+        lnet_selftest_fini();
+        return rc;
+}
+
+#ifdef __KERNEL__
+
+MODULE_DESCRIPTION("LNet Selftest");
+MODULE_LICENSE("GPL");
+
+cfs_module(lnet, "0.9.0", lnet_selftest_init, lnet_selftest_fini);
+
+#else
+
+int
+selftest_wait_events (void)
+{
+        int evts = 0;
+
+        for (;;) {
+                /* Consume all pending events */
+                while (srpc_check_event(0))
+                        evts++;
+                evts += stt_check_events();
+                evts += swi_check_events();
+                if (evts != 0) break;
+
+                /* Nothing happened, block for events */
+                evts += srpc_check_event(stt_poll_interval());
+                /* We may have blocked, check for expired timers */
+                evts += stt_check_events();
+                if (evts == 0) /* timed out and still no event */
+                        break;
+        }
+
+        return evts;
+}
+
+#endif
diff --git a/lnet/selftest/ping_test.c b/lnet/selftest/ping_test.c
new file mode 100644 (file)
index 0000000..93fc9cf
--- /dev/null
@@ -0,0 +1,162 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Author: Liang Zhen <liangzhen@clusterfs.com>
+ *
+ * This file is part of Lustre, http://www.lustre.org
+ *
+ * Test client & Server
+ */
+#include <libcfs/kp30.h>
+#include "selftest.h"
+
+#define LST_PING_TEST_MAGIC     0xbabeface
+
+typedef struct {
+        spinlock_t      pnd_lock;       /* serialize */
+        int             pnd_counter;    /* sequence counter */
+        int             pnd_err_count;  /* error count */
+} lst_ping_data_t;
+
+static lst_ping_data_t  lst_ping_data;
+
+static int
+ping_client_init(sfw_test_instance_t *tsi)
+{
+        spin_lock_init(&lst_ping_data.pnd_lock);
+        lst_ping_data.pnd_counter   = 0;
+        lst_ping_data.pnd_err_count = 0;
+
+        return 0;
+}
+
+static void
+ping_client_fini(sfw_test_instance_t *tsi)
+{
+        CWARN("Total ping %d, failed ping: %d\n",
+              lst_ping_data.pnd_counter, lst_ping_data.pnd_err_count);
+}
+
+static int
+ping_client_prep_rpc(sfw_test_unit_t *tsu,
+                     lnet_process_id_t dest, srpc_client_rpc_t **rpc)
+{
+        srpc_ping_reqst_t *req;
+        struct timeval     tv;
+        int                rc;
+
+        rc = sfw_create_test_rpc(tsu, dest, 0, 0, rpc);
+        if (rc != 0)
+                return rc;
+
+        req = &(*rpc)->crpc_reqstmsg.msg_body.ping_reqst;
+
+        req->pnr_magic = LST_PING_TEST_MAGIC;
+
+        spin_lock(&lst_ping_data.pnd_lock);
+        req->pnr_seq = lst_ping_data.pnd_counter ++;
+        spin_unlock(&lst_ping_data.pnd_lock);
+
+        cfs_fs_timeval(&tv);
+        req->pnr_time_sec  = tv.tv_sec;
+        req->pnr_time_usec = tv.tv_usec;
+
+        return rc;
+}
+
+static void
+ping_client_done_rpc(sfw_test_unit_t *tsu, srpc_client_rpc_t *rpc)
+{
+        srpc_ping_reqst_t *req;
+        srpc_ping_reply_t *rep;
+        struct timeval     tv;
+
+        req = &rpc->crpc_reqstmsg.msg_body.ping_reqst;
+        rep = &rpc->crpc_replymsg.msg_body.ping_reply;
+
+        if (rpc->crpc_status == 0 &&
+            rpc->crpc_replymsg.msg_magic != SRPC_MSG_MAGIC) {
+                __swab32s(&rep->pnr_seq);
+                __swab32s(&rep->pnr_magic);
+                __swab32s(&rep->pnr_status);
+        }
+
+        if (rpc->crpc_status != 0) {
+                CERROR ("Unable to ping %s (%d): %d\n",
+                        libcfs_id2str(rpc->crpc_dest),
+                        req->pnr_seq, rpc->crpc_status);
+        } else if (rep->pnr_magic != LST_PING_TEST_MAGIC) {
+                tsu->tsu_error = -EBADMSG;
+                CERROR ("Bad magic %u from %s, %u expected.\n",
+                        rep->pnr_magic, libcfs_id2str(rpc->crpc_dest),
+                        LST_PING_TEST_MAGIC);
+        } else if (rep->pnr_seq != req->pnr_seq) {
+                tsu->tsu_error = -EBADMSG;
+                CERROR ("Bad seq %u from %s, %u expected.\n",
+                        rep->pnr_seq, libcfs_id2str(rpc->crpc_dest),
+                        req->pnr_seq);
+        }
+
+        if (tsu->tsu_error != 0) {
+                spin_lock(&lst_ping_data.pnd_lock);
+                lst_ping_data.pnd_err_count++;
+                spin_unlock(&lst_ping_data.pnd_lock);
+                return;
+        }
+
+        cfs_fs_timeval(&tv);
+        CDEBUG (D_NET, "%d reply in %u usec\n", rep->pnr_seq,
+                (unsigned)((tv.tv_sec - (unsigned)req->pnr_time_sec) * 1000000 +
+                           (tv.tv_usec - req->pnr_time_usec)));
+        return;
+}
+
+static int
+ping_server_handle (srpc_server_rpc_t *rpc)
+{
+        srpc_service_t    *sv  = rpc->srpc_service;
+        srpc_msg_t        *reqstmsg = &rpc->srpc_reqstbuf->buf_msg;
+        srpc_ping_reqst_t *req = &reqstmsg->msg_body.ping_reqst;
+        srpc_ping_reply_t *rep = &rpc->srpc_replymsg.msg_body.ping_reply;
+
+        LASSERT (sv->sv_id == SRPC_SERVICE_PING);
+
+        if (reqstmsg->msg_magic != SRPC_MSG_MAGIC) {
+                LASSERT (reqstmsg->msg_magic == __swab32(SRPC_MSG_MAGIC));
+
+                __swab32s(&reqstmsg->msg_type);
+                __swab32s(&req->pnr_seq);
+                __swab32s(&req->pnr_magic);
+                __swab64s(&req->pnr_time_sec);
+                __swab64s(&req->pnr_time_usec);
+        }
+        LASSERT (reqstmsg->msg_type == srpc_service2request(sv->sv_id));
+
+        if (req->pnr_magic != LST_PING_TEST_MAGIC) {
+                CERROR ("Unexpect magic %08x from %s\n",
+                        req->pnr_magic, libcfs_id2str(rpc->srpc_peer));
+                return -EINVAL;
+        }
+
+        rep->pnr_seq   = req->pnr_seq;
+        rep->pnr_magic = LST_PING_TEST_MAGIC;
+
+        CDEBUG (D_NET, "Get ping %d from %s\n",
+                req->pnr_seq, libcfs_id2str(rpc->srpc_peer));
+        return 0;
+}
+
+sfw_test_client_ops_t ping_test_client = 
+{
+        .tso_init       = ping_client_init,
+        .tso_fini       = ping_client_fini,
+        .tso_prep_rpc   = ping_client_prep_rpc,
+        .tso_done_rpc   = ping_client_done_rpc,
+};
+
+srpc_service_t ping_test_service = 
+{
+        .sv_name        = "ping test",
+        .sv_handler     = ping_server_handle,
+        .sv_id          = SRPC_SERVICE_PING,
+};
diff --git a/lnet/selftest/rpc.c b/lnet/selftest/rpc.c
new file mode 100644 (file)
index 0000000..bb6f587
--- /dev/null
@@ -0,0 +1,1702 @@
+/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
+ * vim:expandtab:shiftwidth=8:tabstop=8:
+ *
+ * Copyright (C) 2001, 2002 Cluster File Systems, Inc.
+ *   Author: Isaac Huang <isaac@clusterfs.com>
+ *
+ */
+
+#define DEBUG_SUBSYSTEM S_LNET
+
+#include <libcfs/kp30.h>
+#include <libcfs/libcfs.h>
+#include <lnet/lib-lnet.h>
+
+#include "selftest.h"
+
+
+typedef enum {
+        SRPC_STATE_NONE,
+        SRPC_STATE_NI_INIT,
+        SRPC_STATE_EQ_INIT,
+        SRPC_STATE_WI_INIT,
+        SRPC_STATE_RUNNING,
+        SRPC_STATE_STOPPING,
+} srpc_state_t;
+
+#define SRPC_PEER_HASH_SIZE       101  /* # peer lists */
+#define SRPC_PEER_CREDITS         16   /* >= most LND's default peer credit */
+
+struct smoketest_rpc {
+        spinlock_t        rpc_glock;     /* global lock */
+        srpc_service_t   *rpc_services[SRPC_SERVICE_MAX_ID + 1];
+        struct list_head *rpc_peers;     /* hash table of known peers */
+        lnet_handle_eq_t  rpc_lnet_eq;   /* _the_ LNet event queue */
+        srpc_state_t      rpc_state;
+        srpc_counters_t   rpc_counters;
+        __u64             rpc_matchbits; /* matchbits counter */
+} srpc_data;
+
+/* forward ref's */
+int srpc_handle_rpc (swi_workitem_t *wi);
+
+void srpc_get_counters (srpc_counters_t *cnt)
+{
+        spin_lock(&srpc_data.rpc_glock);
+        *cnt = srpc_data.rpc_counters;
+        spin_unlock(&srpc_data.rpc_glock);
+}
+
+void srpc_set_counters (const srpc_counters_t *cnt)
+{
+        spin_lock(&srpc_data.rpc_glock);
+        srpc_data.rpc_counters = *cnt;
+        spin_unlock(&srpc_data.rpc_glock);
+}
+
+void
+srpc_add_bulk_page (srpc_bulk_t *bk, cfs_page_t *pg, int i)
+{
+        LASSERT (i >= 0 && i < bk->bk_niov);
+
+#ifdef __KERNEL__
+        bk->bk_iovs[i].kiov_offset = 0;
+        bk->bk_iovs[i].kiov_page   = pg;
+        bk->bk_iovs[i].kiov_len    = CFS_PAGE_SIZE;
+#else
+        LASSERT (bk->bk_pages != NULL);
+
+        bk->bk_pages[i] = pg;
+        bk->bk_iovs[i].iov_len  = CFS_PAGE_SIZE;
+        bk->bk_iovs[i].iov_base = cfs_page_address(pg);
+#endif
+        return;
+}
+
+void
+srpc_free_bulk (srpc_bulk_t *bk)
+{
+        int         i;
+        cfs_page_t *pg;
+
+        LASSERT (bk != NULL);
+#ifndef __KERNEL__
+        LASSERT (bk->bk_pages != NULL);
+#endif
+
+        for (i = 0; i < bk->bk_niov; i++) {
+#ifdef __KERNEL__
+                pg = bk->bk_iovs[i].kiov_page;
+#else
+                pg = bk->bk_pages[i];
+#endif
+                if (pg == NULL) break;
+
+                cfs_free_page(pg);
+        }
+
+#ifndef __KERNEL__
+        LIBCFS_FREE(bk->bk_pages, sizeof(cfs_page_t *) * bk->bk_niov);
+#endif
+        LIBCFS_FREE(bk, offsetof(srpc_bulk_t, bk_iovs[bk->bk_niov]));
+        return;
+}
+
+srpc_bulk_t *
+srpc_alloc_bulk (int npages, int sink)
+{
+        srpc_bulk_t  *bk;
+        cfs_page_t  **pages;
+        int           i;
+
+        LASSERT (npages > 0 && npages <= LNET_MAX_IOV);
+
+        LIBCFS_ALLOC(bk, offsetof(srpc_bulk_t, bk_iovs[npages]));
+        if (bk == NULL) {
+                CERROR ("Can't allocate descriptor for %d pages\n", npages);
+                return NULL;
+        }
+
+        memset(bk, 0, offsetof(srpc_bulk_t, bk_iovs[npages]));
+        bk->bk_sink = sink;
+        bk->bk_niov = npages;
+        bk->bk_len  = npages * CFS_PAGE_SIZE;
+#ifndef __KERNEL__
+        LIBCFS_ALLOC(pages, sizeof(cfs_page_t *) * npages);
+        if (pages == NULL) {
+                LIBCFS_FREE(bk, offsetof(srpc_bulk_t, bk_iovs[npages]));
+                CERROR ("Can't allocate page array for %d pages\n", npages);
+                return NULL;
+        }
+
+        memset(pages, 0, sizeof(cfs_page_t *) * npages);
+        bk->bk_pages = pages;
+#else
+        UNUSED (pages);
+#endif
+
+        for (i = 0; i < npages; i++) {
+                cfs_page_t *pg = cfs_alloc_page(CFS_ALLOC_STD);
+
+                if (pg == NULL) {
+                        CERROR ("Can't allocate page %d of %d\n", i, npages);
+                        srpc_free_bulk(bk);
+                        return NULL;
+                }
+
+                srpc_add_bulk_page(bk, pg, i);
+        }
+
+        return bk;
+}
+
+
+static inline struct list_head *
+srpc_nid2peerlist (lnet_nid_t nid)
+{
+        unsigned int hash = ((unsigned int)nid) % SRPC_PEER_HASH_SIZE;
+
+        return &srpc_data.rpc_peers[hash];
+}
+
+static inline srpc_peer_t *
+srpc_create_peer (lnet_nid_t nid)
+{
+        srpc_peer_t *peer;
+
+        LASSERT (nid != LNET_NID_ANY);
+
+        LIBCFS_ALLOC(peer, sizeof(srpc_peer_t));
+        if (peer == NULL) {
+                CERROR ("Failed to allocate peer structure for %s\n",
+                        libcfs_nid2str(nid));
+                return NULL;
+        }
+
+        memset(peer, 0, sizeof(srpc_peer_t));
+        peer->stp_nid     = nid;
+        peer->stp_credits = SRPC_PEER_CREDITS;
+
+        spin_lock_init(&peer->stp_lock);
+        CFS_INIT_LIST_HEAD(&peer->stp_rpcq);
+        CFS_INIT_LIST_HEAD(&peer->stp_ctl_rpcq);
+        return peer;
+}
+
+srpc_peer_t *
+srpc_find_peer_locked (lnet_nid_t nid)
+{
+        struct list_head *peer_list = srpc_nid2peerlist(nid);
+        srpc_peer_t      *peer;
+
+        LASSERT (nid != LNET_NID_ANY);
+
+        list_for_each_entry (peer, peer_list, stp_list) {
+                if (peer->stp_nid == nid)
+                        return peer;
+        }
+
+        return NULL;
+}
+
+static srpc_peer_t *
+srpc_nid2peer (lnet_nid_t nid)
+{
+       srpc_peer_t *peer;
+       srpc_peer_t *new_peer;
+
+        spin_lock(&srpc_data.rpc_glock);
+        peer = srpc_find_peer_locked(nid);
+        spin_unlock(&srpc_data.rpc_glock);
+
+        if (peer != NULL)
+                return peer;
+        
+        new_peer = srpc_create_peer(nid);
+
+        spin_lock(&srpc_data.rpc_glock);
+
+        peer = srpc_find_peer_locked(nid);
+        if (peer != NULL) {
+                spin_unlock(&srpc_data.rpc_glock);
+                if (new_peer != NULL)
+                        LIBCFS_FREE(new_peer, sizeof(srpc_peer_t));
+
+                return peer;
+        }
+
+        if (new_peer == NULL) {
+                spin_unlock(&srpc_data.rpc_glock);
+                return NULL;
+        }
+                
+        list_add_tail(&new_peer->stp_list, srpc_nid2peerlist(nid));
+        spin_unlock(&srpc_data.rpc_glock);
+        return new_peer;
+}
+
+static inline __u64
+srpc_next_id (void)
+{
+        __u64 id;
+
+        spin_lock(&srpc_data.rpc_glock);
+        id = srpc_data.rpc_matchbits++;
+        spin_unlock(&srpc_data.rpc_glock);
+        return id;
+}
+
+void
+srpc_init_server_rpc (srpc_server_rpc_t *rpc,
+                      srpc_service_t *sv, srpc_buffer_t *buffer)
+{
+        memset(rpc, 0, sizeof(*rpc));
+        swi_init_workitem(&rpc->srpc_wi, rpc, srpc_handle_rpc);
+
+        rpc->srpc_ev.ev_fired = 1; /* no event expected now */
+
+        rpc->srpc_service  = sv;
+        rpc->srpc_reqstbuf = buffer;
+        rpc->srpc_peer     = buffer->buf_peer;
+        rpc->srpc_self     = buffer->buf_self;
+        rpc->srpc_replymdh = LNET_INVALID_HANDLE;
+}
+
+int
+srpc_add_service (srpc_service_t *sv)
+{
+        int                id = sv->sv_id;
+        int                i;
+        srpc_server_rpc_t *rpc;
+
+        LASSERT (sv->sv_concur > 0);
+        LASSERT (0 <= id && id <= SRPC_SERVICE_MAX_ID);
+
+        spin_lock(&srpc_data.rpc_glock);
+
+        LASSERT (srpc_data.rpc_state == SRPC_STATE_RUNNING);
+
+        if (srpc_data.rpc_services[id] != NULL) {
+                spin_unlock(&srpc_data.rpc_glock);
+                return -EBUSY;
+        }
+
+        srpc_data.rpc_services[id] = sv;
+        spin_unlock(&srpc_data.rpc_glock);
+
+        sv->sv_nprune       = 0;
+        sv->sv_nposted_msg  = 0;
+        sv->sv_shuttingdown = 0;
+        spin_lock_init(&sv->sv_lock);
+        CFS_INIT_LIST_HEAD(&sv->sv_free_rpcq);
+        CFS_INIT_LIST_HEAD(&sv->sv_active_rpcq);
+        CFS_INIT_LIST_HEAD(&sv->sv_posted_msgq);
+        CFS_INIT_LIST_HEAD(&sv->sv_blocked_msgq);
+
+        sv->sv_ev.ev_data = sv;
+        sv->sv_ev.ev_type = SRPC_REQUEST_RCVD;
+
+        for (i = 0; i < sv->sv_concur; i++) {
+                LIBCFS_ALLOC(rpc, sizeof(*rpc));
+                if (rpc == NULL) goto enomem;
+
+                list_add(&rpc->srpc_list, &sv->sv_free_rpcq);
+        }
+
+        CDEBUG (D_NET, "Adding service: id %d, name %s, concurrency %d\n",
+                id, sv->sv_name, sv->sv_concur);
+        return 0;
+
+enomem:
+        while (!list_empty(&sv->sv_free_rpcq)) {
+                rpc = list_entry(sv->sv_free_rpcq.next,
+                                 srpc_server_rpc_t, srpc_list);
+                list_del(&rpc->srpc_list);
+                LIBCFS_FREE(rpc, sizeof(*rpc));
+        }
+
+        spin_lock(&srpc_data.rpc_glock);
+        srpc_data.rpc_services[id] = NULL;
+        spin_unlock(&srpc_data.rpc_glock);
+        return -ENOMEM;
+}
+
+int
+srpc_remove_service (srpc_service_t *sv)
+{
+        int id = sv->sv_id;
+
+        spin_lock(&srpc_data.rpc_glock);
+
+        if (srpc_data.rpc_services[id] != sv) {
+                spin_unlock(&srpc_data.rpc_glock);
+                return -ENOENT;
+        }
+
+        srpc_data.rpc_services[id] = NULL;
+        spin_unlock(&srpc_data.rpc_glock);
+        return 0;
+}
+
+int
+srpc_post_passive_rdma(int portal, __u64 matchbits, void *buf,
+                       int len, int options, lnet_process_id_t peer,
+                       lnet_handle_md_t *mdh, srpc_event_t *ev)
+{
+        int              rc;
+        lnet_md_t        md;
+        lnet_handle_me_t meh;
+
+        rc = LNetMEAttach(portal, peer, matchbits, 0,
+                          LNET_UNLINK, LNET_INS_AFTER, &meh);
+        if (rc != 0) {
+                CERROR ("LNetMEAttach failed: %d\n", rc);
+                LASSERT (rc == -ENOMEM);
+                return -ENOMEM;
+        }
+
+        md.threshold = 1;
+        md.user_ptr  = ev;
+        md.start     = buf;
+        md.length    = len;
+        md.options   = options;
+        md.eq_handle = srpc_data.rpc_lnet_eq;
+
+        rc = LNetMDAttach(meh, md, LNET_UNLINK, mdh);
+        if (rc != 0) {
+                CERROR ("LNetMDAttach failed: %d\n", rc);
+                LASSERT (rc == -ENOMEM);
+
+                rc = LNetMEUnlink(meh);
+                LASSERT (rc == 0);
+                return -ENOMEM;
+        }
+
+        CDEBUG (D_NET,
+                "Posted passive RDMA: peer %s, portal %d, matchbits "LPX64"\n",
+                libcfs_id2str(peer), portal, matchbits);
+        return 0;
+}
+
+int
+srpc_post_active_rdma(int portal, __u64 matchbits, void *buf, int len, 
+                      int options, lnet_process_id_t peer, lnet_nid_t self,
+                      lnet_handle_md_t *mdh, srpc_event_t *ev)
+{
+        int       rc;
+        lnet_md_t md;
+
+        md.user_ptr  = ev;
+        md.start     = buf;
+        md.length    = len;
+        md.eq_handle = srpc_data.rpc_lnet_eq;
+        md.threshold = ((options & LNET_MD_OP_GET) != 0) ? 2 : 1;
+        md.options   = options & ~(LNET_MD_OP_PUT | LNET_MD_OP_GET);
+
+        rc = LNetMDBind(md, LNET_UNLINK, mdh);
+        if (rc != 0) {
+                CERROR ("LNetMDBind failed: %d\n", rc);
+                LASSERT (rc == -ENOMEM);
+                return -ENOMEM;
+        }
+
+        /* this is kind of an abuse of the LNET_MD_OP_{PUT,GET} options.
+         * they're only meaningful for MDs attached to an ME (i.e. passive
+         * buffers... */
+       if ((options & LNET_MD_OP_PUT) != 0) {
+                rc = LNetPut(self, *mdh, LNET_NOACK_REQ, peer,
+                             portal, matchbits, 0, 0);
+        } else {
+               LASSERT ((options & LNET_MD_OP_GET) != 0);
+
+                rc = LNetGet(self, *mdh, peer, portal, matchbits, 0);
+        }
+
+        if (rc != 0) {
+                CERROR ("LNet%s(%s, %d, "LPD64") failed: %d\n",
+                        ((options & LNET_MD_OP_PUT) != 0) ? "Put" : "Get",
+                        libcfs_id2str(peer), portal, matchbits, rc);
+
+                /* The forthcoming unlink event will complete this operation
+                 * with failure, so fall through and return success here.
+                 */
+                rc = LNetMDUnlink(*mdh);
+                LASSERT (rc == 0);
+        } else {
+                CDEBUG (D_NET,
+                        "Posted active RDMA: peer %s, portal %u, matchbits "LPX64"\n",
+                        libcfs_id2str(peer), portal, matchbits);
+        }
+        return 0;
+}
+
+int
+srpc_post_active_rqtbuf(lnet_process_id_t peer, int service, void *buf,
+                        int len, lnet_handle_md_t *mdh, srpc_event_t *ev)
+{
+        int rc;
+        int portal;
+
+        if (service > SRPC_FRAMEWORK_SERVICE_MAX_ID)
+                portal = SRPC_REQUEST_PORTAL;
+        else
+                portal = SRPC_FRAMEWORK_REQUEST_PORTAL;
+
+        rc = srpc_post_active_rdma(portal, service, buf, len, 
+                                   LNET_MD_OP_PUT, peer,
+                                   LNET_NID_ANY, mdh, ev);
+        return rc;
+}
+
+int
+srpc_post_passive_rqtbuf(int service, void *buf, int len,
+                         lnet_handle_md_t *mdh, srpc_event_t *ev)
+{
+        int               rc;
+        int               portal;
+        lnet_process_id_t any = {.nid = LNET_NID_ANY,
+                                 .pid = LNET_PID_ANY};
+
+        if (service > SRPC_FRAMEWORK_SERVICE_MAX_ID)
+                portal = SRPC_REQUEST_PORTAL;
+        else
+                portal = SRPC_FRAMEWORK_REQUEST_PORTAL;
+
+        rc = srpc_post_passive_rdma(portal, service, buf, len,
+                                    LNET_MD_OP_PUT, any, mdh, ev);
+        return rc;
+}
+
+int
+srpc_service_post_buffer (srpc_service_t *sv, srpc_buffer_t *buf)
+{
+        srpc_msg_t *msg = &buf->buf_msg;
+        int         rc;
+
+        LASSERT (!sv->sv_shuttingdown);
+
+        buf->buf_mdh = LNET_INVALID_HANDLE;
+        list_add(&buf->buf_list, &sv->sv_posted_msgq);
+        sv->sv_nposted_msg++;
+        spin_unlock(&sv->sv_lock);
+
+        rc = srpc_post_passive_rqtbuf(sv->sv_id, msg, sizeof(*msg),
+                                      &buf->buf_mdh, &sv->sv_ev);
+
+        /* At this point, a RPC (new or delayed) may have arrived in
+         * msg and its event handler has been called. So we must add
+         * buf to sv_posted_msgq _before_ dropping sv_lock */
+
+        spin_lock(&sv->sv_lock);
+
+        if (rc == 0) {
+                if (sv->sv_shuttingdown) {
+                        spin_unlock(&sv->sv_lock);
+
+                        /* srpc_shutdown_service might have tried to unlink me
+                         * when my buf_mdh was still invalid */
+                        LNetMDUnlink(buf->buf_mdh);
+
+                        spin_lock(&sv->sv_lock);
+                }
+                return 0;
+        }
+
+        sv->sv_nposted_msg--;
+        if (sv->sv_shuttingdown) return rc;
+
+        list_del(&buf->buf_list);
+
+        spin_unlock(&sv->sv_lock);
+        LIBCFS_FREE(buf, sizeof(*buf));
+        spin_lock(&sv->sv_lock);
+        return rc; 
+}
+
+int
+srpc_service_add_buffers (srpc_service_t *sv, int nbuffer)
+{
+        int                rc;
+        int                posted;
+        srpc_buffer_t     *buf;
+
+        LASSERTF (nbuffer > 0,
+                  "nbuffer must be positive: %d\n", nbuffer);
+
+        for (posted = 0; posted < nbuffer; posted++) {
+                LIBCFS_ALLOC(buf, sizeof(*buf));
+                if (buf == NULL) break;
+
+                spin_lock(&sv->sv_lock);
+                rc = srpc_service_post_buffer(sv, buf);
+                spin_unlock(&sv->sv_lock);
+
+                if (rc != 0) break;
+        }
+
+        return posted;
+}
+
+void
+srpc_service_remove_buffers (srpc_service_t *sv, int nbuffer)
+{
+        LASSERTF (nbuffer > 0,
+                  "nbuffer must be positive: %d\n", nbuffer);
+
+        spin_lock(&sv->sv_lock);
+
+        LASSERT (sv->sv_nprune >= 0);
+        LASSERT (!sv->sv_shuttingdown);
+
+        sv->sv_nprune += nbuffer;
+
+        spin_unlock(&sv->sv_lock);
+        return;
+}
+
+/* returns 1 if sv has finished, otherwise 0 */
+int
+srpc_finish_service (srpc_service_t *sv)
+{
+        srpc_server_rpc_t *rpc;
+        srpc_buffer_t     *buf;
+
+        spin_lock(&sv->sv_lock);
+
+        LASSERT (sv->sv_shuttingdown); /* srpc_shutdown_service called */
+
+        if (sv->sv_nposted_msg != 0 || !list_empty(&sv->sv_active_rpcq)) {
+                CDEBUG (D_NET,
+                        "waiting for %d posted buffers to unlink and "
+                        "in-flight RPCs to die.\n",
+                        sv->sv_nposted_msg);
+
+                if (!list_empty(&sv->sv_active_rpcq)) {
+                        rpc = list_entry(sv->sv_active_rpcq.next,
+                                         srpc_server_rpc_t, srpc_list);
+                        CDEBUG (D_NETERROR,
+                                "Active RPC on shutdown: sv %s, peer %s, "
+                                "wi %s scheduled %d running %d, "
+                                "ev fired %d type %d status %d lnet %d\n",
+                                sv->sv_name, libcfs_id2str(rpc->srpc_peer),
+                                swi_state2str(rpc->srpc_wi.wi_state),
+                                rpc->srpc_wi.wi_scheduled,
+                                rpc->srpc_wi.wi_running,
+                                rpc->srpc_ev.ev_fired,
+                                rpc->srpc_ev.ev_type,
+                                rpc->srpc_ev.ev_status,
+                                rpc->srpc_ev.ev_lnet);
+                }
+
+                spin_unlock(&sv->sv_lock);
+                return 0;
+        }
+
+        spin_unlock(&sv->sv_lock); /* no lock needed from now on */
+
+        for (;;) {
+                struct list_head *q;
+
+                if (!list_empty(&sv->sv_posted_msgq))
+                        q = &sv->sv_posted_msgq;
+                else if (!list_empty(&sv->sv_blocked_msgq))
+                        q = &sv->sv_blocked_msgq;
+                else
+                        break;
+
+                buf = list_entry(q->next, srpc_buffer_t, buf_list);
+                list_del(&buf->buf_list);
+
+                LIBCFS_FREE(buf, sizeof(*buf));
+        }
+
+        while (!list_empty(&sv->sv_free_rpcq)) {
+                rpc = list_entry(sv->sv_free_rpcq.next,
+                                 srpc_server_rpc_t, srpc_list);
+                list_del(&rpc->srpc_list);
+                LIBCFS_FREE(rpc, sizeof(*rpc));
+        }
+
+        return 1;
+}
+
+/* called with sv->sv_lock held */
+void
+srpc_service_recycle_buffer (srpc_service_t *sv, srpc_buffer_t *buf)
+{
+        if (sv->sv_shuttingdown)
+                goto free;
+
+        if (sv->sv_nprune == 0) {
+                if (srpc_service_post_buffer(sv, buf) != 0)
+                        CWARN ("Failed to post %s buffer\n", sv->sv_name);
+                return;
+        }
+
+        sv->sv_nprune--;
+free:
+        spin_unlock(&sv->sv_lock);
+        LIBCFS_FREE(buf, sizeof(*buf));
+        spin_lock(&sv->sv_lock);
+}
+
+void
+srpc_shutdown_service (srpc_service_t *sv)
+{
+        srpc_server_rpc_t *rpc;
+        srpc_buffer_t     *buf;
+
+        spin_lock(&sv->sv_lock);
+
+        CDEBUG (D_NET, "Shutting down service: id %d, name %s\n",
+                sv->sv_id, sv->sv_name);
+
+        sv->sv_shuttingdown = 1; /* i.e. no new active RPC */
+
+        /* schedule in-flight RPCs to notice the shutdown */
+        list_for_each_entry (rpc, &sv->sv_active_rpcq, srpc_list) {
+                swi_schedule_workitem(&rpc->srpc_wi);
+        }
+
+        spin_unlock(&sv->sv_lock);
+
+        /* OK to traverse sv_posted_msgq without lock, since no one
+         * touches sv_posted_msgq now */
+        list_for_each_entry (buf, &sv->sv_posted_msgq, buf_list)
+                LNetMDUnlink(buf->buf_mdh);
+
+        return;
+}
+
+int
+srpc_send_request (srpc_client_rpc_t *rpc)
+{
+        srpc_event_t *ev = &rpc->crpc_reqstev;
+        int           rc;
+
+        ev->ev_fired = 0;
+        ev->ev_data  = rpc;
+        ev->ev_type  = SRPC_REQUEST_SENT;
+
+        rc = srpc_post_active_rqtbuf(rpc->crpc_dest, rpc->crpc_service,
+                                     &rpc->crpc_reqstmsg, sizeof(srpc_msg_t),
+                                     &rpc->crpc_reqstmdh, ev);
+        if (rc != 0) {
+                LASSERT (rc == -ENOMEM);
+                ev->ev_fired = 1;  /* no more event expected */
+        }
+        return rc;
+}
+
+int
+srpc_prepare_reply (srpc_client_rpc_t *rpc)
+{
+        srpc_event_t *ev = &rpc->crpc_replyev;
+        __u64        *id = &rpc->crpc_reqstmsg.msg_body.reqst.rpyid;
+        int           rc;
+
+        ev->ev_fired = 0;
+        ev->ev_data  = rpc;
+        ev->ev_type  = SRPC_REPLY_RCVD;
+
+        *id = srpc_next_id();
+
+        rc = srpc_post_passive_rdma(SRPC_RDMA_PORTAL, *id,
+                                    &rpc->crpc_replymsg, sizeof(srpc_msg_t),
+                                    LNET_MD_OP_PUT, rpc->crpc_dest,
+                                    &rpc->crpc_replymdh, ev);
+        if (rc != 0) {
+                LASSERT (rc == -ENOMEM);
+                ev->ev_fired = 1;  /* no more event expected */
+        }
+        return rc;
+}
+
+int
+srpc_prepare_bulk (srpc_client_rpc_t *rpc)
+{
+        srpc_bulk_t  *bk = &rpc->crpc_bulk;
+        srpc_event_t *ev = &rpc->crpc_bulkev;
+        __u64        *id = &rpc->crpc_reqstmsg.msg_body.reqst.bulkid;
+        int           rc;
+        int           opt;
+
+        LASSERT (bk->bk_niov <= LNET_MAX_IOV);
+
+        if (bk->bk_niov == 0) return 0; /* nothing to do */
+
+        opt = bk->bk_sink ? LNET_MD_OP_PUT : LNET_MD_OP_GET;
+#ifdef __KERNEL__
+        opt |= LNET_MD_KIOV;
+#else
+        opt |= LNET_MD_IOVEC;
+#endif
+
+        ev->ev_fired = 0;
+        ev->ev_data  = rpc;
+        ev->ev_type  = SRPC_BULK_REQ_RCVD;
+
+        *id = srpc_next_id();
+
+        rc = srpc_post_passive_rdma(SRPC_RDMA_PORTAL, *id,
+                                    &bk->bk_iovs[0], bk->bk_niov, opt,
+                                    rpc->crpc_dest, &bk->bk_mdh, ev);
+        if (rc != 0) {
+                LASSERT (rc == -ENOMEM);
+                ev->ev_fired = 1;  /* no more event expected */
+        }
+        return rc;
+}
+
+int
+srpc_do_bulk (srpc_server_rpc_t *rpc)
+{
+        srpc_event_t  *ev = &rpc->srpc_ev;
+        srpc_bulk_t   *bk = rpc->srpc_bulk;
+        __u64          id = rpc->srpc_reqstbuf->buf_msg.msg_body.reqst.bulkid;
+        int            rc;
+        int            opt;
+
+        LASSERT (bk != NULL);
+
+        opt = bk->bk_sink ? LNET_MD_OP_GET : LNET_MD_OP_PUT;
+#ifdef __KERNEL__
+        opt |= LNET_MD_KIOV;
+#else
+        opt |= LNET_MD_IOVEC;
+#endif
+
+        ev->ev_fired = 0;
+        ev->ev_data  = rpc;
+        ev->ev_type  = bk->bk_sink ? SRPC_BULK_GET_RPLD : SRPC_BULK_PUT_SENT;
+
+        rc = srpc_post_active_rdma(SRPC_RDMA_PORTAL, id,
+                                   &bk->bk_iovs[0], bk->bk_niov, opt,
+                                   rpc->srpc_peer, rpc->srpc_self,
+                                   &bk->bk_mdh, ev);
+        if (rc != 0)
+                ev->ev_fired = 1;  /* no more event expected */
+        return rc;
+}
+
+/* called with srpc_service_t::sv_lock held */
+inline void
+srpc_schedule_server_rpc (srpc_server_rpc_t *rpc)
+{
+        srpc_service_t *sv = rpc->srpc_service;
+
+        if (sv->sv_id > SRPC_FRAMEWORK_SERVICE_MAX_ID)
+                swi_schedule_workitem(&rpc->srpc_wi);
+        else    /* framework RPCs are handled one by one */
+                swi_schedule_serial_workitem(&rpc->srpc_wi);
+
+        return;
+}
+
+/* only called from srpc_handle_rpc */
+void
+srpc_server_rpc_done (srpc_server_rpc_t *rpc, int status)
+{
+        srpc_service_t *sv = rpc->srpc_service;
+        srpc_buffer_t  *buffer;
+
+        LASSERT (status != 0 || rpc->srpc_wi.wi_state == SWI_STATE_DONE);
+
+        rpc->srpc_status = status;
+
+        CDEBUG (status == 0 ? D_NET : D_NETERROR,
+                "Server RPC done: service %s, peer %s, status %s:%d\n",
+                sv->sv_name, libcfs_id2str(rpc->srpc_peer),
+                swi_state2str(rpc->srpc_wi.wi_state), status);
+
+        if (status != 0) {
+                spin_lock(&srpc_data.rpc_glock);
+                srpc_data.rpc_counters.rpcs_dropped++;
+                spin_unlock(&srpc_data.rpc_glock);
+        }
+
+        if (rpc->srpc_done != NULL)
+                (*rpc->srpc_done) (rpc);
+        LASSERT (rpc->srpc_bulk == NULL);
+
+        spin_lock(&sv->sv_lock);
+
+        if (rpc->srpc_reqstbuf != NULL) {
+                /* NB might drop sv_lock in srpc_service_recycle_buffer, but
+                 * sv won't go away for sv_active_rpcq must not be empty */
+                srpc_service_recycle_buffer(sv, rpc->srpc_reqstbuf);
+                rpc->srpc_reqstbuf = NULL;
+        }
+
+        list_del(&rpc->srpc_list); /* from sv->sv_active_rpcq */
+
+        /*
+         * No one can schedule me now since:
+         * - I'm not on sv_active_rpcq.
+         * - all LNet events have been fired.
+         * Cancel pending schedules and prevent future schedule attempts:
+         */
+        LASSERT (rpc->srpc_ev.ev_fired);
+        swi_kill_workitem(&rpc->srpc_wi);
+
+        if (!sv->sv_shuttingdown && !list_empty(&sv->sv_blocked_msgq)) {
+                buffer = list_entry(sv->sv_blocked_msgq.next,
+                                    srpc_buffer_t, buf_list);
+                list_del(&buffer->buf_list);
+
+                srpc_init_server_rpc(rpc, sv, buffer);
+                list_add_tail(&rpc->srpc_list, &sv->sv_active_rpcq);
+                srpc_schedule_server_rpc(rpc);
+        } else {
+                list_add(&rpc->srpc_list, &sv->sv_free_rpcq);
+        }
+
+        spin_unlock(&sv->sv_lock);
+        return;
+}
+
+/* handles an incoming RPC */
+int
+srpc_handle_rpc (swi_workitem_t *wi)
+{
+        srpc_server_rpc_t *rpc = wi->wi_data;
+        srpc_service_t    *sv = rpc->srpc_service;
+        srpc_event_t      *ev = &rpc->srpc_ev;
+        int                rc = 0;
+
+        LASSERT (wi == &rpc->srpc_wi);
+
+        spin_lock(&sv->sv_lock);
+
+        if (sv->sv_shuttingdown) {
+                spin_unlock(&sv->sv_lock);
+
+                if (rpc->srpc_bulk != NULL)
+                        LNetMDUnlink(rpc->srpc_bulk->bk_mdh);
+                LNetMDUnlink(rpc->srpc_replymdh);
+
+                if (ev->ev_fired) { /* no more event, OK to finish */
+                        srpc_server_rpc_done(rpc, -ESHUTDOWN);
+                        return 1;
+                }
+                return 0;
+        }
+
+        spin_unlock(&sv->sv_lock);
+
+        switch (wi->wi_state) {
+        default:
+                LBUG ();
+        case SWI_STATE_NEWBORN: {
+                srpc_msg_t           *msg;
+                srpc_generic_reply_t *reply;
+
+                msg = &rpc->srpc_reqstbuf->buf_msg;
+                reply = &rpc->srpc_replymsg.msg_body.reply;
+
+                if (msg->msg_version != SRPC_MSG_VERSION &&
+                    msg->msg_version != __swab32(SRPC_MSG_VERSION)) {
+                        CWARN ("Version mismatch: %u, %u expected, from %s\n",
+                               msg->msg_version, SRPC_MSG_VERSION,
+                               libcfs_id2str(rpc->srpc_peer));
+                        reply->status = EPROTO;
+                } else {
+                        reply->status = 0;
+                        rc = (*sv->sv_handler) (rpc);
+                        LASSERT (reply->status == 0 || !rpc->srpc_bulk);
+                }
+
+                if (rc != 0) {
+                        srpc_server_rpc_done(rpc, rc);
+                        return 1;
+                }
+
+                wi->wi_state = SWI_STATE_BULK_STARTED;
+
+                if (rpc->srpc_bulk != NULL) {
+                        rc = srpc_do_bulk(rpc);
+                        if (rc == 0)
+                                return 0; /* wait for bulk */
+
+                        LASSERT (ev->ev_fired);
+                        ev->ev_status = rc;
+                }
+        }
+        case SWI_STATE_BULK_STARTED:
+                LASSERT (rpc->srpc_bulk == NULL || ev->ev_fired);
+
+                if (rpc->srpc_bulk != NULL) {
+                        rc = ev->ev_status;
+
+                        if (sv->sv_bulk_ready != NULL)
+                                rc = (*sv->sv_bulk_ready) (rpc, rc);
+
+                        if (rc != 0) {
+                                srpc_server_rpc_done(rpc, rc);
+                                return 1;
+                        }
+                }
+
+                wi->wi_state = SWI_STATE_REPLY_SUBMITTED;
+                rc = srpc_send_reply(rpc);
+                if (rc == 0)
+                        return 0; /* wait for reply */
+                srpc_server_rpc_done(rpc, rc);
+                return 1;
+
+        case SWI_STATE_REPLY_SUBMITTED:
+                LASSERT (ev->ev_fired);
+
+                wi->wi_state = SWI_STATE_DONE;
+                srpc_server_rpc_done(rpc, ev->ev_status);
+                return 1;
+        }
+
+        return 0;
+}
+
+void
+srpc_client_rpc_expired (void *data)
+{
+        srpc_client_rpc_t *rpc = data;
+
+        CWARN ("Client RPC expired: service %d, peer %s, timeout %d.\n",
+               rpc->crpc_service, libcfs_id2str(rpc->crpc_dest),
+               rpc->crpc_timeout);
+
+        spin_lock(&rpc->crpc_lock);
+
+        rpc->crpc_timeout = 0;
+        srpc_abort_rpc(rpc, -ETIMEDOUT);
+
+        spin_unlock(&rpc->crpc_lock);
+
+        spin_lock(&srpc_data.rpc_glock);
+        srpc_data.rpc_counters.rpcs_expired++;
+        spin_unlock(&srpc_data.rpc_glock);
+        return;
+}
+
+inline void
+srpc_add_client_rpc_timer (srpc_client_rpc_t *rpc)
+{
+        stt_timer_t *timer = &rpc->crpc_timer;
+
+        if (rpc->crpc_timeout == 0) return;
+
+        CFS_INIT_LIST_HEAD(&timer->stt_list);
+        timer->stt_data    = rpc;
+        timer->stt_func    = srpc_client_rpc_expired;
+        timer->stt_expires = cfs_time_add(rpc->crpc_timeout, 
+                                          cfs_time_current_sec());
+        stt_add_timer(timer);
+        return;
+}
+
+/* 
+ * Called with rpc->crpc_lock held.
+ *
+ * Upon exit the RPC expiry timer is not queued and the handler is not
+ * running on any CPU. */
+void
+srpc_del_client_rpc_timer (srpc_client_rpc_t *rpc)
+{     
+        /* timer not planted or already exploded */
+        if (rpc->crpc_timeout == 0) return;
+
+        /* timer sucessfully defused */
+        if (stt_del_timer(&rpc->crpc_timer)) return;
+
+#ifdef __KERNEL__
+        /* timer detonated, wait for it to explode */
+        while (rpc->crpc_timeout != 0) {
+                spin_unlock(&rpc->crpc_lock);
+
+                cfs_schedule(); 
+
+                spin_lock(&rpc->crpc_lock);
+        }
+#else
+        LBUG(); /* impossible in single-threaded runtime */
+#endif
+        return;
+}
+
+void
+srpc_check_sends (srpc_peer_t *peer, int credits)
+{
+        struct list_head  *q;
+        srpc_client_rpc_t *rpc;
+
+        LASSERT (credits >= 0);
+        LASSERT (srpc_data.rpc_state == SRPC_STATE_RUNNING);
+
+        spin_lock(&peer->stp_lock);
+        peer->stp_credits += credits;
+
+        while (peer->stp_credits) {
+                if (!list_empty(&peer->stp_ctl_rpcq))
+                        q = &peer->stp_ctl_rpcq;
+                else if (!list_empty(&peer->stp_rpcq))
+                        q = &peer->stp_rpcq;
+                else
+                        break;
+
+                peer->stp_credits--;
+
+                rpc = list_entry(q->next, srpc_client_rpc_t, crpc_privl);
+                list_del_init(&rpc->crpc_privl);
+                srpc_client_rpc_decref(rpc);  /* --ref for peer->*rpcq */
+
+                swi_schedule_workitem(&rpc->crpc_wi);
+        }
+
+        spin_unlock(&peer->stp_lock);
+        return;
+}
+
+void
+srpc_client_rpc_done (srpc_client_rpc_t *rpc, int status)
+{
+        swi_workitem_t *wi = &rpc->crpc_wi;
+        srpc_peer_t    *peer = rpc->crpc_peer;
+
+        LASSERT (status != 0 || wi->wi_state == SWI_STATE_DONE);
+
+        spin_lock(&rpc->crpc_lock);
+
+        rpc->crpc_closed = 1;
+        if (rpc->crpc_status == 0)
+                rpc->crpc_status = status;
+
+        srpc_del_client_rpc_timer(rpc);
+
+        CDEBUG ((status == 0) ? D_NET : D_NETERROR,
+                "Client RPC done: service %d, peer %s, status %s:%d:%d\n",
+                rpc->crpc_service, libcfs_id2str(rpc->crpc_dest),
+                swi_state2str(wi->wi_state), rpc->crpc_aborted, status);
+
+        /*
+         * No one can schedule me now since:
+         * - RPC timer has been defused.
+         * - all LNet events have been fired.
+         * - crpc_closed has been set, preventing srpc_abort_rpc from 
+         *   scheduling me.
+         * Cancel pending schedules and prevent future schedule attempts:
+         */
+        LASSERT (!srpc_event_pending(rpc));
+        swi_kill_workitem(wi);
+
+        spin_unlock(&rpc->crpc_lock);
+
+        (*rpc->crpc_done) (rpc);
+
+        if (peer != NULL)
+                srpc_check_sends(peer, 1);
+        return;
+}
+
+/* sends an outgoing RPC */
+int
+srpc_send_rpc (swi_workitem_t *wi)
+{
+        int                rc = 0;
+        srpc_client_rpc_t *rpc = wi->wi_data;
+        srpc_msg_t        *reply = &rpc->crpc_replymsg;
+        int                do_bulk = rpc->crpc_bulk.bk_niov > 0;
+
+        LASSERT (rpc != NULL);
+        LASSERT (wi == &rpc->crpc_wi);
+
+        spin_lock(&rpc->crpc_lock);
+
+        if (rpc->crpc_aborted) {
+                spin_unlock(&rpc->crpc_lock);
+                goto abort;
+        }
+
+        spin_unlock(&rpc->crpc_lock);
+
+        switch (wi->wi_state) {
+        default:
+                LBUG ();
+        case SWI_STATE_NEWBORN:
+                LASSERT (!srpc_event_pending(rpc));
+
+                rc = srpc_prepare_reply(rpc);
+                if (rc != 0) {
+                        srpc_client_rpc_done(rpc, rc);
+                        return 1;
+                }
+
+                rc = srpc_prepare_bulk(rpc);
+                if (rc != 0) break;
+
+                wi->wi_state = SWI_STATE_REQUEST_SUBMITTED;
+                rc = srpc_send_request(rpc);
+                break;
+
+        case SWI_STATE_REQUEST_SUBMITTED:
+                /* CAVEAT EMPTOR: rqtev, rpyev, and bulkev may come in any
+                 * order; however, they're processed in a strict order: 
+                 * rqt, rpy, and bulk. */
+                if (!rpc->crpc_reqstev.ev_fired) break;
+
+                rc = rpc->crpc_reqstev.ev_status;
+                if (rc != 0) break;
+
+                wi->wi_state = SWI_STATE_REQUEST_SENT;
+                /* perhaps more events, fall thru */
+        case SWI_STATE_REQUEST_SENT: {
+                srpc_msg_type_t type = srpc_service2reply(rpc->crpc_service);
+
+                if (!rpc->crpc_replyev.ev_fired) break;
+
+                rc = rpc->crpc_replyev.ev_status;
+                if (rc != 0) break;
+
+                if ((reply->msg_type != type && 
+                     reply->msg_type != __swab32(type)) ||
+                    (reply->msg_magic != SRPC_MSG_MAGIC &&
+                     reply->msg_magic != __swab32(SRPC_MSG_MAGIC))) {
+                        CWARN ("Bad message from %s: type %u (%d expected),"
+                               " magic %u (%d expected).\n",
+                               libcfs_id2str(rpc->crpc_dest),
+                               reply->msg_type, type,
+                               reply->msg_magic, SRPC_MSG_MAGIC);
+                        rc = -EBADMSG;
+                        break;
+                }
+
+                if (do_bulk && reply->msg_body.reply.status != 0) {
+                        CWARN ("Remote error %d at %s, unlink bulk buffer in "
+                               "case peer didn't initiate bulk transfer\n",
+                               reply->msg_body.reply.status,
+                               libcfs_id2str(rpc->crpc_dest));
+                        LNetMDUnlink(rpc->crpc_bulk.bk_mdh);
+                }
+
+                wi->wi_state = SWI_STATE_REPLY_RECEIVED;
+        }
+        case SWI_STATE_REPLY_RECEIVED:
+                if (do_bulk && !rpc->crpc_bulkev.ev_fired) break;
+
+                rc = do_bulk ? rpc->crpc_bulkev.ev_status : 0;
+
+                /* Bulk buffer was unlinked due to remote error. Clear error
+                 * since reply buffer still contains valid data.
+                 * NB rpc->crpc_done shouldn't look into bulk data in case of
+                 * remote error. */
+                if (do_bulk && rpc->crpc_bulkev.ev_lnet == LNET_EVENT_UNLINK &&
+                    rpc->crpc_status == 0 && reply->msg_body.reply.status != 0)
+                        rc = 0;
+
+                wi->wi_state = SWI_STATE_DONE;
+                srpc_client_rpc_done(rpc, rc);
+                return 1;
+        }
+
+        if (rc != 0) {
+                spin_lock(&rpc->crpc_lock);
+                srpc_abort_rpc(rpc, rc);
+                spin_unlock(&rpc->crpc_lock);
+        }
+
+abort:
+        if (rpc->crpc_aborted) {
+                LNetMDUnlink(rpc->crpc_reqstmdh);
+                LNetMDUnlink(rpc->crpc_replymdh);
+                LNetMDUnlink(rpc->crpc_bulk.bk_mdh);
+
+                if (!srpc_event_pending(rpc)) {
+                        srpc_client_rpc_done(rpc, -EINTR);
+                        return 1;
+               &nbs