Skip to content

[console][show] Force refresh all lines status during show line #1641

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

Merged
merged 2 commits into from
May 27, 2021
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
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