Commit 8b98cdb6 authored by Darek Stojaczyk's avatar Darek Stojaczyk Committed by Tomasz Zawadzki
Browse files

scripts/rpc.py: add daemon mode



Add rpc_cmd() bash command that sends rpc command to an
rpc.py instance permanently running in background.
This makes sending RPC commands even 17 times faster.

We make use of bash coprocesses - a builtin bash feature
that allow starting background processes with stdin and
stdout connected to pipes. rpc.py will block trying to
read stdin, effectively being always "ready" to read
an RPC command.

The background rpc.py is started with a new --server flag
that's described as:

> Start listening on stdin, parse each line as a regular
> rpc.py execution and create a separate connection for each command.
> Each command's output ends with either **STATUS=0 if the
> command succeeded or **STATUS=1 if it failed.
> --server is meant to be used in conjunction with bash
> coproc, where stdin and stdout are named pipes and can be
> used as a faster way to send RPC commands.

As a part of this patch I'm attaching a sample test
that runs the following rpc commands first with the regular
rpc.py, then the new rpc_cmd() function.

```
time {
        bdevs=$($rpc bdev_get_bdevs)
        [ "$(jq length <<< "$bdevs")" == "0" ]

        malloc=$($rpc bdev_malloc_create 8 512)
        bdevs=$($rpc bdev_get_bdevs)
        [ "$(jq length <<< "$bdevs")" == "1" ]

        $rpc bdev_passthru_create -b "$malloc" -p Passthru0
        bdevs=$($rpc bdev_get_bdevs)
        [ "$(jq length <<< "$bdevs")" == "2" ]

        $rpc bdev_passthru_delete Passthru0
        $rpc bdev_malloc_delete $malloc
        bdevs=$($rpc bdev_get_bdevs)
        [ "$(jq length <<< "$bdevs")" == "0" ]
}
```

Regular rpc.py:
```
real    0m1.477s
user    0m1.289s
sys     0m0.139s
```

rpc_cmd():
```
real    0m0.085s
user    0m0.025s
sys     0m0.006s
```

autotest_common.sh will now spawn an rpc.py daemon if
it's not running yet, and it will offer rpc_cmd() function
to quickly send RPC commands. If the command is invalid or
SPDK returns with error, the bash function will return
a non-zero code and may trigger ERR trap just like a regular
rpc.py instance.

Pipes have major advantage over e.g. unix domain sockets - the pipes
will be automatically closed once the owner process exits.
This means we can create a named pipe in autotest_common.sh,
open it, then start rpc.py in background and never worry
about it again - it will be closed automatically once the
test exits. It doesn't even matter if the test is executed
manually in isolation, or as a part of the entire autotest.

(check_so_deps.sh needs to be modified not to wait for *all*
background processes to finish, but just the ones it started)

Change-Id: If0ded961b7fef3af3837b44532300dee8b5b4663
Signed-off-by: default avatarDarek Stojaczyk <dariusz.stojaczyk@intel.com>
Signed-off-by: default avatarMichal Berger <michalx.berger@intel.com>
Signed-off-by: default avatarPawel Kaminski <pawelx.kaminski@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/621


Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: default avatarBen Walker <benjamin.walker@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
parent aba98412
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -152,6 +152,7 @@ if [ $SPDK_RUN_FUNCTIONAL_TEST -eq 1 ]; then
	timing_enter lib

	run_test "env" test/env/env.sh
	run_test "rpc" test/rpc/rpc.sh
	run_test "rpc_client" test/rpc_client/rpc_client.sh
	run_test "json_config" ./test/json_config/json_config.sh
	run_test "alias_rpc" test/json_config/alias_rpc/alias_rpc.sh
+28 −1
Original line number Diff line number Diff line
@@ -37,6 +37,14 @@ if __name__ == "__main__":
                        help="""Set verbose level. """)
    parser.add_argument('--dry_run', dest='dry_run', action='store_true', help="Display request and exit")
    parser.set_defaults(dry_run=False)
    parser.add_argument('--server', dest='is_server', action='store_true',
                        help="Start listening on stdin, parse each line as a regular rpc.py execution and create \
                                a separate connection for each command. Each command's output ends with either \
                                **STATUS=0 if the command succeeded or **STATUS=1 if it failed. --server is meant \
                                to be used in conjunction with bash coproc, where stdin and stdout are connected to \
                                pipes and can be used as a faster way to send RPC commands. If enabled, rpc.py \
                                must be executed without any other parameters.")
    parser.set_defaults(is_server=False)
    subparsers = parser.add_subparsers(help='RPC methods', dest='called_rpc_name')

    def framework_start_init(args):
@@ -2374,7 +2382,26 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse
                exit(1)

    args = parser.parse_args()
    if args.dry_run:
    if args.is_server:
        for input in sys.stdin:
            cmd = shlex.split(input)
            try:
                tmp_args = parser.parse_args(cmd)
            except SystemExit as ex:
                print("**STATUS=1", flush=True)
                continue

            try:
                tmp_args.client = rpc.client.JSONRPCClient(
                    tmp_args.server_addr, tmp_args.port, tmp_args.timeout,
                    log_level=getattr(logging, tmp_args.verbose.upper()))
                call_rpc_func(tmp_args)
                print("**STATUS=0", flush=True)
            except JSONRPCException as ex:
                print(ex.message)
                print("**STATUS=1", flush=True)
        exit(0)
    elif args.dry_run:
        args.client = dry_run_client()
        print_dict = null_print
        print_json = null_print
+25 −0
Original line number Diff line number Diff line
@@ -300,6 +300,31 @@ for i in "$@"; do
	esac
done

# start rpc.py coprocess if it's not started yet
if [[ -z $RPC_PIPE_PID ]] || ! kill -0 "$RPC_PIPE_PID" &>/dev/null; then
	coproc RPC_PIPE { "$rootdir/scripts/rpc.py" --server; }
	exec {RPC_PIPE_OUTPUT}<&${RPC_PIPE[0]} {RPC_PIPE_INPUT}>&${RPC_PIPE[1]}
	# all descriptors will automatically close together with this bash
	# process, this will make rpc.py stop reading and exit gracefully
fi

function rpc_cmd() {
	xtrace_disable
	local rsp rc

	echo "$@" >&$RPC_PIPE_INPUT
	while read -t 5 -ru $RPC_PIPE_OUTPUT rsp; do
		if [[ $rsp == "**STATUS="* ]]; then
			break
		fi
		echo "$rsp"
	done

	rc=${rsp#*=}
	xtrace_restore
	[[ $rc == 0 ]]
}

function timing() {
	direction="$1"
	testname="$2"
+1 −5
Original line number Diff line number Diff line
@@ -123,11 +123,7 @@ fail_file=$output_dir/check_so_deps_fail

rm -f $fail_file

for lib in $SPDK_LIBS; do
	confirm_deps $lib&
done

wait
( for lib in $SPDK_LIBS; do confirm_deps $lib & done; wait )

$MAKE $MAKEFLAGS clean
git checkout "$rootdir/mk/spdk.lib.mk"

test/rpc/rpc.sh

0 → 100755
+41 −0
Original line number Diff line number Diff line
#!/usr/bin/env bash

testdir=$(readlink -f $(dirname $0))
rootdir=$(readlink -f $testdir/../..)
source $rootdir/test/common/autotest_common.sh

# simply check if rpc commands have any effect on spdk
function rpc_integrity() {
	time {
		bdevs=$($rpc bdev_get_bdevs)
		[ "$(jq length <<< "$bdevs")" == "0" ]

		malloc=$($rpc bdev_malloc_create 8 512)
		bdevs=$($rpc bdev_get_bdevs)
		[ "$(jq length <<< "$bdevs")" == "1" ]

		$rpc bdev_passthru_create -b "$malloc" -p Passthru0
		bdevs=$($rpc bdev_get_bdevs)
		[ "$(jq length <<< "$bdevs")" == "2" ]

		$rpc bdev_passthru_delete Passthru0
		$rpc bdev_malloc_delete $malloc
		bdevs=$($rpc bdev_get_bdevs)
		[ "$(jq length <<< "$bdevs")" == "0" ]
	}
}

$rootdir/app/spdk_tgt/spdk_tgt &
spdk_pid=$!
trap 'killprocess $spdk_pid; exit 1' SIGINT SIGTERM EXIT
waitforlisten $spdk_pid

# basic integrity test
rpc="$rootdir/scripts/rpc.py"
run_test "rpc_integrity" rpc_integrity
# same test, but with rpc_cmd() instead
rpc="rpc_cmd"
run_test "rpc_daemon_integrity" rpc_integrity

trap - SIGINT SIGTERM EXIT
killprocess $spdk_pid