Skip to content

Commit

Permalink
Connect line implementation (and show line output change) (sonic-net#295
Browse files Browse the repository at this point in the history
)
  • Loading branch information
cawand authored and jleveque committed Aug 9, 2018
1 parent 506712e commit c217fe5
Showing 3 changed files with 117 additions and 37 deletions.
31 changes: 16 additions & 15 deletions connect/main.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import click
import errno
import os
import pexpect
import subprocess
import sys
from click_default_group import DefaultGroup
@@ -85,18 +86,8 @@ 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
elif output:
click.echo(output.rstrip('\n'))

rc = proc.poll()
if rc != 0:
sys.exit(rc)
proc = pexpect.spawn(command)
proc.interact()

CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?'])

@@ -116,9 +107,19 @@ def connect():
@connect.command('line')
@click.argument('linenum')
def line(linenum):
"""Connect to line via serial connection"""
# TODO: Stub
return
"""Connect to line LINENUM via serial connection"""
cmd = "consutil connect " + linenum
run_command(cmd)

#
# 'device' command ("connect device")
#
@connect.command('device')
@click.argument('devicename')
def device(devicename):
"""Connect to device DEVICENAME via serial connection"""
cmd = "consutil connect -d " + devicename
run_command(cmd)

if __name__ == '__main__':
connect()
70 changes: 56 additions & 14 deletions consutil/lib.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
try:
import click
import re
import swsssdk
import subprocess
import sys
except ImportError as e:
@@ -18,6 +19,22 @@
ERR_CMD = 1
ERR_DEV = 2

CONSOLE_PORT_TABLE = "CONSOLE_PORT"
BAUD_KEY = "baud_rate"
DEVICE_KEY = "remote_device"
FLOW_KEY = "flow_control"
DEFAULT_BAUD = "9600"

# QUIET == True => picocom will not output any messages, and pexpect will wait for console
# switch login or command line to let user interact with shell
# Downside: if console switch output ever does not match DEV_READY_MSG, program will think connection failed
# QUIET == False => picocom will output messages - welcome message is caught by pexpect, so successful
# connection will always lead to user interacting with shell
# Downside: at end of session, picocom will print exit message, exposing picocom to user
QUIET = False
DEV_READY_MSG = r"([Ll]ogin:|[Pp]assword:|[$>#])" # login prompt or command line prompt
TIMEOUT_SEC = 0.2

# runs command, exit if stderr is written to, returns stdout otherwise
# input: cmd (str), output: output of cmd (str)
def run_command(cmd):
@@ -57,8 +74,8 @@ def getBusyDevices():

# 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 anything of form: Xxx Xxx ( 0)or(00) 00:00:00 0000
regexDate = r"([A-Z][a-z]{2} [A-Z][a-z]{2} [\d ]\d \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
@@ -75,16 +92,41 @@ def getBusyDevices():
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 = run_command(cmd)
# 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))

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
# 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()

match = re.match(r"^speed (\d+) baud;", output)
if match != None:
return match.group(1)
else:
click.echo("Unable to determine baud rate")
return ""
devices = getAllDevices()
linenums = list(map(lambda dev: dev[len(DEVICE_PREFIX):], devices))

for linenum in linenums:
entry = config_db.get_entry(CONSOLE_PORT_TABLE, linenum)
if DEVICE_KEY in entry and entry[DEVICE_KEY] == target:
return linenum

click.echo("Device {} does not exist".format(target))
sys.exit(ERR_DEV)
return ""
53 changes: 45 additions & 8 deletions consutil/main.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@

try:
import click
import os
import pexpect
import re
import subprocess
from tabulate import tabulate
@@ -15,7 +17,7 @@

@click.group()
def consutil():
"""consutil - Command-line utility for interacting with switchs via console device"""
"""consutil - Command-line utility for interacting with switches via console device"""

if os.geteuid() != 0:
print "Root privileges are required for this operation"
@@ -28,7 +30,7 @@ def show():
devices = getAllDevices()
busyDevices = getBusyDevices()

header = ["Line", "Baud", "PID", "Start Time"]
header = ["Line", "Actual/Configured Baud", "PID", "Start Time"]
body = []
for device in devices:
lineNum = device[11:]
@@ -38,10 +40,13 @@ def show():
if lineNum in busyDevices:
pid, date = busyDevices[lineNum]
busy = "*"
baud = getBaud(lineNum)
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])

click.echo(tabulate(body, header, stralign="right"))
# replace repeated "~" with spaces - hacky way to align the "/"s
click.echo(tabulate(body, header, stralign="right").replace('~', ' '))

# 'clear' subcommand
@consutil.command()
@@ -62,10 +67,42 @@ def clear(linenum):

# 'connect' subcommand
@consutil.command()
@click.argument('linenum')
def connect(linenum):
"""Connect to switch via console device"""
click.echo("connect linenum")
@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)

# build and start picocom command
actBaud, _, flowBool = getConnectionInfo(lineNumber)
flowCmd = "h" if flowBool else "n"
quietCmd = "-q" if QUIET else ""
cmd = "sudo picocom -b {} -f {} {} {}{}".format(actBaud, flowCmd, quietCmd, DEVICE_PREFIX, lineNumber)
proc = pexpect.spawn(cmd)
proc.send("\n")

if QUIET:
readyMsg = DEV_READY_MSG
else:
readyMsg = "Terminal ready" # picocom ready message
busyMsg = "Resource temporarily unavailable" # picocom busy message

# interact with picocom or print error message, depending on pexpect output
index = proc.expect([readyMsg, busyMsg, pexpect.EOF, pexpect.TIMEOUT], timeout=TIMEOUT_SEC)
if index == 0: # terminal ready
click.echo("Successful connection to line {}\nPress ^A ^X to disconnect".format(lineNumber))
if QUIET:
# prints picocom output up to and including readyMsg
click.echo(proc.before + proc.match.group(0), nl=False)
proc.interact()
if QUIET:
click.echo("\nTerminating...")
elif index == 1: # resource is busy
click.echo("Cannot connect: line {} is busy".format(lineNumber))
else: # process reached EOF or timed out
click.echo("Cannot connect: unable to open picocom process")

if __name__ == '__main__':
consutil()

0 comments on commit c217fe5

Please sign in to comment.