fa1456a38d
Change-Id: Ia140c4750f06870c40b7058c4afb2e20ca633a49 Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
746 lines
17 KiB
Lua
746 lines
17 KiB
Lua
--[[
|
|
/*
|
|
* Copyright (c) 2016 Cisco and/or its affiliates.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
]]
|
|
|
|
-- Experimental prototype CLI using API to VPP, with tab completion
|
|
--
|
|
-- Written by Andrew Yourtchenko (ayourtch@cisco.com) 2010,2016
|
|
--
|
|
|
|
vpp = require "vpp-lapi"
|
|
|
|
|
|
local dotdotdot = "..."
|
|
|
|
-- First the "readline" routine
|
|
|
|
readln = {
|
|
split = function(str, pat)
|
|
local t = {} -- NOTE: use {n = 0} in Lua-5.0
|
|
local fpat = "(.-)" .. pat
|
|
local last_end = 1
|
|
if str then
|
|
local s, e, cap = str:find(fpat, 1)
|
|
while s do
|
|
if s ~= 1 or cap ~= "" then
|
|
table.insert(t,cap)
|
|
end
|
|
last_end = e+1
|
|
s, e, cap = str:find(fpat, last_end)
|
|
end
|
|
if last_end <= #str then
|
|
cap = str:sub(last_end)
|
|
table.insert(t, cap)
|
|
end
|
|
end
|
|
return t
|
|
end,
|
|
|
|
reader = function()
|
|
local rl = {}
|
|
|
|
rl.init = function()
|
|
os.execute("stty -icanon min 1 -echo")
|
|
rl.rawmode = true
|
|
end
|
|
|
|
rl.done = function()
|
|
os.execute("stty icanon echo")
|
|
rl.rawmode = false
|
|
end
|
|
|
|
rl.prompt = ">"
|
|
rl.history = { "" }
|
|
rl.history_index = 1
|
|
rl.history_length = 1
|
|
|
|
rl.hide_cmd = function()
|
|
local bs = string.char(8) .. " " .. string.char(8)
|
|
for i = 1, #rl.command do
|
|
io.stdout:write(bs)
|
|
end
|
|
end
|
|
|
|
rl.show_cmd = function()
|
|
if rl.command then
|
|
io.stdout:write(rl.command)
|
|
end
|
|
end
|
|
|
|
rl.store_history = function(cmd)
|
|
if cmd == "" then
|
|
return
|
|
end
|
|
rl.history[rl.history_length] = cmd
|
|
rl.history_length = rl.history_length + 1
|
|
rl.history_index = rl.history_length
|
|
rl.history[rl.history_length] = ""
|
|
end
|
|
|
|
rl.readln = function()
|
|
local done = false
|
|
local need_prompt = true
|
|
rl.command = ""
|
|
|
|
if not rl.rawmode then
|
|
rl.init()
|
|
end
|
|
|
|
while not done do
|
|
if need_prompt then
|
|
io.stdout:write(rl.prompt)
|
|
io.stdout:write(rl.command)
|
|
need_prompt = false
|
|
end
|
|
|
|
local ch = io.stdin:read(1)
|
|
if ch:byte(1) == 27 then
|
|
-- CONTROL
|
|
local ch2 = io.stdin:read(1)
|
|
-- arrows
|
|
if ch2:byte(1) == 91 then
|
|
local ch3 = io.stdin:read(1)
|
|
local b = ch3:byte(1)
|
|
if b == 65 then
|
|
ch = "UP"
|
|
elseif b == 66 then
|
|
ch = "DOWN"
|
|
elseif b == 67 then
|
|
ch = "RIGHT"
|
|
elseif b == 68 then
|
|
ch = "LEFT"
|
|
end
|
|
-- print("Byte: " .. ch3:byte(1))
|
|
-- if ch3:byte(1)
|
|
end
|
|
end
|
|
|
|
if ch == "?" then
|
|
io.stdout:write(ch)
|
|
io.stdout:write("\n")
|
|
if rl.help then
|
|
rl.help(rl)
|
|
end
|
|
need_prompt = true
|
|
elseif ch == "\t" then
|
|
if rl.tab_complete then
|
|
rl.tab_complete(rl)
|
|
end
|
|
io.stdout:write("\n")
|
|
need_prompt = true
|
|
elseif ch == "\n" then
|
|
io.stdout:write(ch)
|
|
done = true
|
|
elseif ch == "\004" then
|
|
io.stdout:write("\n")
|
|
rl.command = nil
|
|
done = true
|
|
elseif ch == string.char(127) then
|
|
if rl.command ~= "" then
|
|
io.stdout:write(string.char(8) .. " " .. string.char(8))
|
|
rl.command = string.sub(rl.command, 1, -2)
|
|
end
|
|
elseif #ch > 1 then
|
|
-- control char
|
|
if ch == "UP" then
|
|
rl.hide_cmd()
|
|
if rl.history_index == #rl.history then
|
|
rl.history[rl.history_index] = rl.command
|
|
end
|
|
if rl.history_index > 1 then
|
|
rl.history_index = rl.history_index - 1
|
|
rl.command = rl.history[rl.history_index]
|
|
end
|
|
rl.show_cmd()
|
|
elseif ch == "DOWN" then
|
|
rl.hide_cmd()
|
|
if rl.history_index < rl.history_length then
|
|
rl.history_index = rl.history_index + 1
|
|
rl.command = rl.history[rl.history_index]
|
|
end
|
|
rl.show_cmd()
|
|
end
|
|
else
|
|
io.stdout:write(ch)
|
|
rl.command = rl.command .. ch
|
|
end
|
|
end
|
|
if rl.command then
|
|
rl.store_history(rl.command)
|
|
end
|
|
return rl.command
|
|
end
|
|
return rl
|
|
end
|
|
|
|
}
|
|
|
|
--[[
|
|
|
|
r = reader()
|
|
|
|
local done = false
|
|
|
|
while not done do
|
|
local cmd = r.readln()
|
|
print("Command: " .. tostring(cmd))
|
|
if not cmd or cmd == "quit" then
|
|
done = true
|
|
end
|
|
end
|
|
|
|
r.done()
|
|
|
|
]]
|
|
|
|
--------- MDS show tech parser
|
|
|
|
local print_section = nil
|
|
local list_sections = false
|
|
|
|
local curr_section = "---"
|
|
local curr_parser = nil
|
|
|
|
-- by default operate in batch mode
|
|
local batch_mode = true
|
|
|
|
local db = {}
|
|
local device = {}
|
|
device.output = {}
|
|
local seen_section = {}
|
|
|
|
function start_collection(name)
|
|
device = {}
|
|
seen_section = {}
|
|
end
|
|
|
|
function print_error(errmsg)
|
|
print("@#$:" .. errmsg)
|
|
end
|
|
|
|
function keys(tbl)
|
|
local t = {}
|
|
for k, v in pairs(tbl) do
|
|
table.insert(t, k)
|
|
end
|
|
return t
|
|
end
|
|
|
|
function tset (parent, ...)
|
|
|
|
-- print ('set', ...)
|
|
|
|
local len = select ('#', ...)
|
|
local key, value = select (len-1, ...)
|
|
local cutpoint, cutkey
|
|
|
|
for i=1,len-2 do
|
|
|
|
local key = select (i, ...)
|
|
local child = parent[key]
|
|
|
|
if value == nil then
|
|
if child == nil then return
|
|
elseif next (child, next (child)) then cutpoint = nil cutkey = nil
|
|
elseif cutpoint == nil then cutpoint = parent cutkey = key end
|
|
|
|
elseif child == nil then child = {} parent[key] = child end
|
|
|
|
parent = child
|
|
end
|
|
|
|
if value == nil and cutpoint then cutpoint[cutkey] = nil
|
|
else parent[key] = value return value end
|
|
end
|
|
|
|
|
|
function tget (parent, ...)
|
|
local len = select ('#', ...)
|
|
for i=1,len do
|
|
parent = parent[select (i, ...)]
|
|
if parent == nil then break end
|
|
end
|
|
return parent
|
|
end
|
|
|
|
|
|
local pager_lines = 23
|
|
local pager_printed = 0
|
|
local pager_skipping = false
|
|
local pager_filter_pipe = nil
|
|
|
|
function pager_reset()
|
|
pager_printed = 0
|
|
pager_skipping = false
|
|
if pager_filter_pipe then
|
|
pager_filter_pipe:close()
|
|
pager_filter_pipe = nil
|
|
end
|
|
end
|
|
|
|
|
|
function print_more()
|
|
io.stdout:write(" --More-- ")
|
|
end
|
|
|
|
function print_nomore()
|
|
local bs = string.char(8)
|
|
local bs10 = bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs
|
|
io.stdout:write(bs10 .. " " .. bs10)
|
|
end
|
|
|
|
function print_line(txt)
|
|
if pager_filter_pipe then
|
|
pager_filter_pipe:write(txt .. "\n")
|
|
return
|
|
end
|
|
if pager_printed >= pager_lines then
|
|
print_more()
|
|
local ch = io.stdin:read(1)
|
|
if ch == " " then
|
|
pager_printed = 0
|
|
elseif ch == "\n" then
|
|
pager_printed = pager_printed - 1
|
|
elseif ch == "q" then
|
|
pager_printed = 0
|
|
pager_skipping = true
|
|
end
|
|
print_nomore()
|
|
end
|
|
if not pager_skipping then
|
|
print(txt)
|
|
pager_printed = pager_printed + 1
|
|
else
|
|
-- skip printing
|
|
end
|
|
end
|
|
|
|
function paged_write(text)
|
|
local t = readln.split(text, "[\n]")
|
|
if string.sub(text, -1) == "\n" then
|
|
table.insert(t, "")
|
|
end
|
|
for i, v in ipairs(t) do
|
|
if i < #t then
|
|
print_line(v)
|
|
else
|
|
if pager_filter_pipe then
|
|
pager_filter_pipe:write(v)
|
|
else
|
|
io.stdout:write(v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
function get_choices(tbl, key)
|
|
local res = {}
|
|
for k, v in pairs(tbl) do
|
|
if string.sub(k, 1, #key) == key then
|
|
table.insert(res, k)
|
|
elseif 0 < #key and dotdotdot == k then
|
|
table.insert(res, k)
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
function get_exact_choice(choices, val)
|
|
local exact_idx = nil
|
|
local substr_idx = nil
|
|
local substr_seen = false
|
|
|
|
if #choices == 1 then
|
|
if choices[1] == dotdotdot then
|
|
return 1
|
|
elseif string.sub(choices[1], 1, #val) == val then
|
|
return 1
|
|
else
|
|
return nil
|
|
end
|
|
else
|
|
for i, v in ipairs(choices) do
|
|
if v == val then
|
|
exact_idx = i
|
|
substr_seen = true
|
|
elseif choices[i] ~= dotdotdot and string.sub(choices[i], 1, #val) == val then
|
|
if substr_seen then
|
|
substr_idx = nil
|
|
else
|
|
substr_idx = i
|
|
substr_seen = true
|
|
end
|
|
elseif choices[i] == dotdotdot then
|
|
if substr_seen then
|
|
substr_idx = nil
|
|
else
|
|
substr_idx = i
|
|
substr_seen = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return exact_idx or substr_idx
|
|
end
|
|
|
|
function device_cli_help(rl)
|
|
local key = readln.split(rl.command, "[ ]+")
|
|
local tree = rl.tree
|
|
local keylen = #key
|
|
local fullcmd = ""
|
|
local error = false
|
|
local terse = true
|
|
|
|
if ((#rl.command >= 1) and (string.sub(rl.command, -1) == " ")) or (#rl.command == 0) then
|
|
table.insert(key, "")
|
|
terse = false
|
|
end
|
|
|
|
for i, v in ipairs(key) do
|
|
local choices = get_choices(tree, v)
|
|
local idx = get_exact_choice(choices, v)
|
|
if idx then
|
|
local choice = choices[idx]
|
|
tree = tree[choice]
|
|
fullcmd = fullcmd .. choice .. " "
|
|
else
|
|
if i < #key then
|
|
error = true
|
|
end
|
|
end
|
|
|
|
if i == #key and not error then
|
|
for j, w in ipairs(choices) do
|
|
if terse then
|
|
paged_write(w .. "\t")
|
|
else
|
|
paged_write(" " .. w .. "\n")
|
|
end
|
|
end
|
|
paged_write("\n")
|
|
if terse then
|
|
paged_write(" \n")
|
|
end
|
|
end
|
|
end
|
|
pager_reset()
|
|
end
|
|
|
|
function device_cli_tab_complete(rl)
|
|
local key = readln.split(rl.command, "[ ]+")
|
|
local tree = rl.tree
|
|
local keylen = #key
|
|
local fullcmd = ""
|
|
local error = false
|
|
|
|
for i, v in ipairs(key) do
|
|
local choices = get_choices(tree, v)
|
|
local idx = get_exact_choice(choices, v)
|
|
if idx and choices[idx] ~= dotdotdot then
|
|
local choice = choices[idx]
|
|
tree = tree[choice]
|
|
-- print("level " .. i .. " '" .. choice .. "'")
|
|
fullcmd = fullcmd .. choice .. " "
|
|
else
|
|
-- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
|
|
error = true
|
|
end
|
|
end
|
|
if not error then
|
|
rl.command = fullcmd
|
|
else
|
|
-- print("\n\nerror\n")
|
|
end
|
|
pager_reset()
|
|
end
|
|
|
|
function device_cli_exec(rl)
|
|
|
|
local cmd_nopipe = rl.command
|
|
local cmd_pipe = nil
|
|
|
|
local pipe1, pipe2 = string.find(rl.command, "[|]")
|
|
if pipe1 then
|
|
cmd_nopipe = string.sub(rl.command, 1, pipe1-1)
|
|
cmd_pipe = string.sub(rl.command, pipe2+1, -1)
|
|
end
|
|
|
|
local key = readln.split(cmd_nopipe .. " <cr>", "[ ]+")
|
|
local tree = rl.tree
|
|
local keylen = #key
|
|
local fullcmd = ""
|
|
local error = false
|
|
local func = nil
|
|
|
|
if cmd_pipe then
|
|
pager_filter_pipe = io.popen(cmd_pipe, "w")
|
|
end
|
|
|
|
|
|
rl.choices = {}
|
|
|
|
for i, v in ipairs(key) do
|
|
local choices = get_choices(tree, v)
|
|
local idx = get_exact_choice(choices, v)
|
|
if idx then
|
|
local choice = choices[idx]
|
|
if i == #key then
|
|
func = tree[choice]
|
|
else
|
|
if choice == dotdotdot then
|
|
-- keep the tree the same, update the choice value to match the input string
|
|
choices[idx] = v
|
|
choice = v
|
|
else
|
|
tree = tree[choice]
|
|
end
|
|
end
|
|
-- print("level " .. i .. " '" .. choice .. "'")
|
|
table.insert(rl.choices, choice)
|
|
else
|
|
-- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
|
|
error = true
|
|
return nil
|
|
end
|
|
end
|
|
return func
|
|
end
|
|
|
|
function populate_tree(commands)
|
|
local tree = {}
|
|
|
|
for k, v in pairs(commands) do
|
|
local key = readln.split(k .. " <cr>", "[ ]+")
|
|
local xtree = tree
|
|
for i, kk in ipairs(key) do
|
|
if i == 1 and kk == "sh" then
|
|
kk = "show"
|
|
end
|
|
if i == #key then
|
|
if type(v) == "function" then
|
|
xtree[kk] = v
|
|
else
|
|
xtree[kk] = function(rl) paged_write(table.concat(v, "\n") .. "\n") end
|
|
end
|
|
else
|
|
if not xtree[kk] then
|
|
xtree[kk] = {}
|
|
end
|
|
xtree = xtree[kk]
|
|
end
|
|
end
|
|
end
|
|
return tree
|
|
end
|
|
|
|
function trim (s)
|
|
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
|
|
end
|
|
|
|
|
|
function init_vpp(vpp)
|
|
local root_dir = "/home/ubuntu/vpp"
|
|
local pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
|
|
|
|
vpp:init({ pneum_path = pneum_path })
|
|
|
|
vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
|
|
vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
|
|
|
|
vpp:connect("lua_cli")
|
|
end
|
|
|
|
function run_cli(vpp, cli)
|
|
local reply = vpp:api_call("cli_inband", { cmd = cli })
|
|
if reply and #reply == 1 then
|
|
local rep = reply[1]
|
|
if 0 == rep.retval then
|
|
return rep.reply
|
|
else
|
|
return "XXXXXLUACLI: API RETVAL ERROR : " .. tostring(rep.retval)
|
|
end
|
|
else
|
|
return "XXXXXLUACLI ERROR, RAW REPLY: " .. vpp.dump(reply)
|
|
end
|
|
end
|
|
|
|
|
|
function toprintablestring(s)
|
|
if type(s) == "string" then
|
|
return "\n"..vpp.hex_dump(s)
|
|
else
|
|
return tostring(s)
|
|
end
|
|
end
|
|
|
|
function interactive_cli(r)
|
|
while not done do
|
|
pager_reset()
|
|
local cmd = r.readln()
|
|
if not cmd then
|
|
done = true
|
|
elseif cmd == "quit" or cmd == "exit" then
|
|
done = true
|
|
else
|
|
local func = device_cli_exec(r)
|
|
if func then
|
|
func(r)
|
|
else
|
|
if trim(cmd) == "" then
|
|
else
|
|
for i = 1, #r.prompt do
|
|
paged_write(" ")
|
|
end
|
|
paged_write("^\n% Invalid input detected at '^' marker.\n\n")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
device = {}
|
|
device.output = {}
|
|
|
|
init_vpp(vpp)
|
|
cmds_str = run_cli(vpp, "?")
|
|
vpp_cmds = readln.split(cmds_str, "\n")
|
|
vpp_clis = {}
|
|
|
|
for linenum, line in ipairs(vpp_cmds) do
|
|
local m,h = string.match(line, "^ (.-) (.*)$")
|
|
if m and #m > 0 then
|
|
table.insert(vpp_clis, m)
|
|
device.output["vpp debug cli " .. m] = function(rl)
|
|
-- print("ARBITRARY CLI" .. vpp.dump(rl.choices))
|
|
print("LUACLI command: " .. table.concat(rl.choices, " "))
|
|
local sub = {}
|
|
--
|
|
for i=4, #rl.choices -1 do
|
|
table.insert(sub, rl.choices[i])
|
|
end
|
|
local cli = table.concat(sub, " ")
|
|
print("Running CLI: " .. tostring(cli))
|
|
paged_write(run_cli(vpp, cli))
|
|
end
|
|
device.output["vpp debug cli " .. m .. " " .. dotdotdot] = function(rl)
|
|
print("ARGH")
|
|
end
|
|
|
|
local ret = run_cli(vpp, "help " .. m)
|
|
device.output["help vpp debug cli " .. m] = { ret }
|
|
end
|
|
end
|
|
|
|
for linenum, line in ipairs(vpp_clis) do
|
|
-- print(line, ret)
|
|
end
|
|
|
|
for msgnum, msgname in ipairs(vpp.msg_number_to_name) do
|
|
local cli, numspaces = string.gsub(msgname, "_", " ")
|
|
device.output["call " .. cli .. " " .. dotdotdot] = function(rl)
|
|
print("ARGH")
|
|
end
|
|
device.output["call " .. cli] = function(rl)
|
|
print("LUACLI command: " .. table.concat(rl.choices, " "))
|
|
print("Running API: " .. msgname) -- vpp.dump(rl.choices))
|
|
local out = {}
|
|
local args = {}
|
|
local ntaken = 0
|
|
local argname = ""
|
|
for i=(1+1+numspaces+1), #rl.choices-1 do
|
|
-- print(i, rl.choices[i])
|
|
if ntaken > 0 then
|
|
ntaken = ntaken -1
|
|
else
|
|
local fieldname = rl.choices[i]
|
|
local field = vpp.msg_name_to_fields[msgname][fieldname]
|
|
if field then
|
|
local s = rl.choices[i+1]
|
|
s=s:gsub("\\x(%x%x)",function (x) return string.char(tonumber(x,16)) end)
|
|
args[fieldname] = s
|
|
ntaken = 1
|
|
end
|
|
end
|
|
end
|
|
-- print("ARGS: ", vpp.dump(args))
|
|
local ret = vpp:api_call(msgname, args)
|
|
for i, reply in ipairs(ret) do
|
|
table.insert(out, "=================== Entry #" .. tostring(i))
|
|
for k, v in pairs(reply) do
|
|
table.insert(out, " " .. tostring(k) .. " : " .. toprintablestring(v))
|
|
end
|
|
end
|
|
-- paged_write(vpp.dump(ret) .. "\n\n")
|
|
paged_write(table.concat(out, "\n").."\n\n")
|
|
end
|
|
device.output["call " .. cli .. " help"] = function(rl)
|
|
local out = {}
|
|
for k, v in pairs(vpp.msg_name_to_fields[msgname]) do
|
|
table.insert(out, tostring(k) .. " : " .. v["ctype"] .. " ; " .. tostring(vpp.dump(v)) )
|
|
end
|
|
-- paged_write(vpp.dump(vpp.msg_name_to_fields[msgname]) .. "\n\n")
|
|
paged_write(table.concat(out, "\n").."\n\n")
|
|
end
|
|
-- vpp.msg_name_to_number = {}
|
|
end
|
|
|
|
|
|
|
|
local r = readln.reader()
|
|
local done = false
|
|
|
|
r.prompt = "VPP(luaCLI)#"
|
|
|
|
r.help = device_cli_help
|
|
r.tab_complete = device_cli_tab_complete
|
|
print("===== CLI view, use ^D to end =====")
|
|
|
|
r.tree = populate_tree(device.output)
|
|
-- readln.pretty("xxxx", r.tree)
|
|
|
|
|
|
for idx, an_arg in ipairs(arg) do
|
|
local fname = an_arg
|
|
if fname == "-i" then
|
|
pager_lines = 23
|
|
interactive_cli(r)
|
|
else
|
|
pager_lines = 100000000
|
|
for line in io.lines(fname) do
|
|
r.command = line
|
|
local func = device_cli_exec(r)
|
|
if func then
|
|
func(r)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if #arg == 0 then
|
|
print("You should specify '-i' as an argument for the interactive session,")
|
|
print("but with no other sources of commands, we start interactive session now anyway")
|
|
interactive_cli(r)
|
|
end
|
|
|
|
vpp:disconnect()
|
|
r.done()
|
|
|
|
|