Blame SOURCES/nfs-utils-2.3.3-rpcctl.patch

ec890a
diff --git a/configure.ac b/configure.ac
ec890a
index f2f2303b..6d464ac5 100644
ec890a
--- a/configure.ac
ec890a
+++ b/configure.ac
ec890a
@@ -639,6 +639,7 @@ AC_CONFIG_FILES([
ec890a
 	tools/rpcgen/Makefile
ec890a
 	tools/mountstats/Makefile
ec890a
 	tools/nfs-iostat/Makefile
ec890a
+	tools/rpcctl/Makefile
ec890a
 	tools/nfsdclnts/Makefile
ec890a
 	tools/nfsconf/Makefile
ec890a
 	tools/nfsdclddb/Makefile
ec890a
diff --git a/tools/Makefile.am b/tools/Makefile.am
ec890a
index 9b4b0803..c3feabbe 100644
ec890a
--- a/tools/Makefile.am
ec890a
+++ b/tools/Makefile.am
ec890a
@@ -12,6 +12,6 @@ if CONFIG_NFSDCLD
ec890a
 OPTDIRS += nfsdclddb
ec890a
 endif
ec890a
 
ec890a
-SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat nfsdclnts $(OPTDIRS)
ec890a
+SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat rpcctl nfsdclnts $(OPTDIRS)
ec890a
 
ec890a
 MAINTAINERCLEANFILES = Makefile.in
ec890a
diff --git a/tools/rpcctl/Makefile.am b/tools/rpcctl/Makefile.am
ec890a
new file mode 100644
ec890a
index 00000000..33fb431f
ec890a
--- /dev/null
ec890a
+++ b/tools/rpcctl/Makefile.am
ec890a
@@ -0,0 +1,13 @@
ec890a
+## Process this file with automake to produce Makefile.in
ec890a
+PYTHON_FILES =  rpcctl.py
ec890a
+
ec890a
+man8_MANS = rpcctl.man
ec890a
+
ec890a
+EXTRA_DIST = $(man8_MANS) $(PYTHON_FILES)
ec890a
+
ec890a
+all-local: $(PYTHON_FILES)
ec890a
+
ec890a
+install-data-hook:
ec890a
+	$(INSTALL) -m 755 rpcctl.py $(DESTDIR)$(sbindir)/rpcctl
ec890a
+
ec890a
+MAINTAINERCLEANFILES=Makefile.in
ec890a
diff --git a/tools/rpcctl/rpcctl.man b/tools/rpcctl/rpcctl.man
ec890a
new file mode 100644
ec890a
index 00000000..b87ba0df
ec890a
--- /dev/null
ec890a
+++ b/tools/rpcctl/rpcctl.man
ec890a
@@ -0,0 +1,67 @@
ec890a
+.\"
ec890a
+.\" rpcctl(8)
ec890a
+.\"
ec890a
+.TH rpcctl 8 "15 Feb 2022"
ec890a
+.SH NAME
ec890a
+rpcctl \- Displays SunRPC connection information
ec890a
+.SH SYNOPSIS
ec890a
+.nf
ec890a
+.BR rpcctl " [ \fB\-h \fR| \fB\-\-help \fR] { \fBclient \fR| \fBswitch \fR| \fBxprt \fR}"
ec890a
+.P
ec890a
+.BR "rpcctl client" " \fR[ \fB\-h \fR| \fB\-\-help \fR] { \fBshow \fR}"
ec890a
+.BR "rpcctl client show " "\fR[ \fB\-h \f| \fB\-\-help \fR] [ \fIXPRT \fR]"
ec890a
+.P
ec890a
+.BR "rpcctl switch" " \fR[ \fB\-h \fR| \fB\-\-help \fR] { \fBset \fR| \fBshow \fR}"
ec890a
+.BR "rpcctl switch set" " \fR[ \fB\-h \fR| \fB\-\-help \fR] \fISWITCH \fBdstaddr \fINEWADDR"
ec890a
+.BR "rpcctl switch show" " \fR[ \fB\-h \fR| \fB\-\-help \fR] [ \fISWITCH \fR]"
ec890a
+.P
ec890a
+.BR "rpcctl xprt" " \fR[ \fB\-h \fR| \fB\-\-help \fR] { \fBremove \fR| \fBset \fR| \fBshow \fR}"
ec890a
+.BR "rpcctl xprt remove" " \fR[ \fB\-h \fR| \fB\-\-help \fR] \fIXPRT"
ec890a
+.BR "rpcctl xprt set" " \fR[ \fB\-h \fR| \fB\-\-help \fR] \fIXPRT \fR{ \fBdstaddr \fINEWADDR \fR| \fBoffline \fR| \fBonline \fR}"
ec890a
+.BR "rpcctl xprt show" " \fR[ \fB\-h \fR| \fB\-\-help \fR] [ \fIXPRT \fR]"
ec890a
+.fi
ec890a
+.SH DESCRIPTION
ec890a
+.RB "The " rpcctl " command displays information collected in the SunRPC sysfs files about the system's SunRPC objects.
ec890a
+.P
ec890a
+.SS rpcctl client \fR- \fBCommands operating on RPC clients
ec890a
+.IP "\fBshow \fR[ \fICLIENT \fR] \fB(default)"
ec890a
+Show detailed information about the RPC clients on this system.
ec890a
+If \fICLIENT \fRwas provided, then only show information about a single RPC client.
ec890a
+.P
ec890a
+.SS rpcctl switch \fR- \fBCommands operating on groups of transports
ec890a
+.IP "\fBset \fISWITCH \fBdstaddr \fINEWADDR"
ec890a
+Change the destination address of all transports in the \fISWITCH \fRto \fINEWADDR\fR.
ec890a
+\fINEWADDR \fRcan be an IP address, DNS name, or anything else resolvable by \fBgethostbyname\fR(3).
ec890a
+.IP "\fBshow \fR[ \fISWITCH \fR] \fB(default)"
ec890a
+Show detailed information about groups of transports on this system.
ec890a
+If \fISWITCH \fRwas provided, then only show information about a single transport group.
ec890a
+.P
ec890a
+.SS rpcctl xprt \fR- \fBCommands operating on individual transports
ec890a
+.IP "\fBremove \fIXPRT"
ec890a
+Removes the specified \fIXPRT \fRfrom the system.
ec890a
+Note that "main" transports cannot be removed.
ec890a
+.P
ec890a
+.IP "\fBset \fIXPRT \fBdstaddr \fINEWADDR"
ec890a
+Change the destination address of the specified \fIXPRT \fR to \fINEWADDR\fR.
ec890a
+\fINEWADDR \fRcan be an IP address, DNS name, or anything else resolvable by \fBgethostbyname\fR(3).
ec890a
+.P
ec890a
+.IP "\fBset \fIXPRT \fBoffline"
ec890a
+Sets the specified \fIXPRT\fR's state to offline.
ec890a
+.P
ec890a
+.IP "\fBset \fIXPRT \fBonline"
ec890a
+Sets the specified \fIXPRT\fR's state to online.
ec890a
+.IP "\fBshow \fR[ \fIXPRT \fR] \fB(default)"
ec890a
+Show detailed information about this system's transports.
ec890a
+If \fIXPRT \fRwas provided, then only show information about a single transport.
ec890a
+.SH EXAMPLES
ec890a
+.IP "\fBrpcctl switch show switch-2"
ec890a
+Show details about the RPC switch named "switch-2".
ec890a
+.IP "\fBrpcctl xprt remove xprt-4"
ec890a
+Remove the xprt named "xprt-4" from the system.
ec890a
+.IP "\fBrpcctl xprt set xprt-3 dstaddr https://linux-nfs.org
ec890a
+Change the dstaddr of the xprt named "xprt-3" to point to linux-nfs.org
ec890a
+.SH DIRECTORY
ec890a
+.TP
ec890a
+.B /sys/kernel/sunrpc/
ec890a
+.SH AUTHOR
ec890a
+Anna Schumaker <Anna.Schumaker@Netapp.com>
ec890a
diff --git a/tools/rpcctl/rpcctl.py b/tools/rpcctl/rpcctl.py
ec890a
new file mode 100755
ec890a
index 00000000..d2110ad6
ec890a
--- /dev/null
ec890a
+++ b/tools/rpcctl/rpcctl.py
ec890a
@@ -0,0 +1,262 @@
ec890a
+#!/usr/bin/python3
ec890a
+import argparse
ec890a
+import collections
ec890a
+import errno
ec890a
+import os
ec890a
+import pathlib
ec890a
+import socket
ec890a
+import sys
ec890a
+
ec890a
+with open("/proc/mounts", 'r') as f:
ec890a
+    mount = [ line.split()[1] for line in f if "sysfs" in line ]
ec890a
+    if len(mount) == 0:
ec890a
+        print("ERROR: sysfs is not mounted")
ec890a
+        sys.exit(1)
ec890a
+
ec890a
+sunrpc = pathlib.Path(mount[0]) / "kernel" / "sunrpc"
ec890a
+if not sunrpc.is_dir():
ec890a
+    print("ERROR: sysfs does not have sunrpc directory")
ec890a
+    sys.exit(1)
ec890a
+
ec890a
+def read_addr_file(path):
ec890a
+    try:
ec890a
+        with open(path, 'r') as f:
ec890a
+            return f.readline().strip()
ec890a
+    except:
ec890a
+        return "(enoent)"
ec890a
+
ec890a
+def write_addr_file(path, newaddr):
ec890a
+    with open(path, 'w') as f:
ec890a
+        f.write(newaddr)
ec890a
+    return read_addr_file(path)
ec890a
+
ec890a
+def read_info_file(path):
ec890a
+    res = collections.defaultdict(int)
ec890a
+    try:
ec890a
+        with open(path) as info:
ec890a
+            lines = [ l.split("=", 1) for l in info if "=" in l ]
ec890a
+            res.update({ key:int(val.strip()) for (key, val) in lines })
ec890a
+    finally:
ec890a
+        return res
ec890a
+
ec890a
+
ec890a
+class Xprt:
ec890a
+    def __init__(self, path):
ec890a
+        self.path = path
ec890a
+        self.name = path.stem.rsplit("-", 1)[0]
ec890a
+        self.type = path.stem.split("-")[2]
ec890a
+        self.info = read_info_file(path / "xprt_info")
ec890a
+        self.dstaddr = read_addr_file(path / "dstaddr")
ec890a
+        self.srcaddr = read_addr_file(path / "srcaddr")
ec890a
+        self.read_state()
ec890a
+
ec890a
+    def __lt__(self, rhs):
ec890a
+        return self.name < rhs.name
ec890a
+
ec890a
+    def _xprt(self):
ec890a
+        main = ", main" if self.info.get("main_xprt") else ""
ec890a
+        return f"{self.name}: {self.type}, {self.dstaddr}, " \
ec890a
+               f"port {self.info['dst_port']}, state <{self.state}>{main}"
ec890a
+
ec890a
+    def _src_reqs(self):
ec890a
+        return f"	Source: {self.srcaddr}, port {self.info['src_port']}, " \
ec890a
+               f"Requests: {self.info['num_reqs']}"
ec890a
+
ec890a
+    def _cong_slots(self):
ec890a
+        return f"	Congestion: cur {self.info['cur_cong']}, win {self.info['cong_win']}, " \
ec890a
+               f"Slots: min {self.info['min_num_slots']}, max {self.info['max_num_slots']}"
ec890a
+
ec890a
+    def _queues(self):
ec890a
+        return f"	Queues: binding {self.info['binding_q_len']}, " \
ec890a
+               f"sending {self.info['sending_q_len']}, pending {self.info['pending_q_len']}, " \
ec890a
+               f"backlog {self.info['backlog_q_len']}, tasks {self.info['tasks_queuelen']}"
ec890a
+
ec890a
+    def __str__(self):
ec890a
+        if not self.path.exists():
ec890a
+            return f"{self.name}: has been removed"
ec890a
+        return "\n".join([self._xprt(), self._src_reqs(),
ec890a
+                          self._cong_slots(), self._queues() ])
ec890a
+
ec890a
+    def read_state(self):
ec890a
+        if self.path.exists():
ec890a
+            with open(self.path / "xprt_state") as f:
ec890a
+                self.state = ','.join(f.readline().split()[1:])
ec890a
+
ec890a
+    def small_str(self):
ec890a
+        main = " [main]" if self.info.get("main_xprt") else ""
ec890a
+        return f"{self.name}: {self.type}, {self.dstaddr}{main}"
ec890a
+
ec890a
+    def set_dstaddr(self, newaddr):
ec890a
+        self.dstaddr = write_addr_file(self.path / "dstaddr", newaddr)
ec890a
+
ec890a
+    def set_state(self, state):
ec890a
+        if self.info.get("main_xprt"):
ec890a
+            raise Exception(f"Main xprts cannot be set {state}")
ec890a
+        with open(self.path / "xprt_state", 'w') as f:
ec890a
+            f.write(state)
ec890a
+        self.read_state()
ec890a
+
ec890a
+    def remove(self):
ec890a
+        if self.info.get("main_xprt"):
ec890a
+            raise Exception("Main xprts cannot be removed")
ec890a
+        self.set_state("offline")
ec890a
+        self.set_state("remove")
ec890a
+
ec890a
+    def add_command(subparser):
ec890a
+        parser = subparser.add_parser("xprt", help="Commands for individual xprts")
ec890a
+        parser.set_defaults(func=Xprt.show, xprt=None)
ec890a
+        subparser = parser.add_subparsers()
ec890a
+
ec890a
+        remove = subparser.add_parser("remove", help="Remove an xprt")
ec890a
+        remove.add_argument("xprt", metavar="XPRT", nargs=1,
ec890a
+                            help="Name of the xprt to remove")
ec890a
+        remove.set_defaults(func=Xprt.set_property, property="remove")
ec890a
+
ec890a
+        show = subparser.add_parser("show", help="Show xprts")
ec890a
+        show.add_argument("xprt", metavar="XPRT", nargs='?',
ec890a
+                          help="Name of a specific xprt to show")
ec890a
+        show.set_defaults(func=Xprt.show)
ec890a
+
ec890a
+        set = subparser.add_parser("set", help="Change an xprt property")
ec890a
+        set.add_argument("xprt", metavar="XPRT", nargs=1,
ec890a
+                         help="Name of a specific xprt to modify")
ec890a
+        subparser = set.add_subparsers(required=True)
ec890a
+        online = subparser.add_parser("online", help="Set an xprt online")
ec890a
+        online.set_defaults(func=Xprt.set_property, property="online")
ec890a
+        offline = subparser.add_parser("offline", help="Set an xprt offline")
ec890a
+        offline.set_defaults(func=Xprt.set_property, property="offline")
ec890a
+        dstaddr = subparser.add_parser("dstaddr", help="Change an xprt's dstaddr")
ec890a
+        dstaddr.add_argument("newaddr", metavar="NEWADDR", nargs=1,
ec890a
+                             help="The new address for the xprt")
ec890a
+        dstaddr.set_defaults(func=Xprt.set_property, property="dstaddr")
ec890a
+
ec890a
+    def get_by_name(name):
ec890a
+        glob = f"**/{name}-*" if name else "**/xprt-*"
ec890a
+        res = [ Xprt(x) for x in (sunrpc / "xprt-switches").glob(glob) ]
ec890a
+        if name and len(res) == 0:
ec890a
+            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
ec890a
+                                    f"{sunrpc / 'xprt-switches' / glob}")
ec890a
+        return sorted(res)
ec890a
+
ec890a
+    def show(args):
ec890a
+        for xprt in Xprt.get_by_name(args.xprt):
ec890a
+            print(xprt)
ec890a
+
ec890a
+    def set_property(args):
ec890a
+        for xprt in Xprt.get_by_name(args.xprt[0]):
ec890a
+            if args.property == "dstaddr":
ec890a
+                xprt.set_dstaddr(socket.gethostbyname(args.newaddr[0]))
ec890a
+            elif args.property == "remove":
ec890a
+                xprt.remove()
ec890a
+            else:
ec890a
+                xprt.set_state(args.property)
ec890a
+        print(xprt)
ec890a
+
ec890a
+
ec890a
+class XprtSwitch:
ec890a
+    def __init__(self, path, sep=":"):
ec890a
+        self.path = path
ec890a
+        self.name = path.stem
ec890a
+        self.info = read_info_file(path / "xprt_switch_info")
ec890a
+        self.xprts = sorted([ Xprt(p) for p in self.path.iterdir() if p.is_dir() ])
ec890a
+        self.sep = sep
ec890a
+
ec890a
+    def __lt__(self, rhs):
ec890a
+        return self.name < rhs.name
ec890a
+
ec890a
+    def __str__(self):
ec890a
+        switch =  f"{self.name}{self.sep} " \
ec890a
+                  f"xprts {self.info['num_xprts']}, " \
ec890a
+                  f"active {self.info['num_active']}, " \
ec890a
+                  f"queue {self.info['queue_len']}"
ec890a
+        xprts = [ f"	{x.small_str()}" for x in self.xprts ]
ec890a
+        return "\n".join([ switch ] + xprts)
ec890a
+
ec890a
+    def add_command(subparser):
ec890a
+        parser = subparser.add_parser("switch", help="Commands for xprt switches")
ec890a
+        parser.set_defaults(func=XprtSwitch.show, switch=None)
ec890a
+        subparser = parser.add_subparsers()
ec890a
+
ec890a
+        show = subparser.add_parser("show", help="Show xprt switches")
ec890a
+        show.add_argument("switch", metavar="SWITCH", nargs='?',
ec890a
+                          help="Name of a specific switch to show")
ec890a
+        show.set_defaults(func=XprtSwitch.show)
ec890a
+
ec890a
+        set = subparser.add_parser("set", help="Change an xprt switch property")
ec890a
+        set.add_argument("switch", metavar="SWITCH", nargs=1,
ec890a
+                         help="Name of a specific xprt switch to modify")
ec890a
+        subparser = set.add_subparsers(required=True)
ec890a
+        dstaddr = subparser.add_parser("dstaddr", help="Change an xprt switch's dstaddr")
ec890a
+        dstaddr.add_argument("newaddr", metavar="NEWADDR", nargs=1,
ec890a
+                             help="The new address for the xprt switch")
ec890a
+        dstaddr.set_defaults(func=XprtSwitch.set_property, property="dstaddr")
ec890a
+
ec890a
+    def get_by_name(name):
ec890a
+        xprt_switches = sunrpc / "xprt-switches"
ec890a
+        if name:
ec890a
+            return [ XprtSwitch(xprt_switches / name) ]
ec890a
+        return [ XprtSwitch(f) for f in sorted(xprt_switches.iterdir()) ]
ec890a
+
ec890a
+    def show(args):
ec890a
+        for switch in XprtSwitch.get_by_name(args.switch):
ec890a
+            print(switch)
ec890a
+
ec890a
+    def set_property(args):
ec890a
+        for switch in XprtSwitch.get_by_name(args.switch[0]):
ec890a
+            resolved = socket.gethostbyname(args.newaddr[0])
ec890a
+            for xprt in switch.xprts:
ec890a
+                xprt.set_dstaddr(resolved)
ec890a
+        print(switch)
ec890a
+
ec890a
+
ec890a
+class RpcClient:
ec890a
+    def __init__(self, path):
ec890a
+        self.path = path
ec890a
+        self.name = path.stem
ec890a
+        self.switch = XprtSwitch(path / (path / "switch").readlink(), sep=",")
ec890a
+
ec890a
+    def __lt__(self, rhs):
ec890a
+        return self.name < rhs.name
ec890a
+
ec890a
+    def __str__(self):
ec890a
+        return f"{self.name}: {self.switch}"
ec890a
+
ec890a
+    def add_command(subparser):
ec890a
+        parser = subparser.add_parser("client", help="Commands for rpc clients")
ec890a
+        parser.set_defaults(func=RpcClient.show, client=None)
ec890a
+        subparser = parser.add_subparsers()
ec890a
+
ec890a
+        show = subparser.add_parser("show", help="Show rpc clients")
ec890a
+        show.add_argument("client", metavar="CLIENT", nargs='?',
ec890a
+                          help="Name of a specific rpc client to show")
ec890a
+        parser.set_defaults(func=RpcClient.show)
ec890a
+
ec890a
+    def get_by_name(name):
ec890a
+        rpc_clients = sunrpc / "rpc-clients"
ec890a
+        if name:
ec890a
+            return [ RpcClient(rpc_clients / name) ]
ec890a
+        return [ RpcClient(f) for f in sorted(rpc_clients.iterdir()) ]
ec890a
+
ec890a
+    def show(args):
ec890a
+        for client in RpcClient.get_by_name(args.client):
ec890a
+            print(client)
ec890a
+
ec890a
+
ec890a
+parser = argparse.ArgumentParser()
ec890a
+
ec890a
+def show_small_help(args):
ec890a
+    parser.print_usage()
ec890a
+    print("sunrpc dir:", sunrpc)
ec890a
+parser.set_defaults(func=show_small_help)
ec890a
+
ec890a
+subparser = parser.add_subparsers(title="commands")
ec890a
+RpcClient.add_command(subparser)
ec890a
+XprtSwitch.add_command(subparser)
ec890a
+Xprt.add_command(subparser)
ec890a
+
ec890a
+args = parser.parse_args()
ec890a
+try:
ec890a
+    args.func(args)
ec890a
+except Exception as e:
ec890a
+    print(str(e))
ec890a
+    sys.exit(1)