Skip to content

Commit

Permalink
[console][show] Force refresh all lines status during show line (#1641)
Browse files Browse the repository at this point in the history
* [console][show] Force refresh all lines status during show line
* Fix UTs

Signed-off-by: Jing Kan jika@microsoft.com
  • Loading branch information
Blueve committed May 27, 2021
1 parent b616cd9 commit 18bed46
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 32 deletions.
55 changes: 37 additions & 18 deletions consutil/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,17 @@ class ConsolePortProvider(object):
The provider can let user to get console ports information.
"""

def __init__(self, db, configured_only):
def __init__(self, db, configured_only, refresh=False):
self._db = db
self._db_utils = DbUtils(db)
self._configured_only = configured_only
self._ports = []
self._init_all()
self._init_all(refresh)

def get_all(self):
"""Gets all console ports information"""
for port in self._ports:
yield ConsolePortInfo(self._db, port)
yield ConsolePortInfo(self._db_utils, port)

def get(self, target, use_device=False):
"""Gets information of a ports, the target is the line number by default"""
Expand All @@ -75,21 +76,30 @@ def get(self, target, use_device=False):
# identify the line number by searching configuration
for port in self._ports:
if search_key in port and port[search_key] == target:
return ConsolePortInfo(self._db, port)
return ConsolePortInfo(self._db_utils, port)

raise LineNotFoundError

def _init_all(self):
def _init_all(self, refresh):
config_db = self._db.cfgdb
state_db = self._db.db

# Querying CONFIG_DB to get configured console ports
keys = config_db.get_keys(CONSOLE_PORT_TABLE)
ports = []
if refresh:
busy_lines = SysInfoProvider.list_active_console_processes()
for k in keys:
port = config_db.get_entry(CONSOLE_PORT_TABLE, k)
port[LINE_KEY] = k
port[CUR_STATE_KEY] = state_db.get_all(state_db.STATE_DB, "{}|{}".format(CONSOLE_PORT_TABLE, k))
if refresh:
if k in busy_lines:
pid, date = busy_lines[k]
port[CUR_STATE_KEY] = self._db_utils.update_state(k, BUSY_FLAG, pid, date)
else:
port[CUR_STATE_KEY] = self._db_utils.update_state(k, IDLE_FLAG)
else:
port[CUR_STATE_KEY] = state_db.get_all(state_db.STATE_DB, "{}|{}".format(CONSOLE_PORT_TABLE, k))
ports.append(port)

# Querying device directory to get all available console ports
Expand All @@ -103,8 +113,8 @@ def _init_all(self):
self._ports = ports

class ConsolePortInfo(object):
def __init__(self, db, info):
self._db = db
def __init__(self, db_utils, info):
self._db_utils = db_utils
self._info = info
self._session = None

Expand Down Expand Up @@ -224,16 +234,8 @@ def refresh(self):
self._update_state(IDLE_FLAG, "", "")

def _update_state(self, state, pid, date, line_num=None):
state_db = self._db.db
line_key = "{}|{}".format(CONSOLE_PORT_TABLE, self.line_num if line_num is None else line_num)
state_db.set(state_db.STATE_DB, line_key, STATE_KEY, state)
state_db.set(state_db.STATE_DB, line_key, PID_KEY, pid)
state_db.set(state_db.STATE_DB, line_key, START_TIME_KEY, date)
self._info[CUR_STATE_KEY] = {
STATE_KEY: state,
PID_KEY: pid,
START_TIME_KEY: date
}
self._info[CUR_STATE_KEY] = self._db_utils.update_state(
self.line_num if line_num is None else line_num, state, pid, date)

class ConsoleSession(object):
"""
Expand Down Expand Up @@ -333,6 +335,23 @@ def run_command(cmd, abort=True):
sys.exit(ERR_CMD)
return output if abort else (output, error)

class DbUtils(object):
def __init__(self, db):
self._db = db
self._config_db = db.cfgdb
self._state_db = db.db

def update_state(self, line_num, state, pid="", date=""):
key = "{}|{}".format(CONSOLE_PORT_TABLE, line_num)
self._state_db.set(self._state_db.STATE_DB, key, STATE_KEY, state)
self._state_db.set(self._state_db.STATE_DB, key, PID_KEY, pid)
self._state_db.set(self._state_db.STATE_DB, key, START_TIME_KEY, date)
return {
STATE_KEY: state,
PID_KEY: pid,
START_TIME_KEY: date
}

class InvalidConfigurationError(Exception):
def __init__(self, config_key, message):
self.config_key = config_key
Expand Down
2 changes: 1 addition & 1 deletion consutil/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def consutil(db):
@click.option('--brief', '-b', metavar='<brief_mode>', required=False, is_flag=True)
def show(db, brief):
"""Show all ports and their info include available ttyUSB devices unless specified brief mode"""
port_provider = ConsolePortProvider(db, brief)
port_provider = ConsolePortProvider(db, brief, refresh=True)
ports = list(port_provider.get_all())

# sort ports for table rendering
Expand Down
67 changes: 54 additions & 13 deletions tests/console_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def test_console_port_provider_get_line_by_device_not_found(self):
def test_console_port_info_refresh_without_session(self):
db = Db()

port = ConsolePortInfo(db, { "LINE" : "1" })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1" })
port.refresh()
assert port.busy
assert port.session_pid == "223"
Expand All @@ -313,15 +313,15 @@ def test_console_port_info_refresh_without_session(self):
def test_console_port_info_refresh_without_session_idle(self):
db = Db()

port = ConsolePortInfo(db, { "LINE" : "1" })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1" })
port.refresh()
assert port.busy == False

@mock.patch('consutil.lib.SysInfoProvider.get_active_console_process_info', mock.MagicMock(return_value=("1", "223", "2020/11/2")))
def test_console_port_info_refresh_with_session(self):
db = Db()

port = ConsolePortInfo(db, { "LINE" : "1" })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1" })
port._session = ConsoleSession(port, mock.MagicMock(pid="223"))
print(port)

Expand All @@ -334,7 +334,7 @@ def test_console_port_info_refresh_with_session(self):
def test_console_port_info_refresh_with_session_line_mismatch(self):
db = Db()

port = ConsolePortInfo(db, { "LINE" : "1" })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1" })
port._session = ConsoleSession(port, mock.MagicMock(pid="223"))
print(port)

Expand All @@ -347,7 +347,7 @@ def test_console_port_info_refresh_with_session_line_mismatch(self):
def test_console_port_info_refresh_with_session_process_ended(self):
db = Db()

port = ConsolePortInfo(db, { "LINE" : "1" })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1" })
port._session = ConsoleSession(port, mock.MagicMock(pid="223"))
print(port)

Expand All @@ -356,23 +356,23 @@ def test_console_port_info_refresh_with_session_process_ended(self):

def test_console_port_info_connect_state_busy(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "CUR_STATE" : { "state" : "busy" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "CUR_STATE" : { "state" : "busy" } })

port.refresh = mock.MagicMock(return_value=None)
with pytest.raises(LineBusyError):
port.connect()

def test_console_port_info_connect_invalid_config(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "CUR_STATE" : { "state" : "idle" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "CUR_STATE" : { "state" : "idle" } })

port.refresh = mock.MagicMock(return_value=None)
with pytest.raises(InvalidConfigurationError):
port.connect()

def test_console_port_info_connect_device_busy(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })

port.refresh = mock.MagicMock(return_value=None)
mock_proc = mock.MagicMock(spec=subprocess.Popen)
Expand All @@ -384,7 +384,7 @@ def test_console_port_info_connect_device_busy(self):

def test_console_port_info_connect_connection_fail(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })

port.refresh = mock.MagicMock(return_value=None)
mock_proc = mock.MagicMock(spec=subprocess.Popen)
Expand All @@ -396,7 +396,7 @@ def test_console_port_info_connect_connection_fail(self):

def test_console_port_info_connect_success(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })

port.refresh = mock.MagicMock(return_value=None)
mock_proc = mock.MagicMock(spec=subprocess.Popen, pid="223")
Expand All @@ -409,22 +409,22 @@ def test_console_port_info_connect_success(self):

def test_console_port_info_clear_session_line_not_busy(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "idle" } })

port.refresh = mock.MagicMock(return_value=None)
assert not port.clear_session()

@mock.patch('consutil.lib.SysInfoProvider.run_command', mock.MagicMock(return_value=None))
def test_console_port_info_clear_session_with_state_db(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "busy", "pid" : "223" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "busy", "pid" : "223" } })

port.refresh = mock.MagicMock(return_value=None)
assert port.clear_session()

def test_console_port_info_clear_session_with_existing_session(self):
db = Db()
port = ConsolePortInfo(db, { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "busy" } })
port = ConsolePortInfo(DbUtils(db), { "LINE" : "1", "baud_rate" : "9600", "CUR_STATE" : { "state" : "busy" } })
port._session = ConsoleSession(port, None)
port._session.close = mock.MagicMock(return_value=None)
port.refresh = mock.MagicMock(return_value=None)
Expand Down Expand Up @@ -538,6 +538,7 @@ def setup_class(cls):
3 9600 Enabled - -
"""
@mock.patch('consutil.lib.SysInfoProvider.init_device_prefix', mock.MagicMock(return_value=None))
@mock.patch('consutil.lib.SysInfoProvider.list_active_console_processes', mock.MagicMock(return_value={ "2" : ("223", "Wed Mar 6 08:31:35 2019")}))
def test_show(self):
runner = CliRunner()
db = Db()
Expand All @@ -556,6 +557,46 @@ def test_show(self):
assert result.exit_code == 0
assert result.output == TestConsutilShow.expect_show_output

@mock.patch('consutil.lib.SysInfoProvider.init_device_prefix', mock.MagicMock(return_value=None))
@mock.patch('consutil.lib.SysInfoProvider.list_active_console_processes', mock.MagicMock(return_value={ "2" : ("223", "Wed Mar 6 08:31:35 2019")}))
def test_show_stale_idle_to_busy(self):
runner = CliRunner()
db = Db()
db.cfgdb.set_entry("CONSOLE_PORT", 1, { "remote_device" : "switch1", "baud_rate" : "9600" })
db.cfgdb.set_entry("CONSOLE_PORT", 2, { "remote_device" : "switch2", "baud_rate" : "9600" })
db.cfgdb.set_entry("CONSOLE_PORT", 3, { "baud_rate" : "9600", "flow_control" : "1" })

# use '--brief' option to avoid access system
result = runner.invoke(consutil.consutil.commands["show"], ['--brief'], obj=db)
print(result.exit_code)
print(sys.stderr, result.output)
assert result.exit_code == 0
assert result.output == TestConsutilShow.expect_show_output

@mock.patch('consutil.lib.SysInfoProvider.init_device_prefix', mock.MagicMock(return_value=None))
@mock.patch('consutil.lib.SysInfoProvider.list_active_console_processes', mock.MagicMock(return_value={ "2" : ("223", "Wed Mar 6 08:31:35 2019")}))
def test_show_stale_busy_to_idle(self):
runner = CliRunner()
db = Db()
db.cfgdb.set_entry("CONSOLE_PORT", 1, { "remote_device" : "switch1", "baud_rate" : "9600" })
db.cfgdb.set_entry("CONSOLE_PORT", 2, { "remote_device" : "switch2", "baud_rate" : "9600" })
db.cfgdb.set_entry("CONSOLE_PORT", 3, { "baud_rate" : "9600", "flow_control" : "1" })

db.db.set(db.db.STATE_DB, "CONSOLE_PORT|1", "state", "busy")
db.db.set(db.db.STATE_DB, "CONSOLE_PORT|1", "pid", "222")
db.db.set(db.db.STATE_DB, "CONSOLE_PORT|1", "start_time", "Wed Mar 6 08:31:35 2019")

db.db.set(db.db.STATE_DB, "CONSOLE_PORT|2", "state", "busy")
db.db.set(db.db.STATE_DB, "CONSOLE_PORT|2", "pid", "223")
db.db.set(db.db.STATE_DB, "CONSOLE_PORT|2", "start_time", "Wed Mar 6 08:31:35 2019")

# use '--brief' option to avoid access system
result = runner.invoke(consutil.consutil.commands["show"], ['--brief'], obj=db)
print(result.exit_code)
print(sys.stderr, result.output)
assert result.exit_code == 0
assert result.output == TestConsutilShow.expect_show_output

class TestConsutilConnect(object):
@classmethod
def setup_class(cls):
Expand Down

0 comments on commit 18bed46

Please sign in to comment.