diff --git a/consutil/lib.py b/consutil/lib.py index 8c7cf1aeaf..fe718f12ff 100644 --- a/consutil/lib.py +++ b/consutil/lib.py @@ -18,6 +18,7 @@ ERR_CMD = 1 ERR_DEV = 2 +ERR_CFG = 3 CONSOLE_PORT_TABLE = "CONSOLE_PORT" LINE_KEY = "LINE" @@ -47,48 +48,34 @@ def run_command(cmd): sys.exit(ERR_CMD) return output -# returns a sorted list of all devices -def getAllDevices(): +# returns a list of all lines +def getAllLines(): config_db = ConfigDBConnector() config_db.connect() # Querying CONFIG_DB to get configured console ports keys = config_db.get_keys(CONSOLE_PORT_TABLE) - devices = [] + lines = [] for k in keys: - device = config_db.get_entry(CONSOLE_PORT_TABLE, k) - device[LINE_KEY] = k - devices.append(device) + line = config_db.get_entry(CONSOLE_PORT_TABLE, k) + line[LINE_KEY] = k + lines.append(line) # Querying device directory to get all available console ports cmd = "ls " + DEVICE_PREFIX + "*" output = run_command(cmd) - availableTtys = output.split('\n') availableTtys = list(filter(lambda dev: re.match(DEVICE_PREFIX + r"\d+", dev) != None, availableTtys)) for tty in availableTtys: k = tty[len(DEVICE_PREFIX):] if k not in keys: - device = { LINE_KEY: k } - devices.append(device) - - devices.sort(key=lambda dev: int(dev[LINE_KEY])) - return devices - -# exits if inputted line number does not correspond to a device -# input: linenum -def checkDevice(linenum): - config_db = ConfigDBConnector() - config_db.connect() - - entry = config_db.get_entry(CONSOLE_PORT_TABLE, str(linenum)) - if not entry: - click.echo("Line number {} does not exist".format(linenum)) - sys.exit(ERR_DEV) + line = { LINE_KEY: k } + lines.append(line) + return lines -# returns a dictionary of busy devices and their info +# returns a dictionary of busy lines and their info # maps line number to (pid, process start time) -def getBusyDevices(): +def getBusyLines(): cmd = 'ps -eo pid,lstart,cmd | grep -E "(mini|pico)com"' output = run_command(cmd) processes = output.split('\n') @@ -103,48 +90,33 @@ def getBusyDevices(): regexCmd = r"\S*(?:(?:mini)|(?:pico))com .*" + DEVICE_PREFIX + r"(\d+)(?: .*)?" regexProcess = re.compile(r"^"+regexPid+r" "+regexDate+r" "+regexCmd+r"$") - busyDevices = {} + busyLines = {} 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 actual baud rate, configured baud rate, -# and flow control settings of device corresponding to line number -# input: linenum (str), output: (actual baud (str), configured baud (str), flow control (bool)) -def getConnectionInfo(linenum): - config_db = ConfigDBConnector() - config_db.connect() - entry = config_db.get_entry(CONSOLE_PORT_TABLE, str(linenum)) + busyLines[linenum_key] = (pid, date) + return busyLines - conf_baud = "-" if BAUD_KEY not in entry else entry[BAUD_KEY] - act_baud = DEFAULT_BAUD if conf_baud == "-" else conf_baud - flow_control = False - if FLOW_KEY in entry and entry[FLOW_KEY] == "1": - flow_control = True - - return (act_baud, conf_baud, flow_control) - -# returns the line number corresponding to target, or exits if line number cannot be found +# returns the target device corresponding to target, or None if line number connot be found # if deviceBool, interprets target as device name # otherwise interprets target as line number -# input: target (str), deviceBool (bool), output: linenum (str) -def getLineNumber(target, deviceBool): - if not deviceBool: - return target - - config_db = ConfigDBConnector() - config_db.connect() - - devices = getAllDevices() - for device in devices: - if DEVICE_KEY in device and device[DEVICE_KEY] == target: - return device[LINE_KEY] - - click.echo("Device {} does not exist".format(target)) - sys.exit(ERR_DEV) - return "" +# input: target (str), deviceBool (bool), output: device (dict) +def getLine(target, deviceBool=False): + lines = getAllLines() + + # figure out the search key + searchKey = LINE_KEY + if deviceBool: + searchKey = DEVICE_KEY + + # identify the line number by searching configuration + lineNumber = None + for line in lines: + if searchKey in line and line[searchKey] == target: + lineNumber = line[LINE_KEY] + targetLine = line + + return targetLine if lineNumber else None \ No newline at end of file diff --git a/consutil/main.py b/consutil/main.py index f73cafa81f..074b9e3ba6 100644 --- a/consutil/main.py +++ b/consutil/main.py @@ -21,50 +21,56 @@ def consutil(): if os.geteuid() != 0: click.echo("Root privileges are required for this operation") - sys.exit(1) + sys.exit(ERR_CMD) # 'show' subcommand @consutil.command() def show(): - """Show all /dev/ttyUSB lines and their info""" - devices = getAllDevices() - busyDevices = getBusyDevices() + """Show all lines and their info""" + lines = getAllLines() + busyLines = getBusyLines() - header = ["Line", "Actual/Configured Baud", "PID", "Start Time", "Device"] + # sort lines for table rendering + lines.sort(key=lambda dev: int(dev[LINE_KEY])) + + # set table header style + header = ["Line", "Baud", "PID", "Start Time", "Device"] body = [] - for device in devices: - lineNum = device[LINE_KEY] + for line in lines: + # configured information + lineNum = line[LINE_KEY] + baud = '-' if BAUD_KEY not in line else line[BAUD_KEY] + remoteDevice = '-' if DEVICE_KEY not in line else line[DEVICE_KEY] + + # runtime information busy = " " pid = "" date = "" - remoteDevice = '-' if DEVICE_KEY not in device else device[DEVICE_KEY] - if lineNum in busyDevices: - pid, date = busyDevices[lineNum] + if lineNum in busyLines: + pid, date = busyLines[lineNum] busy = "*" - actBaud, confBaud, _ = getConnectionInfo(lineNum) - # repeated "~" will be replaced by spaces - hacky way to align the "/"s - baud = "{}/{}{}".format(actBaud, confBaud, "~"*(15-len(confBaud))) body.append([busy+lineNum, baud, pid, date, remoteDevice]) - - # replace repeated "~" with spaces - hacky way to align the "/"s - click.echo(tabulate(body, header, stralign="right").replace('~', ' ')) + click.echo(tabulate(body, header, stralign='right')) # 'clear' subcommand @consutil.command() -@click.argument('linenum') -def clear(linenum): +@click.argument('target') +def clear(target): """Clear preexisting connection to line""" - checkDevice(linenum) - linenum = str(linenum) + targetLine = getLine(target) + if not targetLine: + click.echo("Target [{}] does not exist".format(linenum)) + sys.exit(ERR_DEV) + lineNumber = targetLine[LINE_KEY] - busyDevices = getBusyDevices() - if linenum in busyDevices: - pid, _ = busyDevices[linenum] + busyLines = getBusyLines() + if lineNumber in busyLines: + pid, _ = busyLines[lineNumber] cmd = "sudo kill -SIGTERM " + pid click.echo("Sending SIGTERM to process " + pid) run_command(cmd) else: - click.echo("No process is connected to line " + linenum) + click.echo("No process is connected to line " + lineNumber) # 'connect' subcommand @consutil.command() @@ -72,15 +78,23 @@ def clear(linenum): @click.option('--devicename', '-d', is_flag=True, help="connect by name - if flag is set, interpret linenum as device name instead") def connect(target, devicename): """Connect to switch via console device - TARGET is line number or device name of switch""" - lineNumber = getLineNumber(target, devicename) - checkDevice(lineNumber) - lineNumber = str(lineNumber) + # identify the target line + targetLine = getLine(target, devicename) + if not targetLine: + click.echo("Cannot connect: target [{}] does not exist".format(target)) + sys.exit(ERR_DEV) + lineNumber = targetLine[LINE_KEY] # build and start picocom command - actBaud, _, flowBool = getConnectionInfo(lineNumber) + if BAUD_KEY in targetLine: + baud = targetLine[BAUD_KEY] + else: + click.echo("Cannot connect: line [{}] has no baud rate".format(lineNumber)) + sys.exit(ERR_CFG) + flowBool = True if FLOW_KEY in targetLine and targetLine[FLOW_KEY] == "1" else False flowCmd = "h" if flowBool else "n" quietCmd = "-q" if QUIET else "" - cmd = "sudo picocom -b {} -f {} {} {}{}".format(actBaud, flowCmd, quietCmd, DEVICE_PREFIX, lineNumber) + cmd = "sudo picocom -b {} -f {} {} {}{}".format(baud, flowCmd, quietCmd, DEVICE_PREFIX, lineNumber) proc = pexpect.spawn(cmd) proc.send("\n") @@ -106,4 +120,4 @@ def connect(target, devicename): click.echo("Cannot connect: unable to open picocom process") if __name__ == '__main__': - consutil() + consutil() \ No newline at end of file