diff --git a/README.md b/README.md index c42a300e..a75c6a9a 100755 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The Application Developmers Guide is the best document to read first. - **cp_shell** - Web interface for running linux shell commands. - **cli_sample** - - Includes cppxssh module that enables SSH access to local CLI to send commands and return output. + - Includes csterm module that enables access to local CLI to send commands and return output. - **ipverify_custom_action** - Create a custom action in a function to be called when an IPverify test status changes. - **cpu_usage** diff --git a/cli_sample/cli_sample.py b/cli_sample/cli_sample.py index c194a7b6..07cb9678 100644 --- a/cli_sample/cli_sample.py +++ b/cli_sample/cli_sample.py @@ -1,37 +1,16 @@ # cli - execute CLI command and return output -from csclient import EventingCSClient - - -def cli(username, password, cmd): - """ - :param username: string - username for SSH login - :param password: string - user password for SSH login - :param cmd: string - CLI command to execute - Example: "sms 2081234567 'hello world' int1" - Example: "arpdump" - :return: string - Text output returned from CLI command - """ - import cppxssh - ssh_tunnel = cppxssh.cppxssh() - ssh_tunnel.login('localhost', username, password, auto_prompt_reset=False) - ssh_tunnel.PROMPT = '\[[0-9A-Za-z_]+@.+\]\$ ' - ssh_tunnel.sendline(cmd) - ssh_tunnel.prompt() - output = ssh_tunnel.before.decode() - del ssh_tunnel - try: - # try to remove the command echo - return output.split(cmd + '\r\n')[1] - except IndexError: - # for some reason we failed to remove so return original - return output +import time +from csclient import EventingCSClient +from csterm import CSTerm cp = EventingCSClient('cli_sample') +ct = CSTerm(cp) cp.log('Starting...') -cp.log('Output:\n' + cli('admin', '11', 'arpdump')) +while True: + # use ct.exec followed by CLI command to execute + # Example: "sms 2081234567 'hello world' int1" + # Example: "arpdump" + cp.log('Output:\n' + ct.exec('arpdump')) + time.sleep(10) diff --git a/cli_sample/csterm.py b/cli_sample/csterm.py new file mode 100644 index 00000000..f24024d7 --- /dev/null +++ b/cli_sample/csterm.py @@ -0,0 +1,83 @@ +import random +import time +import re + + +class CSTerm: + INTERVAL = 0.3 # how often to poll for output, faster can sometimes miss output + + def __init__(self, csclient, timeout=10, soft_timeout=5, user=None): + """ + :param csclient: csclient.EventingCSClient + csclient object to use for communication + :param timeout: int + absolute maximum number of seconds to wait for output (default 10) + :param soft_timeout: int + number of seconds to wait for output before sending interrupt (default 5) + """ + self.c = csclient + self.timeout = timeout + self.soft_timeout = soft_timeout + self.s_id = "term-%s" % random.randint(100000000, 999999999) + self.user = user + + def exec(self, cmds, clean=True): + """ + :param cmds: list or string + list of commands to execute or single command to execute + :param clean: bool + if True, remove terminal escape sequences from output + :return: string + Text output returned from CLI command + """ + cmds = cmds if isinstance(cmds, list) else [cmds] + cmds = [c + '\n' for c in cmds] + timeout = self.timeout * (1 / self.INTERVAL) + soft_timeout = self.soft_timeout * (1 / self.INTERVAL) + + k = iter(cmds) + kp = next(k) + r = '' + while timeout > 0: + + self.c.put("/control/csterm/%s" % self.s_id, self._k(kp)) + rp = self.c.get("/control/csterm/%s" % self.s_id) + + r += rp['k'] + # the output generally will end with a prompt and no newline, so we can check for that + # after we've sent all our commands + if kp == "" and not r.endswith('\n'): + break + + kp = next(k, None) or "" + timeout-=1 + time.sleep(self.INTERVAL) + if timeout < soft_timeout: + self.c.put("/control/csterm/%s" % self.s_id, self._k('\x03')) + rp = self.c.get("/control/csterm/%s" % self.s_id) + r += rp['k'] + soft_timeout = 0 + + # remove the prompt and any terminal escape sequences + if clean: + r = re.sub(r'(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])','', r) + r = r.split('\n') + prompt = r[-1] + r = [l for l in r if not l.startswith(prompt)] + r = "\n".join(r) + + return r + + def _k(self, v): + r = {"k": v} + if self.user: + r["u"] = self.user + return r + +if __name__ == '__main__': + import sys + from csclient import EventingCSClient + + c = EventingCSClient('cli_sample') + ct = CSTerm(c, user="admin") + print(ct.exec(sys.argv[1:])) diff --git a/cli_sample/package.ini b/cli_sample/package.ini index eef1bc69..21a67c95 100644 --- a/cli_sample/package.ini +++ b/cli_sample/package.ini @@ -9,3 +9,4 @@ reboot = true auto_start = true version_major = 1 version_minor = 0 +version_patch = 1 \ No newline at end of file diff --git a/cli_sample/readme.txt b/cli_sample/readme.txt index aa288dbb..1cd8fa62 100755 --- a/cli_sample/readme.txt +++ b/cli_sample/readme.txt @@ -5,7 +5,7 @@ cli_sample Application Version =================== -1.0 +1.0.1 NCOS Devices Supported @@ -20,7 +20,7 @@ None Application Purpose =================== -Includes cppxssh module that enables SSH access to local CLI to send commands and return output. Example sends "arpdump". +Includes a csterm.py library that uses csclient control tree access to local CLI to send commands and return output. Example sends "arpdump". Expected Output =============== @@ -32,4 +32,36 @@ Expected Output ethernet primarylan1 STALE 14:b1:c8:01:59:09 fe80::1cd1:9ffa:135:3ed4 ethernet primarylan1 STALE 14:b1:c8:01:59:09 fe80::18d5:408e:d760:2e39 +Notes +==== +csterm.py is a useful utility for interacting with the NCOS CLI. The usage is straight forward: +To run a single command: + c = EventingCSClient('cli_sample') + ct = CSTerm(c) + ct.exec("arpdump") +Multiple commands can be run by passing a list of commands: + ct.exec(["clients", "arpdump"]) + +An instance of CSTerm invokes a single CLI session, similar to SSHing into the device. Besides +sending a list of commands into exec, you can execute exec multiple times to send multiple commands + + ct.exec("clients") + ct.exec("wan") + ct.exec("arpdump") + +Intuitively, it's then possible to automate a workflow. Here's an example using NCOS ssh client +to SSH into a local machine and run a command: + + ct.exec(["ssh user@host", # ssh into host + "yes", # respond 'yes' to accept host key + "password", # respond with password + "cd workflow", # change directory + "ls", # list files + "exit"]) # exit ssh session to return back to NCOS cli + +You can adjust some of the timers for CSTerm: +ct = CSTerm(c, timeout=10, soft_timeout=5) +Timeout is the absolute timeout when running a command, soft_timeout is the timeout for sending +a "ctrl+c" to the console to terminate the running command. The default values are +10 and 5 seconds respectively. \ No newline at end of file