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