Skip to content

Commit 6f9a736

Browse files
authored
[console] improve picocom launch efficiency (#376)
replace pexpect calls with direct function call and exec while preserving the original logic <!-- Please make sure you've read and understood our contributing guidelines: https://github.com/Azure/SONiC/blob/gh-pages/CONTRIBUTING.md CODE_OF_CONDUCT.md LICENSE README.md SECURITY.md SUPPORT.md azure-pipelines failure_prs.log scripts skip_prs.log Make sure all your commits include a signature generated with `git commit -s` ** If this is a bug fix, make sure your description includes "closes #xxxx", "fixes #xxxx" or "resolves #xxxx" so that GitHub automatically closes the related issue when the PR is merged. If you are adding/modifying/removing any command or utility script, please also make sure to add/modify/remove any unit tests from the tests directory as appropriate. If you are modifying or removing an existing 'show', 'config' or 'sonic-clear' subcommand, or you are adding a new subcommand, please make sure you also update the Command Line Reference Guide (doc/Command-Reference.md) to reflect your changes. Please provide the following information: --> #### What I did streamlined the picocom launch process #### How I did it replaced the first pexpect call with a direct function call wrapped the picocom launch in a bash script replaced the second pexpect call with execvp #### How to verify it first, launch console app just like before then, use ps -ef to make sure "connect line" is no longer a standalone process #### Previous command output (if the output of a command-line utility has changed) connect line X or just reverse ssh Signed-off-by: Sonic Build Admin <sonicbld@microsoft.com>
1 parent 265466d commit 6f9a736

4 files changed

Lines changed: 65 additions & 50 deletions

File tree

connect/main.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import configparser
22
import os
3-
import pexpect
43
import sys
54

65
import click
@@ -66,15 +65,6 @@ def get_command(self, ctx, cmd_name):
6665
return click.Group.get_command(self, ctx, matches[0])
6766
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
6867

69-
def run_command(command, display_cmd=False):
70-
if display_cmd:
71-
click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green'))
72-
73-
proc = pexpect.spawn(command)
74-
proc.interact()
75-
proc.close()
76-
return proc.exitstatus
77-
7868
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?'])
7969

8070
#
@@ -95,8 +85,8 @@ def connect():
9585
@click.option('--devicename', '-d', is_flag=True, help="connect by name - if flag is set, interpret target as device name instead")
9686
def line(target, devicename):
9787
"""Connect to line LINENUM via serial connection"""
98-
cmd = "consutil connect {}".format("--devicename " if devicename else "") + str(target)
99-
sys.exit(run_command(cmd))
88+
from consutil.lib import console_connect
89+
console_connect(target, use_device=devicename)
10090

10191
#
10292
# 'device' command ("connect device")
@@ -105,8 +95,8 @@ def line(target, devicename):
10595
@click.argument('devicename')
10696
def device(devicename):
10797
"""Connect to device DEVICENAME via serial connection"""
108-
cmd = "consutil connect -d " + devicename
109-
sys.exit(run_command(cmd))
98+
from consutil.lib import console_connect
99+
console_connect(devicename, use_device=True)
110100

111101
if __name__ == '__main__':
112102
connect()

consutil/lib.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,50 @@ def update_state(self, line_num, state, pid="", date=""):
384384
START_TIME_KEY: date
385385
}
386386

387+
388+
def initialize_console_runtime(db):
389+
"""Validate console feature state and initialize device prefix."""
390+
config_db = db.cfgdb
391+
data = config_db.get_entry(CONSOLE_SWITCH_TABLE, FEATURE_KEY)
392+
if FEATURE_ENABLED_KEY not in data or data[FEATURE_ENABLED_KEY] == "no":
393+
click.echo("Console switch feature is disabled")
394+
sys.exit(ERR_DISABLE)
395+
396+
SysInfoProvider.init_device_prefix()
397+
398+
399+
def console_connect(target, use_device=False, db=None):
400+
"""Connect to a console port. Can be called directly without Click context."""
401+
if db is None:
402+
from utilities_common.db import Db
403+
db = Db()
404+
initialize_console_runtime(db)
405+
406+
port_provider = ConsolePortProvider(db, configured_only=False)
407+
try:
408+
target_port = port_provider.get(target, use_device=use_device)
409+
except LineNotFoundError:
410+
click.echo("Cannot connect: target [{}] does not exist".format(target))
411+
sys.exit(ERR_DEV)
412+
413+
line_num = target_port.line_num
414+
415+
try:
416+
session = target_port.connect()
417+
except LineBusyError:
418+
click.echo("Cannot connect: line [{}] is busy".format(line_num))
419+
sys.exit(ERR_BUSY)
420+
except InvalidConfigurationError as cfg_err:
421+
click.echo("Cannot connect: {}".format(cfg_err.message))
422+
sys.exit(ERR_CFG)
423+
except ConnectionFailedError:
424+
click.echo("Cannot connect: unable to open picocom process")
425+
sys.exit(ERR_DEV)
426+
427+
click.echo("Successful connection to line [{}]\nPress ^{} ^X to disconnect"
428+
.format(line_num, target_port.escape_char.upper() if target_port.escape_char is not None else "A"))
429+
session.interact()
430+
387431
class InvalidConfigurationError(Exception):
388432
def __init__(self, config_key, message):
389433
self.config_key = config_key

consutil/main.py

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from tabulate import tabulate
1616
from .lib import *
17+
from .lib import initialize_console_runtime, console_connect
1718
except ImportError as e:
1819
raise ImportError("%s - required module not found" % str(e))
1920

@@ -22,13 +23,7 @@
2223
@clicommon.pass_db
2324
def consutil(db):
2425
"""consutil - Command-line utility for interacting with switches via console device"""
25-
config_db = db.cfgdb
26-
data = config_db.get_entry(CONSOLE_SWITCH_TABLE, FEATURE_KEY)
27-
if FEATURE_ENABLED_KEY not in data or data[FEATURE_ENABLED_KEY] == "no":
28-
click.echo("Console switch feature is disabled")
29-
sys.exit(ERR_DISABLE)
30-
31-
SysInfoProvider.init_device_prefix()
26+
initialize_console_runtime(db)
3227

3328

3429
# 'show' subcommand
@@ -116,7 +111,6 @@ def clear(db, target, devicename):
116111
else:
117112
click.echo("Cleared line")
118113

119-
120114
# 'connect' subcommand
121115
@consutil.command()
122116
@clicommon.pass_db
@@ -125,33 +119,7 @@ def clear(db, target, devicename):
125119
help="connect by name - if flag is set, interpret target as device name instead")
126120
def connect(db, target, devicename):
127121
"""Connect to switch via console device - TARGET is line number or device name of switch"""
128-
# identify the target line
129-
port_provider = ConsolePortProvider(db, configured_only=False)
130-
try:
131-
target_port = port_provider.get(target, use_device=devicename)
132-
except LineNotFoundError:
133-
click.echo("Cannot connect: target [{}] does not exist".format(target))
134-
sys.exit(ERR_DEV)
135-
136-
line_num = target_port.line_num
137-
138-
# connect
139-
try:
140-
session = target_port.connect()
141-
except LineBusyError:
142-
click.echo("Cannot connect: line [{}] is busy".format(line_num))
143-
sys.exit(ERR_BUSY)
144-
except InvalidConfigurationError as cfg_err:
145-
click.echo("Cannot connect: {}".format(cfg_err.message))
146-
sys.exit(ERR_CFG)
147-
except ConnectionFailedError:
148-
click.echo("Cannot connect: unable to open picocom process")
149-
sys.exit(ERR_DEV)
150-
151-
# interact
152-
click.echo("Successful connection to line [{}]\nPress ^{} ^X to disconnect"
153-
.format(line_num, target_port.escape_char.upper() if target_port.escape_char is not None else "A"))
154-
session.interact()
122+
console_connect(target, use_device=devicename, db=db)
155123

156124

157125
if __name__ == '__main__':

tests/console_test.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from click.testing import CliRunner
1616
from utilities_common.db import Db
1717
from consutil.lib import ConsolePortProvider, ConsolePortInfo, ConsoleSession, SysInfoProvider, DbUtils, \
18-
InvalidConfigurationError, LineBusyError, LineNotFoundError, ConnectionFailedError
18+
InvalidConfigurationError, LineBusyError, LineNotFoundError, ConnectionFailedError, console_connect
1919
from sonic_py_common import device_info
2020
from jsonpatch import JsonPatchConflict
2121

@@ -1368,6 +1368,19 @@ def test_connect_default_escape_after_clear(self):
13681368
assert result.exit_code == 0
13691369
assert result.output == "Successful connection to line [1]\nPress ^A ^X to disconnect\n"
13701370

1371+
@mock.patch('consutil.lib.SysInfoProvider.list_console_ttys', mock.MagicMock(return_value=["/dev/ttyUSB1"]))
1372+
@mock.patch('consutil.lib.SysInfoProvider.init_device_prefix', mock.MagicMock(return_value=None))
1373+
@mock.patch('consutil.lib.ConsolePortInfo.connect',
1374+
mock.MagicMock(return_value=mock.MagicMock(interact=mock.MagicMock(return_value=None))))
1375+
def test_console_connect_without_db(self):
1376+
prepared_db = Db()
1377+
prepared_db.cfgdb.set_entry("CONSOLE_SWITCH", "console_mgmt", {"enabled": "yes"})
1378+
prepared_db.cfgdb.set_entry("CONSOLE_PORT", 1, {"remote_device": "switch1", "baud_rate": "9600"})
1379+
1380+
with mock.patch('utilities_common.db.Db', return_value=prepared_db) as mock_db_cls:
1381+
console_connect('1')
1382+
mock_db_cls.assert_called_once()
1383+
13711384

13721385
class TestConsutilClear(object):
13731386
@classmethod

0 commit comments

Comments
 (0)