--[[ /* * 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. */ ]] -- json decode/encode from https://gist.github.com/tylerneylon/59f4bcf316be525b30ab -- licensed by the author tylerneylon into public domain. Thanks! local json = {} -- Internal functions. local function kind_of(obj) if type(obj) ~= 'table' then return type(obj) end local i = 1 for _ in pairs(obj) do if obj[i] ~= nil then i = i + 1 else return 'table' end end if i == 1 then return 'table' else return 'array' end end local function escape_str(s) local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} for i, c in ipairs(in_char) do s = s:gsub(c, '\\' .. out_char[i]) end return s end -- Returns pos, did_find; there are two cases: -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. -- 2. Delimiter not found: pos = pos after leading space; did_find = false. -- This throws an error if err_if_missing is true and the delim is not found. local function skip_delim(str, pos, delim, err_if_missing) pos = pos + #str:match('^%s*', pos) if str:sub(pos, pos) ~= delim then if err_if_missing then error('Expected ' .. delim .. ' near position ' .. pos) end return pos, false end return pos + 1, true end -- Expects the given pos to be the first character after the opening quote. -- Returns val, pos; the returned pos is after the closing quote character. local function parse_str_val(str, pos, val) val = val or '' local early_end_error = 'End of input found while parsing string.' if pos > #str then error(early_end_error) end local c = str:sub(pos, pos) if c == '"' then return val, pos + 1 end if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end -- We must have a \ character. local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} local nextc = str:sub(pos + 1, pos + 1) if not nextc then error(early_end_error) end return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) end -- Returns val, pos; the returned pos is after the number's final character. local function parse_num_val(str, pos) local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) local val = tonumber(num_str) if not val then error('Error parsing number at position ' .. pos .. '.') end return val, pos + #num_str end -- Public values and functions. function json.stringify(obj, as_key) local s = {} -- We'll build the string as an array of strings to be concatenated. local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise. if kind == 'array' then if as_key then error('Can\'t encode array as key.') end s[#s + 1] = '[' for i, val in ipairs(obj) do if i > 1 then s[#s + 1] = ', ' end s[#s + 1] = json.stringify(val) end s[#s + 1] = ']' elseif kind == 'table' then if as_key then error('Can\'t encode table as key.') end s[#s + 1] = '{' for k, v in pairs(obj) do if #s > 1 then s[#s + 1] = ', ' end s[#s + 1] = json.stringify(k, true) s[#s + 1] = ':' s[#s + 1] = json.stringify(v) end s[#s + 1] = '}' elseif kind == 'string' then return '"' .. escape_str(obj) .. '"' elseif kind == 'number' then if as_key then return '"' .. tostring(obj) .. '"' end return tostring(obj) elseif kind == 'boolean' then return tostring(obj) elseif kind == 'nil' then return 'null' else error('Unjsonifiable type: ' .. kind .. '.') end return table.concat(s) end json.null = {} -- This is a one-off table to represent the null value. function json.parse(str, pos, end_delim) pos = pos or 1 if pos > #str then error('Reached unexpected end of input.') end local pos = pos + #str:match('^%s*', pos) -- Skip whitespace. local first = str:sub(pos, pos) if first == '{' then -- Parse an object. local obj, key, delim_found = {}, true, true pos = pos + 1 while true do key, pos = json.parse(str, pos, '}') if key == nil then return obj, pos end if not delim_found then error('Comma missing between object items.') end pos = skip_delim(str, pos, ':', true) -- true -> error if missing. obj[key], pos = json.parse(str, pos) pos, delim_found = skip_delim(str, pos, ',') end elseif first == '[' then -- Parse an array. local arr, val, delim_found = {}, true, true pos = pos + 1 while true do val, pos = json.parse(str, pos, ']') if val == nil then return arr, pos end if not delim_found then error('Comma missing between array items.') end arr[#arr + 1] = val pos, delim_found = skip_delim(str, pos, ',') end elseif first == '"' then -- Parse a string. return parse_str_val(str, pos + 1) elseif first == '-' or first:match('%d') then -- Parse a number. return parse_num_val(str, pos) elseif first == end_delim then -- End of an object or array. return nil, pos + 1 else -- Parse true, false, or null. local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} for lit_str, lit_val in pairs(literals) do local lit_end = pos + #lit_str - 1 if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end end local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) error('Invalid json syntax starting at ' .. pos_info_str) end end local vpp = {} local ffi = require("ffi") --[[ The basic type definitions. A bit of weird gymnastic with unionization of the hton* and ntoh* functions results is to make handling of signed and unsigned types a bit cleaner, essentially building typecasting into a C union. The vl_api_opaque_message_t is a synthetic type assumed to have enough storage to hold the entire API message regardless of the type. During the operation it is casted to the specific message struct types. ]] ffi.cdef([[ typedef uint8_t u8; typedef int8_t i8; typedef uint16_t u16; typedef int16_t i16; typedef uint32_t u32; typedef int32_t i32; typedef uint64_t u64; typedef int64_t i64; typedef double f64; typedef float f32; #pragma pack(1) typedef union { u16 u16; i16 i16; } lua_ui16t; #pragma pack(1) typedef union { u32 u32; i32 i32; } lua_ui32t; u16 ntohs(uint16_t hostshort); u16 htons(uint16_t hostshort); u32 htonl(uint32_t along); u32 ntohl(uint32_t along); void *memset(void *s, int c, size_t n); void *memcpy(void *dest, void *src, size_t n); #pragma pack(1) typedef struct _vl_api_opaque_message { u16 _vl_msg_id; u8 data[65536]; } vl_api_opaque_message_t; ]]) -- CRC-based version stuff local crc32c_table = ffi.new('const uint32_t[256]', { 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 } ); local function CRC8(crc, d) return bit.bxor(bit.rshift(crc, 8), crc32c_table[bit.band(0xff, bit.bxor(crc, d))]) end local function CRC16(crc, d) crc = CRC8(crc, bit.band(d, 0xFF)) d = bit.rshift(d, 8) crc = CRC8(crc, bit.band(d, 0xFF)) return crc end local function string_crc(str, crc) for i=1,#str do -- print("S", i, string.byte(str, i), string.char(string.byte(str, i))) crc = CRC8(crc, string.byte(str, i)) end return crc end local tokens = { { ["match"] =' ', ["act"] = { } }, { ["match"] ='\n', ["act"] = { } }, { ["match"] ="manual_endian", ["act"] = { "NODE_MANUAL_ENDIAN", "MANUAL_ENDIAN", 276 } }, { ["match"] ="define", ["act"] = { "NODE_DEFINE", "DEFINE", 267 } }, { ["match"] ="dont_trace", ["act"] = { "NODE_DONT_TRACE", "DONT_TRACE", 279 } }, { ["match"] ="f64", ["act"] = { "NODE_F64", "PRIMTYPE", string_crc } }, { ["match"] ="i16", ["act"] = { "NODE_I16", "PRIMTYPE", string_crc } }, { ["match"] ="i32", ["act"] = { "NODE_I32", "PRIMTYPE", string_crc } }, { ["match"] ="i64", ["act"] = { "NODE_I64", "PRIMTYPE", string_crc } }, { ["match"] ="i8", ["act"] = { "NODE_I8", "PRIMTYPE", string_crc } }, { ["match"] ="manual_print", ["act"] = { "NODE_MANUAL_PRINT", "MANUAL_PRINT", 275 } }, { ["match"] ="noversion", ["act"] = { "NODE_NOVERSION", "NOVERSION", 274 } }, { ["match"] ="packed", ["act"] = { "NODE_PACKED", "TPACKED", 266 } }, { ["match"] ="typeonly", ["act"] = { "NODE_TYPEONLY", "TYPEONLY", 278 } }, { ["match"] ="u16", ["act"] = { "NODE_U16", "PRIMTYPE", string_crc } }, { ["match"] ="u32", ["act"] = { "NODE_U32", "PRIMTYPE", string_crc } }, { ["match"] ="u64", ["act"] = { "NODE_U64", "PRIMTYPE", string_crc } }, { ["match"] ="u8", ["act"] = { "NODE_U8", "PRIMTYPE", string_crc } }, { ["match"] ="union", ["act"] = { "NODE_UNION", "UNION", 271 } }, { ["match"] ="uword", ["act"] = { "NODE_UWORD", "PRIMTYPE", string_crc } }, { ["match"] ="%(", ["act"] = { "NODE_LPAR", "LPAR", 259 } }, { ["match"] ="%)", ["act"] = { "NODE_RPAR", "RPAR", 258 } }, { ["match"] =";", ["act"] = { "NODE_SEMI", "SEMI", 260 } }, { ["match"] ="%[", ["act"] = { "NODE_LBRACK", "LBRACK", 261 } }, { ["match"] ="%]", ["act"] = { "NODE_RBRACK", "RBRACK", 262 } }, { ["match"] ="%{", ["act"] = { "NODE_LCURLY", "LCURLY", 268 } }, { ["match"] ="%}", ["act"] = { "NODE_RCURLY", "RCURLY", 269 } }, { ["match"] ='%b""', ["act"] = { "NODE_STRING", "STRING", string_crc } }, { ["match"] ='%b@@', ["act"] = { "NODE_HELPER", "HELPER_STRING", string_crc } }, -- TODO: \ must be consumed { ["match"] ='[_a-zA-Z][_a-zA-Z0-9]*', ["act"] = { "NODE_NAME", "NAME", string_crc } }, { ["match"] ='[0-9]+', ["act"] = { "NODE_NUMBER", "NUMBER", string_crc } }, { ["match"] ='#[^\n]+', ["act"] = { "NODE_PRAGMA", "PRAGMA", nil } }, } function vpp.crc_version_string(data) local input_crc = 0 -- Get rid of comments data = data:gsub("/%*.-%*/", "") data = data:gsub("//[^\n]+", "") -- print(data) idx = 1 while (true) do local matched = nil for k, v in ipairs(tokens) do if not matched then local x, y, cap = string.find(data, v["match"], idx) if x == idx then matched = { ["node"] = v["act"], ["x"] = x, ["y"] = y, ["cap"] = cap, ["chars"] = string.sub(data, x, y) } -- print(k, v, x, y, cap, matched.chars, matched.node[0] ) end end end if matched then idx = idx + (matched.y - matched.x + 1) if matched.node[1] then local act = matched.node[3] if type(act) == "function" then input_crc = act(matched.chars, input_crc) elseif type(act) == "number" then input_crc = CRC16(input_crc, act) end -- print(vpp.dump(matched)) end else -- print("NOT MATCHED!") local crc = CRC16(input_crc, 0xFFFFFFFF) return string.sub(string.format("%x", crc), -8) end end end function vpp.dump(o) if type(o) == 'table' then local s = '{ ' for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end s = s .. '['..k..'] = ' .. vpp.dump(v) .. ',' end return s .. '} ' else return tostring(o) end end function vpp.hex_dump(buf) local ret = {} for i=1,math.ceil(#buf/16) * 16 do if (i-1) % 16 == 0 then table.insert(ret, string.format('%08X ', i-1)) end table.insert(ret, ( i > #buf and ' ' or string.format('%02X ', buf:byte(i)) )) if i % 8 == 0 then table.insert(ret, ' ') end if i % 16 == 0 then table.insert(ret, buf:sub(i-16+1, i):gsub('%c','.')..'\n' ) end end return table.concat(ret) end function vpp.c_str(text_in) local text = text_in -- \000 will be helpfully added by ffi.copy local c_str = ffi.new("char[?]", #text+1) ffi.copy(c_str, text) return c_str end function vpp.init(vpp, args) local vac_api = args.vac_api or [[ int cough_vac_attach(char *vac_path, char *cough_path); int vac_connect(char *name, char *chroot_prefix, void *cb); int vac_disconnect(void); int vac_read(char **data, int *l); int vac_write(char *data, int len); void vac_free(char *data); uint32_t vac_get_msg_index(unsigned char * name); ]] vpp.vac_path = args.vac_path ffi.cdef(vac_api) local init_res = 0 vpp.vac = ffi.load(vpp.vac_path) if (init_res < 0) then return nil end vpp.next_msg_num = 1 vpp.msg_name_to_number = {} vpp.msg_name_to_fields = {} vpp.msg_number_to_name = {} vpp.msg_number_to_type = {} vpp.msg_number_to_pointer_type = {} vpp.msg_name_to_crc = {} vpp.c_type_to_fields = {} vpp.events = {} vpp.plugin_version = {} vpp.is_connected = false vpp.t_lua2c = {} vpp.t_c2lua = {} vpp.t_lua2c["u8"] = function(c_type, src, dst_c_ptr) if type(src) == "string" then -- ffi.copy adds a zero byte at the end. Grrr. -- ffi.copy(dst_c_ptr, src) ffi.C.memcpy(dst_c_ptr, vpp.c_str(src), #src) return(#src) elseif type(src) == "table" then for i,v in ipairs(src) do ffi.cast("u8 *", dst_c_ptr)[i-1] = v end return(#src) else return 1, src -- ffi.cast("u8", src) end end vpp.t_c2lua["u8"] = function(c_type, src_ptr, src_len) if src_len then return ffi.string(src_ptr, src_len) else return (tonumber(src_ptr)) end end vpp.t_lua2c["u16"] = function(c_type, src, dst_c_ptr) if type(src) == "table" then for i,v in ipairs(src) do ffi.cast("u16 *", dst_c_ptr)[i-1] = ffi.C.htons(v) end return(2 * #src) else return 2, (ffi.C.htons(src)) end end vpp.t_c2lua["u16"] = function(c_type, src_ptr, src_len) if src_len then local out = {} for i = 0,src_len-1 do out[i+1] = tonumber(ffi.C.ntohs(src_ptr[i])) end return out else return (tonumber(ffi.C.ntohs(src_ptr))) end end vpp.t_lua2c["u32"] = function(c_type, src, dst_c_ptr) if type(src) == "table" then for i,v in ipairs(src) do ffi.cast("u32 *", dst_c_ptr)[i-1] = ffi.C.htonl(v) end return(4 * #src) else return 4, (ffi.C.htonl(src)) end end vpp.t_c2lua["u32"] = function(c_type, src_ptr, src_len) if src_len then local out = {} for i = 0,src_len-1 do out[i+1] = tonumber(ffi.C.ntohl(src_ptr[i])) end return out else return (tonumber(ffi.C.ntohl(src_ptr))) end end vpp.t_lua2c["i32"] = function(c_type, src, dst_c_ptr) if type(src) == "table" then for i,v in ipairs(src) do ffi.cast("i32 *", dst_c_ptr)[i-1] = ffi.C.htonl(v) end return(4 * #src) else return 4, (ffi.C.htonl(src)) end end vpp.t_c2lua["i32"] = function(c_type, src_ptr, src_len) local ntohl = function(src) local u32val = ffi.cast("u32", src) local ntohlval = (ffi.C.ntohl(u32val)) local out = tonumber(ffi.cast("i32", ntohlval + 0LL)) return out end if src_len then local out = {} for i = 0,src_len-1 do out[i+1] = tonumber(ntohl(src_ptr[i])) end else return (tonumber(ntohl(src_ptr))) end end vpp.t_lua2c["u64"] = function(c_type, src, dst_c_ptr) if type(src) == "table" then for i,v in ipairs(src) do ffi.cast("u64 *", dst_c_ptr)[i-1] = v --- FIXME ENDIAN end return(8 * #src) else return 8, ffi.cast("u64", src) --- FIXME ENDIAN end end vpp.t_c2lua["u64"] = function(c_type, src_ptr, src_len) if src_len then local out = {} for i = 0,src_len-1 do out[i+1] = tonumber(src_ptr[i]) -- FIXME ENDIAN end return out else return (tonumber(src_ptr)) --FIXME ENDIAN end end vpp.t_lua2c["__MSG__"] = function(c_type, src, dst_c_ptr) local dst = ffi.cast(c_type .. " *", dst_c_ptr) local additional_len = 0 local fields_info = vpp.c_type_to_fields[c_type] -- print("__MSG__ type: " .. tostring(c_type)) ffi.C.memset(dst_c_ptr, 0, ffi.sizeof(dst[0])) -- print(vpp.dump(fields_info)) -- print(vpp.dump(src)) for k,v in pairs(src) do local field = fields_info[k] if not field then print("ERROR: field " .. tostring(k) .. " in message " .. tostring(c_type) .. " is unknown") end local lua2c = vpp.t_lua2c[field.c_type] -- print("__MSG__ field " .. tostring(k) .. " : " .. vpp.dump(field)) -- if the field is not an array type, try to coerce the argument to a number if not field.array and type(v) == "string" then v = tonumber(v) end if not lua2c then print("__MSG__ " .. tostring(c_type) .. " t_lua2c: can not store field " .. field.name .. " type " .. field.c_type .. " dst " .. tostrin
# 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.

"""VAT executor library."""

import json

from robot.api import logger

from resources.libraries.python.ssh import SSH
from resources.libraries.python.constants import Constants
from resources.libraries.python.VatHistory import VatHistory


__all__ = ['VatExecutor']


def cleanup_vat_json_output(json_output):
    """Return VAT JSON output cleaned from VAT clutter.

    Clean up VAT JSON output from clutter like vat# prompts and such.

    :param json_output: Cluttered JSON output.
    :returns: Cleaned up output JSON string.
    """

    retval = json_output
    clutter = ['vat#', 'dump_interface_table error: Misc']
    for garbage in clutter:
        retval = retval.replace(garbage, '')
    return retval


class VatExecutor(object):
    """Contains methods for executing VAT commands on DUTs."""
    def __init__(self):
        self._stdout = None
        self._stderr = None
        self._ret_code = None

    def execute_script(self, vat_name, node, timeout=10, json_out=True):
        """Copy local_path script to node, execute it and return result.

        :param vat_name: Name of the vat script file. Only the file name of
        the script is required, the resources path is prepended automatically.
        :param node: Node to execute the VAT script on.
        :param timeout: Seconds to allow the script to run.
        :param json_out: Require JSON output.
        :return: (rc, stdout, stderr) tuple.
        """

        ssh =