Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[consutil] Remove actual baud and refactor lib for future change #1130

Merged
merged 1 commit into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 33 additions & 61 deletions consutil/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

ERR_CMD = 1
ERR_DEV = 2
ERR_CFG = 3

CONSOLE_PORT_TABLE = "CONSOLE_PORT"
LINE_KEY = "LINE"
Expand Down Expand Up @@ -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')
Expand All @@ -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
74 changes: 44 additions & 30 deletions consutil/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,66 +21,80 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we need to add some more check there before sending that kill signal.

What if we have a pid logged in db but somehow process quit didn't clear the db. However, some critical process took the PID. Blindly sending the kill could be dangerous. @jleveque what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comments doesn't block this PR. If change needed, the change can come with a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yxieca: Adding a check to ensure the PID is still assigned to the expected process is probably a good idea to add in a future PR. It definitely wouldn't hurt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, totally agree.

Currently, it is safe to clear it by the PID because the PID were extract from PS command.
The check will be necessary after I introduced STATE_DB.
I will add checks in future PR. Thanks for comments

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()
@click.argument('target')
@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")

Expand All @@ -106,4 +120,4 @@ def connect(target, devicename):
click.echo("Cannot connect: unable to open picocom process")

if __name__ == '__main__':
consutil()
consutil()