2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-05 08:45:23 +00:00

utilities: Add bash command-line completion script.

This patch adds bash command-line completion script for ovs-appctl,
ovs-dpctl, ovs-ofctl and ovsdb-tool command.  Right now, the script
can do the following:

- display available completion or complete on unfinished user input
  (long option, subcommand, and argument).

- once the subcommand (e.g. ofproto/trace) has been given, the
  script will print the subcommand format.

- the script can convert between keywords like 'bridge/port/interface/dp'
  and the available record in ovsdb.

The limitations are:

- only support small set of important keywords
  (dp, datapath, bridge, switch, port, interface, iface).

- does not support parsing of nested options
  (e.g. ovsdb-tool create [db [schema]]).

- does not support expansion on repeatitive argument
  (e.g. ovs-dpctl show [dp...]).

- only support matching on long options, and only in the format
  (--option [arg], i.e. should not use --option=[arg]).

To use the script, either copy it inside /etc/bash_completion.d/
or manually run it via . ovs-command-compgen.bash.

Also, a unit testsuite is provided as ovs-command-compgen-test.bash.
It is suggested that this test script be run only inside
tutorial/sandbox directory.

For more info please refer to utilities/ovs-command-compgen.INSTALL.md.

Signed-off-by: Alex Wang <alexw@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
This commit is contained in:
Alex Wang
2014-10-19 21:36:58 -07:00
parent 03ec3f1296
commit 423ede182b
5 changed files with 1365 additions and 0 deletions

3
NEWS
View File

@@ -1,5 +1,8 @@
Post-v2.3.0
---------------------
- Add bash command-line completion support for ovs-appctl/ovs-dpctl/
ovs-ofctl/ovsdb-tool commands. Please check
utilities/ovs-command-compgen.INSTALL.md for how to use.
- The "learn" action supports a new flag "delete_learned" that causes
the learned flows to be deleted when the flow with the "learn" action
is deleted.

View File

@@ -26,6 +26,9 @@ utilities/ovs-lib: $(top_builddir)/config.status
EXTRA_DIST += \
utilities/ovs-check-dead-ifs.in \
utilities/ovs-command-compgen.bash \
utilities/ovs-command-compgen-test.bash \
utilities/ovs-command-compgen.INSTALL.md \
utilities/ovs-ctl.in \
utilities/ovs-dev.py \
utilities/ovs-docker \

View File

@@ -0,0 +1,688 @@
#!/bin/bash
#
# Tests for the ovs-command-compgen.bash
#
# Please run this with ovs-command-compgen.bash script inside
# ovs-sandbox, under the same directory.
#
# For information about running the ovs-sandbox, please refer to
# the tutorial directory.
#
#
#
COMP_OUTPUT=
TMP=
EXPECT=
TEST_RESULT=
TEST_COUNTER=0
TEST_COMMANDS=(ovs-appctl ovs-ofctl ovs-dpctl ovsdb-tool)
TEST_APPCTL_TARGETS=(ovs-vswitchd ovsdb-server ovs-ofctl)
#
# Helper functions.
#
get_command_format() {
local input="$@"
echo "$(grep -A 1 "Command format" <<< "$input" | tail -n+2)"
}
get_argument_expansion() {
local input="$@"
echo "$(grep -- "available completions for keyword" <<< "$input" | sed -e 's/^[ \t]*//')"
}
get_available_completions() {
local input="$@"
echo "$(sed -e '1,/Available/d' <<< "$input" | tail -n+2)"
}
generate_expect_completions() {
local keyword="$1"
local completions="$2"
echo "available completions for keyword \"$keyword\": $completions" \
| sed -e 's/[ \t]*$//'
}
reset_globals() {
COMP_OUTPUT=
TMP=
EXPECT=
TEST_RESULT=
}
#
# $1: Test name.
# $2: ok or fail.
#
print_result() {
(( TEST_COUNTER++ ))
printf "%2d: %-70s %s\n" "$TEST_COUNTER" "$1" "$2"
}
#
# $1: test stage
# $2: actual
# $3: expect
#
print_error() {
local stage="$1"
local actual="$2"
local expect="$3"
printf "failed at stage_%s:\n" "$stage"
printf "actual output: %s\n" "$actual"
printf "expect output: %s\n" "$expect"
}
#
# Sub-tests.
#
ovs_apptcl_TAB() {
local target="$1"
local target_line=
local comp_output tmp expect
if [ -n "$target" ]; then
target_line="--target $target"
fi
comp_output="$(bash ovs-command-compgen.bash debug ovs-appctl $target_line TAB 2>&1)"
tmp="$(get_available_completions "$comp_output")"
expect="$(ovs-appctl --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1)
$(ovs-appctl $target_line list-commands | tail -n +2 | cut -c3- | cut -d ' ' -f1 | sort)"
if [ "$tmp" = "$expect" ]; then
echo "ok"
else
echo "fail"
fi
}
#
# Test preparation.
#
ovs-vsctl add-br br0
ovs-vsctl add-port br0 p1
#
# Begin the test.
#
cat <<EOF
## ------------------------------- ##
## ovs-command-compgen unit tests. ##
## ------------------------------- ##
EOF
# complete ovs-appctl [TAB]
# complete ovs-dpctl [TAB]
# complete ovs-ofctl [TAB]
# complete ovsdb-tool [TAB]
for test_command in ${TEST_COMMANDS[@]}; do
reset_globals
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ${test_command} TAB 2>&1)"
TMP="$(get_available_completions "$COMP_OUTPUT")"
EXPECT="$(${test_command} --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1)
$(${test_command} list-commands | tail -n +2 | cut -c3- | cut -d ' ' -f1 | sort)"
if [ "$TMP" = "$EXPECT" ]; then
TEST_RESULT=ok
else
TEST_RESULT=fail
fi
print_result "complete ${test_command} [TAB]" "$TEST_RESULT"
done
# complete ovs-appctl --tar[TAB]
reset_globals
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl --tar 2>&1)"
TMP="$(get_available_completions "$COMP_OUTPUT")"
EXPECT="--target"
if [ "$TMP" = "$EXPECT" ]; then
TEST_RESULT=ok
else
TEST_RESULT=fail
fi
print_result "complete ovs-appctl --targ[TAB]" "$TEST_RESULT"
# complete ovs-appctl --target [TAB]
reset_globals
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl --target TAB 2>&1)"
TMP="$(get_available_completions "$COMP_OUTPUT")"
EXPECT="$(echo ${TEST_APPCTL_TARGETS[@]} | tr ' ' '\n' | sort)"
if [ "$TMP" = "$EXPECT" ]; then
TEST_RESULT=ok
else
TEST_RESULT=fail
fi
print_result "complete ovs-appctl --target [TAB]" "$TEST_RESULT"
# complete ovs-appctl --target ovs-vswitchd [TAB]
# complete ovs-appctl --target ovsdb-server [TAB]
# complete ovs-appctl --target ovs-ofctl [TAB]
reset_globals
for target in ${TEST_APPCTL_TARGETS[@]}; do
target_field="--target $i "
if [ "$target" = "ovs-ofctl" ]; then
ovs-ofctl monitor br0 --detach --no-chdir --pidfile
fi
TEST_RESULT="$(ovs_apptcl_TAB $target)"
print_result "complete ovs-appctl ${target_field}[TAB]" "$TEST_RESULT"
if [ "$target" = "ovs-ofctl" ]; then
ovs-appctl --target ovs-ofctl exit
fi
done
# check all subcommand formats
reset_globals
TMP="$(ovs-appctl list-commands | tail -n +2 | cut -c3- | cut -d ' ' -f1 | sort)"
# for each subcmd, check the print of subcmd format
for i in $TMP; do
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl $i TAB 2>&1)"
tmp="$(get_command_format "$COMP_OUTPUT")"
EXPECT="$(ovs-appctl list-commands | tail -n+2 | cut -c3- | grep -- "^$i " | tr -s ' ' | sort)"
if [ "$tmp" = "$EXPECT" ]; then
TEST_RESULT=ok
else
TEST_RESULT=fail
break
fi
done
print_result "check all subcommand format" "$TEST_RESULT"
# complex completion check - bfd/set-forwarding
# bfd/set-forwarding [interface] normal|false|true
# test expansion of 'interface'
reset_globals
for i in loop_once; do
# check the top level completion.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bfd/set-forwarding TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "normal" "")
$(generate_expect_completions "false" "")
$(generate_expect_completions "true" "")
$(generate_expect_completions "interface" "p1")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT="p1"
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to 'true', there should be no more completions.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bfd/set-forwarding true TAB 2>&1)"
TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")"
EXPECT="Command format:
bfd/set-forwarding [interface] normal|false|true"
if [ "$TMP" != "$EXPECT" ]; then
print_error "3" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to 'p1', there should still be the completion for booleans.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bfd/set-forwarding p1 TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "normal" "")
$(generate_expect_completions "false" "")
$(generate_expect_completions "true" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "4" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "5" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to 'p1 false', there should still no more completions.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bfd/set-forwarding p1 false TAB 2>&1)"
TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")"
EXPECT="Command format:
bfd/set-forwarding [interface] normal|false|true"
if [ "$TMP" != "$EXPECT" ]; then
print_error "6" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "complex completion check - bfd/set-forwarding" "$TEST_RESULT"
# complex completion check - lacp/show
# lacp/show [port]
# test expansion on 'port'
reset_globals
for i in loop_once; do
# check the top level completion.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl lacp/show TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "port" "br0 p1")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT="br0 p1"
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to 'p1', there should be no more completions.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl lacp/show p1 TAB 2>&1)"
TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")"
EXPECT="Command format:
lacp/show [port]"
if [ "$TMP" != "$EXPECT" ]; then
print_error "3" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "complex completion check - lacp/show" "$TEST_RESULT"
# complex completion check - ofproto/trace
# ofproto/trace {[dp_name] odp_flow | bridge br_flow} [-generate|packet]
# test expansion on 'dp|dp_name' and 'bridge'
reset_globals
for i in loop_once; do
# check the top level completion.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "bridge" "br0")
$(generate_expect_completions "odp_flow" "")
$(generate_expect_completions "dp_name" "ovs-system")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT="br0 ovs-system"
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to 'ovs-system', should go to the dp-name path.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace ovs-system TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "odp_flow" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "3" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "4" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set odp_flow to some random string, should go to the next level.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace ovs-system "in_port(123),mac(),ip,tcp" TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "-generate" "-generate")
$(generate_expect_completions "packet" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "5" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT="-generate"
if [ "$TMP" != "$EXPECT" ]; then
print_error "6" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set packet to some random string, there should be no more completions.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace ovs-system "in_port(123),mac(),ip,tcp" "ABSJDFLSDJFOIWEQR" TAB 2>&1)"
TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")"
EXPECT="Command format:
ofproto/trace {[dp_name] odp_flow | bridge br_flow} [-generate|packet]"
if [ "$TMP" != "$EXPECT" ]; then
print_error "7" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to 'br0', should go to the bridge path.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace br0 TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "br_flow" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "8" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "9" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to some random string, should go to the odp_flow path.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ofproto/trace "in_port(123),mac(),ip,tcp" TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "-generate" "-generate")
$(generate_expect_completions "packet" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "10" "$TMP" "$EXPEC"T
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT="-generate"
if [ "$TMP" != "$EXPECT" ]; then
print_error "11" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "complex completion check - ofproto/trace" "$TEST_RESULT"
# complex completion check - vlog/set
# vlog/set {spec | PATTERN:facility:pattern}
# test non expandable arguments
reset_globals
for i in loop_once; do
# check the top level completion.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl vlog/set TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "PATTERN:facility:pattern" "")
$(generate_expect_completions "spec" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# set argument to random 'abcd', there should be no more completions.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl vlog/set abcd TAB 2>&1)"
TMP="$(sed -e '/./,$!d' <<< "$COMP_OUTPUT")"
EXPECT="Command format:
vlog/set {spec | PATTERN:facility:pattern}"
if [ "$TMP" != "$EXPECT" ]; then
print_error "3" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "complex completion check - vlog/set" "$TEST_RESULT"
# complete after delete port
reset_globals
ovs-vsctl del-port p1
for i in loop_once; do
# check match on interface, there should be no available interface expansion.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bfd/set-forwarding TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "normal" "")
$(generate_expect_completions "false" "")
$(generate_expect_completions "true" "")
$(generate_expect_completions "interface" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check match on port, there should be no p1 as port.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl lacp/show TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "port" "br0")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "3" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT="br0"
if [ "$TMP" != "$EXPECT" ]; then
print_error "4" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "complete after delete port" "$TEST_RESULT"
# complete after delete bridge
reset_globals
ovs-vsctl del-br br0
for i in loop_once; do
# check match on port, there should be no p1 as port.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl bridge/dump-flows TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT="$(generate_expect_completions "bridge" "")"
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check the available completions.
TMP="$(get_available_completions "$COMP_OUTPUT" | tr '\n' ' ' | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
# check 'ovs-ofctl monitor [misslen] [invalid_ttl] [watch:[...]]', should
# not show any available completion.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-ofctl monitor non_exist_br TAB 2>&1)"
TMP="$(get_argument_expansion "$COMP_OUTPUT" | sed -e 's/[ \t]*$//')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "3" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "complete after delete bridge" "$TEST_RESULT"
# negative test - incorrect subcommand
reset_globals
for i in loop_once; do
# incorrect subcommand
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ERROR 2>&1)"
TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./,$!d')"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
print_error "1" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl ERROR TAB 2>&1)"
TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./!d')"
EXPECT="Command format:"
if [ "$TMP" != "$EXPECT" ]; then
print_error "2" "$TMP" "$EXPECT"
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "negative test - incorrect subcommand" "$TEST_RESULT"
# negative test - no ovs-vswitchd
# negative test - no ovsdb-server
# negative test - no ovs-ofctl
# should not see any error.
reset_globals
killall ovs-vswitchd ovsdb-server
for i in ${TEST_APPCTL_TARGETS[@]}; do
for j in loop_once; do
reset_globals
daemon="$i"
# should show no avaiable subcommands.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl --target $daemon TAB 2>&1)"
TMP="$(get_available_completions "$COMP_OUTPUT")"
EXPECT="$(ovs-appctl --option | sort | sed -n '/^--.*/p' | cut -d '=' -f1)"
if [ "$TMP" != "$EXPECT" ]; then
TEST_RESULT=fail
break
fi
# should not match any input.
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovs-appctl --target $daemon ERROR SUBCMD TAB 2>&1)"
TMP="$(echo "$COMP_OUTPUT" | sed -e 's/[ \t]*$//' | sed -e '/./!d')"
EXPECT="Command format:"
if [ "$TMP" != "$EXPECT" ]; then
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "negative test - no $daemon" "$TEST_RESULT"
done
# negative test - do not match on nested option
reset_globals
for i in loop_once; do
COMP_OUTPUT="$(bash ovs-command-compgen.bash debug ovsdb-tool create TAB 2>&1)"
TMP="$(get_available_completions "$COMP_OUTPUT")"
EXPECT=
if [ "$TMP" != "$EXPECT" ]; then
TEST_RESULT=fail
break
fi
TEST_RESULT=ok
done
print_result "negative test - do not match on nested option" "$TEST_RESULT"

View File

@@ -0,0 +1,57 @@
Using bash command-line completion script
-----------------------------------------
ovs-command-compgen.bash adds bash command-line completion support
for ovs-appctl, ovs-dpctl, ovs-ofctl and ovsdb-tool commands.
Features:
---------
display available completion or complete on unfinished user input
(long option, subcommand, and argument).
once the subcommand (e.g. ofproto/trace) has been given, the
script will print the subcommand format.
the script can convert between keywords like 'bridge/port/interface/dp'
and the available record in ovsdb.
Limitations:
------------
only support small set of important keywords
(dp, datapath, bridge, switch, port, interface, iface).
does not support parsing of nested options
(e.g. ovsdb-tool create [db [schema]]).
does not support expansion on repeatitive argument
(e.g. ovs-dpctl show [dp...]).
only support matching on long options, and only in the format
(--option [arg], i.e. should not use --option=[arg]).
How to use:
-----------
To use the script, either copy it inside /etc/bash_completion.d/
or manually run it via . ovs-command-compgen.bash.
Test:
-----
An unit testsuite is provided as ovs-command-compgen-test.bash.
To run the test, first enter ovs sandbox via:
make sandbox && cd sandbox
Then copy both ovs-command-compgen-test.bash and ovs-command-compgen.bash
to the current directory. Finally, run the test via:
bash ovs-command-compgen-test.bash
Bug Reporting:
--------------
Please report problems to bugs@openvswitch.org.

View File

@@ -0,0 +1,614 @@
#!/bin/bash
#
# A bash command completion script for ovs-appctl.
#
#
# Right now, the script can do the following:
#
# - display available completion or complete on unfinished user input
# (long option, subcommand, and argument).
#
# - once the subcommand (e.g. ofproto/trace) has been given, the
# script will print the subcommand format.
#
# - the script can convert between keywords like 'bridge/port/interface/dp'
# and the available record in ovsdb.
#
# The limitation are:
#
# - only support small set of important keywords
# (dp, datapath, bridge, switch, port, interface, iface).
#
# - does not support parsing of nested option
# (e.g. ovsdb-tool create [db [schema]]).
#
# - does not support expansion on repeatitive argument
# (e.g. ovs-dpctl show [dp...]).
#
# - only support matching on long options, and only in the format
# (--option [arg], i.e. should not use --option=[arg]).
#
#
#
# Keywords
# ========
#
#
#
# Expandable keywords.
_KWORDS=(bridge switch port interface iface dp_name dp)
# Command name.
_COMMAND=
# Printf enabler.
_PRINTF_ENABLE=
# Bash prompt.
_BASH_PROMPT=
# Output to the compgen.
_COMP_WORDLIST=
#
# For ovs-appctl command only.
#
# Target in the current completion, default ovs-vswitchd.
_APPCTL_TARGET=
# Possible targets.
_POSSIBLE_TARGETS="ovs-vswitchd ovsdb-server ovs-ofctl"
# Command Extraction
# ==================
#
#
#
# Extracts all subcommands of 'command'.
# If fails, returns nothing.
extract_subcmds() {
local command=$_COMMAND
local target=
local subcmds error
if [ -n "$_APPCTL_TARGET" ]; then
target="--target $_APPCTL_TARGET"
fi
subcmds="$($command $target list-commands 2>/dev/null | tail -n +2 | cut -c3- \
| cut -d ' ' -f1)" || error="TRUE"
if [ -z "$error" ]; then
echo "$subcmds"
fi
}
# Extracts all long options of ovs-appctl.
# If fails, returns nothing.
extract_options() {
local command=$_COMMAND
local options error
options="$($command --option 2>/dev/null | sort | sed -n '/^--.*/p' | cut -d '=' -f1)" \
|| error="TRUE"
if [ -z "$error" ]; then
echo "$options"
fi
}
# Returns the option format, if the option asks for an argument.
# If fails, returns nothing.
option_require_arg() {
local command=$_COMMAND
local option=$1
local require_arg error
require_arg="$($command --option | sort | sed -n '/^--.*/p' | grep -- "$option" | grep -- "=")" \
|| error="TRUE"
if [ -z "$error" ]; then
echo "$require_arg"
fi
}
# Combination Discovery
# =====================
#
#
#
# Given the subcommand formats, finds all possible completions
# at current completion level.
find_possible_comps() {
local combs="$@"
local comps=
local line
while read line; do
local arg=
for arg in $line; do
# If it is an optional argument, gets all completions,
# and continues.
if [ -n "$(sed -n '/^\[.*\]$/p' <<< "$arg")" ]; then
local opt_arg="$(sed -e 's/^\[\(.*\)\]$/\1/' <<< "$arg")"
local opt_args=()
IFS='|' read -a opt_args <<< "$opt_arg"
comps="${opt_args[@]} $comps"
# If it is in format "\[*", it is a start of nested
# option, do not parse.
elif [ -n "$(sed -n "/^\[.*$/p" <<< "$arg")" ]; then
break;
# If it is a compulsory argument, adds it to the comps
# and break, since all following args are for next stage.
else
local args=()
IFS='|' read -a args <<< "$arg"
comps="${args[@]} $comps"
break;
fi
done
done <<< "$combs"
echo "$comps"
}
# Given the subcommand format, and the current command line input,
# finds all possible completions.
subcmd_find_comp_based_on_input() {
local format="$1"
local cmd_line=($2)
local mult=
local combs=
local comps=
local arg line
# finds all combinations by searching for '{}'.
# there should only be one '{}', otherwise, the
# command format should be changed to multiple commands.
mult="$(sed -n 's/^.*{\(.*\)}.*$/ \1/p' <<< "$format" | tr '|' '\n' | cut -c1-)"
if [ -n "$mult" ]; then
while read line; do
local tmp=
tmp="$(sed -e "s@{\(.*\)}@$line@" <<< "$format")"
combs="$combs@$tmp"
done <<< "$mult"
combs="$(tr '@' '\n' <<< "$combs")"
else
combs="$format"
fi
# Now, starts from the first argument, narrows down the
# subcommand format combinations.
for arg in "${subcmd_line[@]}"; do
local kword possible_comps
# Finds next level possible comps.
possible_comps=$(find_possible_comps "$combs")
# Finds the kword.
kword="$(arg_to_kwords "$arg" "$possible_comps")"
# Returns if could not find 'kword'
if [ -z "$kword" ]; then
return
fi
# Trims the 'combs', keeps context only after 'kword'.
if [ -n "$combs" ]; then
combs="$(sed -n "s@^.*\[\?$kword|\?[a-z_]*\]\? @@p" <<< "$combs")"
fi
done
comps="$(find_possible_comps "$combs")"
echo "$(kwords_to_args "$comps")"
}
# Helper
# ======
#
#
#
# Prints the input to stderr. $_PRINTF_ENABLE must be filled.
printf_stderr() {
local stderr_out="$@"
if [ -n "$_PRINTF_ENABLE" ]; then
printf "\n$stderr_out" 1>&2
fi
}
# Extracts the bash prompt PS1, outputs it with the input argument
# via 'printf_stderr'.
#
# Original idea inspired by:
# http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2
#
# The code below is taken from Peter Amidon. His change makes it more
# robust.
extract_bash_prompt() {
local myPS1 v
myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")"
v="$(bash --norc --noprofile -i 2>&1 <<< $'PS1=\"'"$myPS1"$'\" \n# Begin prompt\n# End prompt')"
v="${v##*# Begin prompt}"
_BASH_PROMPT="$(tail -n +2 <<< "${v%# End prompt*}" | sed 's/\\Begin prompt/Begin prompt/; s/\\End prompt/End prompt/')"
}
# Keyword Conversion
# ==================
#
#
#
# All completion functions.
complete_bridge () {
local result error
result=$(ovs-vsctl list-br 2>/dev/null | grep -- "^$1") || error="TRUE"
if [ -z "$error" ]; then
echo "${result}"
fi
}
complete_port () {
local ports result error
local all_ports
all_ports=$(ovs-vsctl --format=table \
--no-headings \
--columns=name \
list Port 2>/dev/null) || error="TRUE"
ports=$(printf "$all_ports" | sort | tr -d '"' | uniq -u)
result=$(grep -- "^$1" <<< "$ports")
if [ -z "$error" ]; then
echo "${result}"
fi
}
complete_iface () {
local bridge bridges result error
bridges=$(ovs-vsctl list-br 2>/dev/null) || error="TRUE"
for bridge in $bridges; do
local ifaces
ifaces=$(ovs-vsctl list-ifaces "${bridge}" 2>/dev/null) || error="TRUE"
result="${result} ${ifaces}"
done
if [ -z "$error" ]; then
echo "${result}"
fi
}
complete_dp () {
local dps result error
dps=$(ovs-appctl dpctl/dump-dps 2>/dev/null | cut -d '@' -f2) || error="TRUE"
result=$(grep -- "^$1" <<< "$dps")
if [ -z "$error" ]; then
echo "${result}"
fi
}
# Converts the argument (e.g. bridge/port/interface/dp name) to
# the corresponding keywords.
# Returns empty string if could not map the arg to any keyword.
arg_to_kwords() {
local arg="$1"
local possible_kwords=($2)
local non_parsables=()
local match=
local kword
for kword in ${possible_kwords[@]}; do
case "$kword" in
bridge|switch)
match="$(complete_bridge "$arg")"
;;
port)
match="$(complete_port "$arg")"
;;
interface|iface)
match="$(complete_iface "$arg")"
;;
dp_name|dp)
match="$(complete_dp "$arg")"
;;
*)
if [ "$arg" = "$kword" ]; then
match="$kword"
else
non_parsables+=("$kword")
continue
fi
;;
esac
if [ -n "$match" ]; then
echo "$kword"
return
fi
done
# If there is only one non-parsable kword,
# just assumes the user input it.
if [ "${#non_parsables[@]}" -eq "1" ]; then
echo "$non_parsables"
return
fi
}
# Expands the keywords to the corresponding instance names.
kwords_to_args() {
local possible_kwords=($@)
local args=()
local kword
for kword in ${possible_kwords[@]}; do
local match=
case "${kword}" in
bridge|switch)
match="$(complete_bridge "")"
;;
port)
match="$(complete_port "")"
;;
interface|iface)
match="$(complete_iface "")"
;;
dp_name|dp)
match="$(complete_dp "")"
;;
-*)
# Treats option as kword as well.
match="$kword"
;;
*)
match=
;;
esac
match=$(echo "$match" | tr '\n' ' ' | sed -e 's/^[ \t]*//')
args+=( $match )
if [ -n "$_PRINTF_ENABLE" ]; then
local output_stderr=
if [ -z "$printf_expand_once" ]; then
printf_expand_once="once"
printf -v output_stderr "\nArgument expansion:\n"
fi
printf -v output_stderr "$output_stderr available completions \
for keyword \"%s\": %s " "$kword" "$match"
printf_stderr "$output_stderr"
fi
done
echo "${args[@]}"
}
# Parse and Compgen
# =================
#
#
#
# This function takes the current command line arguments as input,
# finds the command format and returns the possible completions.
parse_and_compgen() {
local command=$_COMMAND
local subcmd_line=($@)
local subcmd=${subcmd_line[0]}
local target=
local subcmd_format=
local comp_wordlist=
if [ -n "$_APPCTL_TARGET" ]; then
target="--target $_APPCTL_TARGET"
fi
# Extracts the subcommand format.
subcmd_format="$($command $target list-commands 2>/dev/null | tail -n +2 | cut -c3- \
| awk -v opt=$subcmd '$1 == opt {print $0}' | tr -s ' ' )"
# Prints subcommand format.
printf_stderr "$(printf "\nCommand format:\n%s" "$subcmd_format")"
# Finds the possible completions based on input argument.
comp_wordlist="$(subcmd_find_comp_based_on_input "$subcmd_format" \
"${subcmd_line[@]}")"
echo "$comp_wordlist"
}
# Compgen Helper
# ==============
#
#
#
# Takes the current command line arguments and returns the possible
# completions.
#
# At the beginning, the options are checked and completed. For ovs-appctl
# completion, The function looks for the --target option which gives the
# target daemon name. If it is not provided, by default, 'ovs-vswitchd'
# is used.
#
# Then, tries to locate and complete the subcommand. If the subcommand
# is provided, the following arguments are passed to the 'parse_and_compgen'
# function to figure out the corresponding completion of the subcommand.
#
# Returns the completion arguments on success.
ovs_comp_helper() {
local cmd_line_so_far=($@)
local comp_wordlist _subcmd options i
local j=-1
# Parse the command-line args till we find the subcommand.
for i in "${!cmd_line_so_far[@]}"; do
# if $i is not greater than $j, it means the previous iteration
# skips not-visited args. so, do nothing and catch up.
if [ $i -le $j ]; then continue; fi
j=$i
if [[ "${cmd_line_so_far[i]}" =~ ^--* ]]; then
# If --target is found, locate the target daemon.
# Else, it is an option command, fill the comp_wordlist with
# all options.
if [ "$_COMMAND" = "ovs-appctl" ] \
&& [[ "${cmd_line_so_far[i]}" =~ ^--target$ ]]; then
_APPCTL_TARGET="ovs-vswitchd"
if [ -n "${cmd_line_so_far[j+1]}" ]; then
local daemon
for daemon in $_POSSIBLE_TARGETS; do
# Greps "$daemon" in argument, since the argument may
# be the path to the pid file.
if [ "$daemon" = "${cmd_line_so_far[j+1]}" ]; then
_APPCTL_TARGET="$daemon"
((j++))
break
fi
done
continue
else
comp_wordlist="$_POSSIBLE_TARGETS"
break
fi
else
options="$(extract_options $_COMMAND)"
# See if we could find the exact option.
if [ "${cmd_line_so_far[i]}" = "$(grep -- "${cmd_line_so_far[i]}" <<< "$options")" ]; then
# If an argument is required and next argument is non-empty,
# skip it. Else, return directly.
if [ -n "$(option_require_arg "${cmd_line_so_far[i]}")" ]; then
((j++))
if [ -z "${cmd_line_so_far[j]}" ]; then
printf_stderr "\nOption requires an arugment."
return
fi
fi
continue
# Else, need to keep completing on option.
else
comp_wordlist="$options"
break
fi
fi
fi
# Takes the first non-option argument as subcmd.
_subcmd="${cmd_line_so_far[i]}"
break
done
if [ -z "$comp_wordlist" ]; then
# If the subcommand is not found, provides all subcmds and options.
if [ -z "$_subcmd" ]; then
comp_wordlist="$(extract_subcmds) $(extract_options)"
# Else parses the current arguments and finds the possible completions.
else
# $j stores the index of the subcmd in cmd_line_so_far.
comp_wordlist="$(parse_and_compgen "${cmd_line_so_far[@]:$j}")"
fi
fi
echo "$comp_wordlist"
}
# Compgen
# =======
#
#
#
# The compgen function.
_ovs_command_complete() {
local cur prev
_COMMAND=${COMP_WORDS} # element 0 is the command.
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
# Do not print anything at first [TAB] execution.
if [ "$COMP_TYPE" -eq "9" ]; then
_PRINTF_ENABLE=
else
_PRINTF_ENABLE="enabled"
fi
# Extracts bash prompt PS1.
if [ "$1" != "debug" ]; then
extract_bash_prompt
fi
# Invokes the helper function to get all available completions.
# Always not input the 'COMP_WORD' at 'COMP_CWORD', since it is
# the one to be completed.
_COMP_WORDLIST="$(ovs_comp_helper \
${COMP_WORDS[@]:1:COMP_CWORD-1})"
# This is a hack to prevent autocompleting when there is only one
# available completion and printf disabled.
if [ -z "$_PRINTF_ENABLE" ] && [ -n "$_COMP_WORDLIST" ]; then
_COMP_WORDLIST="$_COMP_WORDLIST none void no-op"
fi
# Prints all available completions to stderr. If there is only one matched
# completion, do nothing.
if [ -n "$_PRINTF_ENABLE" ] \
&& [ -n "$(echo $_COMP_WORDLIST | tr ' ' '\n' | \
grep -- "^$cur")" ]; then
printf_stderr "\nAvailable completions:\n"
fi
# If there is no match between '$cur' and the '$_COMP_WORDLIST'
# prints a bash prompt since the 'complete' will not print it.
if [ -n "$_PRINTF_ENABLE" ] \
&& [ -z "$(echo $_COMP_WORDLIST | tr ' ' '\n' | grep -- "^$cur")" ] \
&& [ "$1" != "debug" ]; then
printf_stderr "\n$_BASH_PROMPT${COMP_WORDS[@]}"
fi
if [ "$1" = "debug" ]; then
printf_stderr "$(echo $_COMP_WORDLIST | tr ' ' '\n' | sort -u | grep -- "$cur")\n"
else
COMPREPLY=( $(compgen -W "$(echo $_COMP_WORDLIST | tr ' ' '\n' \
| sort -u)" -- $cur) )
fi
return 0
}
# Needed for the sorting of completions in display.
export LC_ALL=C
# Debug mode.
if [ "$1" = "debug" ]; then
shift
COMP_TYPE=0
COMP_WORDS=($@)
COMP_CWORD="$(expr $# - 1)"
# If the last argument is TAB, it means that the previous
# argument is already complete and script should complete
# next argument which is not input yet. This hack is for
# compromising the fact that bash cannot take unquoted
# empty argument.
if [ "${COMP_WORDS[-1]}" = "TAB" ]; then
COMP_WORDS[${#COMP_WORDS[@]}-1]=""
fi
_ovs_command_complete "debug"
# Normal compgen mode.
else
complete -F _ovs_command_complete ovs-appctl
complete -F _ovs_command_complete ovs-ofctl
complete -F _ovs_command_complete ovs-dpctl
complete -F _ovs_command_complete ovsdb-tool
fi