forked from sonic-net/sonic-buildimage
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Console show/clear/connect skeleton (sonic-net#278)
* new consutil package - includes helper functions, and show/clear/connect skeletons Signed-off-by: Cayla Wanderman-Milne <t-cawand@microsoft.com> * Added connect command skeleton, added show line and clear line skeletons Signed-off-by: Cayla Wanderman-Milne <t-cawand@microsoft.com> * Added connect and consutil packages to setup.py and to bash completion Signed-off-by: Cayla Wanderman-Milne <t-cawand@microsoft.com> * Added docstrings and "TODO Stub" comments to clear line, show line, and connect line Signed-off-by: Cayla Wanderman-Milne <t-cawand@microsoft.com>
- Loading branch information
Showing
9 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
#! /usr/bin/python -u | ||
|
||
import click | ||
import errno | ||
import os | ||
import subprocess | ||
import sys | ||
from click_default_group import DefaultGroup | ||
|
||
try: | ||
# noinspection PyPep8Naming | ||
import ConfigParser as configparser | ||
except ImportError: | ||
# noinspection PyUnresolvedReferences | ||
import configparser | ||
|
||
|
||
# This is from the aliases example: | ||
# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py | ||
class Config(object): | ||
"""Object to hold CLI config""" | ||
|
||
def __init__(self): | ||
self.path = os.getcwd() | ||
self.aliases = {} | ||
|
||
def read_config(self, filename): | ||
parser = configparser.RawConfigParser() | ||
parser.read([filename]) | ||
try: | ||
self.aliases.update(parser.items('aliases')) | ||
except configparser.NoSectionError: | ||
pass | ||
|
||
|
||
# Global Config object | ||
_config = None | ||
|
||
|
||
# This aliased group has been modified from click examples to inherit from DefaultGroup instead of click.Group. | ||
# DefaultGroup is a superclass of click.Group which calls a default subcommand instead of showing | ||
# a help message if no subcommand is passed | ||
class AliasedGroup(DefaultGroup): | ||
"""This subclass of a DefaultGroup supports looking up aliases in a config | ||
file and with a bit of magic. | ||
""" | ||
|
||
def get_command(self, ctx, cmd_name): | ||
global _config | ||
|
||
# If we haven't instantiated our global config, do it now and load current config | ||
if _config is None: | ||
_config = Config() | ||
|
||
# Load our config file | ||
cfg_file = os.path.join(os.path.dirname(__file__), 'aliases.ini') | ||
_config.read_config(cfg_file) | ||
|
||
# Try to get builtin commands as normal | ||
rv = click.Group.get_command(self, ctx, cmd_name) | ||
if rv is not None: | ||
return rv | ||
|
||
# No builtin found. Look up an explicit command alias in the config | ||
if cmd_name in _config.aliases: | ||
actual_cmd = _config.aliases[cmd_name] | ||
return click.Group.get_command(self, ctx, actual_cmd) | ||
|
||
# Alternative option: if we did not find an explicit alias we | ||
# allow automatic abbreviation of the command. "status" for | ||
# instance will match "st". We only allow that however if | ||
# there is only one command. | ||
matches = [x for x in self.list_commands(ctx) | ||
if x.lower().startswith(cmd_name.lower())] | ||
if not matches: | ||
# No command name matched. Issue Default command. | ||
ctx.arg0 = cmd_name | ||
cmd_name = self.default_cmd_name | ||
return DefaultGroup.get_command(self, ctx, cmd_name) | ||
elif len(matches) == 1: | ||
return DefaultGroup.get_command(self, ctx, matches[0]) | ||
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) | ||
|
||
def run_command(command, display_cmd=False): | ||
if display_cmd: | ||
click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green')) | ||
|
||
proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) | ||
|
||
while True: | ||
output = proc.stdout.readline() | ||
if output == "" and proc.poll() is not None: | ||
break | ||
if output: | ||
try: | ||
click.echo(output.rstrip('\n')) | ||
except IOError as e: | ||
# In our version of Click (v6.6), click.echo() and click.echo_via_pager() do not properly handle | ||
# SIGPIPE, and if a pipe is broken before all output is processed (e.g., pipe output to 'head'), | ||
# it will result in a stack trace. This is apparently fixed upstream, but for now, we silently | ||
# ignore SIGPIPE here. | ||
if e.errno == errno.EPIPE: | ||
sys.exit(0) | ||
else: | ||
raise | ||
|
||
rc = proc.poll() | ||
if rc != 0: | ||
sys.exit(rc) | ||
|
||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) | ||
|
||
# | ||
# 'connect' group (root group) | ||
# | ||
|
||
# This is our entrypoint - the main "connect" command | ||
@click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) | ||
def connect(): | ||
"""SONiC command line - 'connect' command""" | ||
pass | ||
|
||
# | ||
# 'line' command ("connect line") | ||
# | ||
@connect.command('line') | ||
@click.argument('linenum') | ||
def line(linenum): | ||
"""Connect to line via serial connection""" | ||
# TODO: Stub | ||
return | ||
|
||
if __name__ == '__main__': | ||
connect() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#!/usr/bin/env python | ||
# | ||
# lib.py | ||
# | ||
# Helper code for CLI for interacting with switches via console device | ||
# | ||
|
||
try: | ||
import click | ||
import re | ||
import subprocess | ||
import sys | ||
except ImportError as e: | ||
raise ImportError("%s - required module not found" % str(e)) | ||
|
||
DEVICE_PREFIX = "/dev/ttyUSB" | ||
|
||
ERR_CMD = 1 | ||
ERR_DEV = 2 | ||
|
||
# runs command, exit if stderr is written to, returns stdout otherwise | ||
# input: cmd (str), output: output of cmd (str) | ||
def popenWrapper(cmd): | ||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) | ||
output = proc.stdout.read() | ||
error = proc.stderr.read() | ||
if error != "": | ||
click.echo("Command resulted in error: {}".format(error)) | ||
sys.exit(ERR_CMD) | ||
return output | ||
|
||
# exits if inputted line number does not correspond to a device | ||
# input: linenum | ||
def checkDevice(linenum): | ||
devices = getAllDevices() | ||
if DEVICE_PREFIX + str(linenum) not in devices: | ||
click.echo("Line number {} does not exist".format(linenum)) | ||
sys.exit(ERR_DEV) | ||
|
||
# returns a sorted list of all devices (whose name matches DEVICE_PREFIX) | ||
def getAllDevices(): | ||
cmd = "ls " + DEVICE_PREFIX + "*" | ||
output = popenWrapper(cmd) | ||
|
||
devices = output.split('\n') | ||
devices = list(filter(lambda dev: re.match(DEVICE_PREFIX + r"\d+", dev) != None, devices)) | ||
devices.sort(key=lambda dev: int(dev[len(DEVICE_PREFIX):])) | ||
|
||
return devices | ||
|
||
# returns a dictionary of busy devices and their info | ||
# maps line number to (pid, process start time) | ||
def getBusyDevices(): | ||
cmd = 'ps -eo pid,lstart,cmd | grep -E "(mini|pico)com"' | ||
output = popenWrapper(cmd) | ||
processes = output.split('\n') | ||
|
||
# matches any number of spaces then any number of digits | ||
regexPid = r" *(\d+)" | ||
# matches anything of form: Xxx Xxx 00 00:00:00 0000 | ||
regexDate = r"([A-Z][a-z]{2} [A-Z][a-z]{2} \d{2} \d{2}:\d{2}:\d{2} \d{4})" | ||
# matches any non-whitespace characters ending in minicom or picocom, | ||
# then a space and any chars followed by /dev/ttyUSB<any digits>, | ||
# then a space and any chars | ||
regexCmd = r"\S*(?:(?:mini)|(?:pico))com .*" + DEVICE_PREFIX + r"(\d+)(?: .*)?" | ||
regexProcess = re.compile(r"^"+regexPid+r" "+regexDate+r" "+regexCmd+r"$") | ||
|
||
busyDevices = {} | ||
for process in processes: | ||
match = regexProcess.match(process) | ||
if match != None: | ||
pid = match.group(1) | ||
date = match.group(2) | ||
linenum_key = match.group(3) | ||
busyDevices[linenum_key] = (pid, date) | ||
return busyDevices | ||
|
||
# returns baud rate of device corresponding to line number | ||
# input: linenum (str) | ||
def getBaud(linenum): | ||
checkDevice(linenum) | ||
cmd = "sudo stty -F " + DEVICE_PREFIX + str(linenum) | ||
output = popenWrapper(cmd) | ||
|
||
match = re.match(r"^speed (\d+) baud;", output) | ||
if match != None: | ||
return match.group(1) | ||
else: | ||
click.echo("Unable to determine baud rate") | ||
return "" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#!/usr/bin/env python | ||
# | ||
# main.py | ||
# | ||
# Command-line utility for interacting with switches over serial via console device | ||
# | ||
|
||
try: | ||
import click | ||
import re | ||
import subprocess | ||
except ImportError as e: | ||
raise ImportError("%s - required module not found" % str(e)) | ||
|
||
@click.group() | ||
def consutil(): | ||
"""consutil - Command-line utility for interacting with switchs via console device""" | ||
|
||
if os.geteuid() != "" | ||
print "Root privileges are required for this operation" | ||
sys.exit(1) | ||
|
||
# 'show' subcommand | ||
@consutil.command() | ||
def line(): | ||
"""Show all /dev/ttyUSB lines""" | ||
click.echo("show line") | ||
|
||
# 'clear' subcommand | ||
@consutil.command() | ||
@click.argument('linenum') | ||
def clear(linenum): | ||
"""Clear preexisting connection to line""" | ||
click.echo("clear line linenum") | ||
|
||
# 'connect' subcommand | ||
@consutil.command() | ||
@click.argument('linenum') | ||
def connect(linenum): | ||
"""Connect to switch via console device""" | ||
click.echo("connect linenum") | ||
|
||
if __name__ == '__main__': | ||
consutil() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
_connect_completion() { | ||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \ | ||
COMP_CWORD=$COMP_CWORD \ | ||
_CONNECT_COMPLETE=complete $1 ) ) | ||
return 0 | ||
} | ||
|
||
complete -F _connect_completion -o default connect; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
_consutil_completion() { | ||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \ | ||
COMP_CWORD=$COMP_CWORD \ | ||
_CONSUTIL_COMPLETE=complete $1 ) ) | ||
return 0 | ||
} | ||
|
||
complete -F _consutil_completion -o default consutil; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters