diff --git a/consutil/lib.py b/consutil/lib.py index 56f4a6359631..e2ebf1da06c6 100644 --- a/consutil/lib.py +++ b/consutil/lib.py @@ -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""" @@ -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 @@ -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 @@ -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): """ @@ -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 diff --git a/consutil/main.py b/consutil/main.py index 9fc41cded527..be6ac8af2dce 100644 --- a/consutil/main.py +++ b/consutil/main.py @@ -34,7 +34,7 @@ def consutil(db): @click.option('--brief', '-b', metavar='', 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 diff --git a/tests/console_test.py b/tests/console_test.py index 3df59adbdfd0..8161eda7ddf6 100644 --- a/tests/console_test.py +++ b/tests/console_test.py @@ -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" @@ -313,7 +313,7 @@ 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 @@ -321,7 +321,7 @@ def test_console_port_info_refresh_without_session_idle(self): 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) @@ -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) @@ -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) @@ -356,7 +356,7 @@ 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): @@ -364,7 +364,7 @@ def test_console_port_info_connect_state_busy(self): 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): @@ -372,7 +372,7 @@ def test_console_port_info_connect_invalid_config(self): 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) @@ -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) @@ -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") @@ -409,7 +409,7 @@ 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() @@ -417,14 +417,14 @@ def test_console_port_info_clear_session_line_not_busy(self): @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) @@ -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() @@ -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):