-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
always resume automated sessions via remote
- Loading branch information
Showing
6 changed files
with
148 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
# This file is part of Checkbox. | ||
# | ||
# Copyright 2017-2019 Canonical Ltd. | ||
# Copyright 2017-2023 Canonical Ltd. | ||
# Written by: | ||
# Maciej Kisielewski <[email protected]> | ||
# | ||
# Checkbox is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License version 3, | ||
|
@@ -20,11 +19,15 @@ | |
functionality. | ||
""" | ||
import gettext | ||
import json | ||
import logging | ||
import os | ||
import socket | ||
import sys | ||
from checkbox_ng import app_context | ||
from plainbox.impl.config import Configuration | ||
from plainbox.impl.secure.sudo_broker import is_passwordless_sudo | ||
from plainbox.impl.session.assistant import ResumeCandidate | ||
from plainbox.impl.session.remote_assistant import RemoteSessionAssistant | ||
from plainbox.impl.session.restart import RemoteDebRestartStrategy | ||
from plainbox.impl.session.restart import RemoteSnappyRestartStrategy | ||
|
@@ -36,7 +39,6 @@ | |
|
||
|
||
class SessionAssistantAgent(rpyc.Service): | ||
|
||
session_assistant = None | ||
controlling_controller_conn = None | ||
controller_blaster = None | ||
|
@@ -58,10 +60,14 @@ def exposed_register_controller_blaster(self, callable): | |
def on_connect(self, conn): | ||
try: | ||
if SessionAssistantAgent.controller_blaster: | ||
msg = 'Forcefully disconnected by new controller from {}:{}'.format( | ||
conn._config['endpoints'][1][0], conn._config['endpoints'][1][1]) | ||
msg = "Forcefully disconnected by new controller from {}:{}".format( | ||
conn._config["endpoints"][1][0], | ||
conn._config["endpoints"][1][1], | ||
) | ||
SessionAssistantAgent.controller_blaster(msg) | ||
old_controller = SessionAssistantAgent.controlling_controller_conn | ||
old_controller = ( | ||
SessionAssistantAgent.controlling_controller_conn | ||
) | ||
if old_controller is not None: | ||
old_controller.close() | ||
SessionAssistantAgent.controller_blaster = None | ||
|
@@ -82,7 +88,7 @@ def on_disconnect(self, conn): | |
self.controlling_controller_conn = None | ||
|
||
|
||
class RemoteAgent(): | ||
class RemoteAgent: | ||
""" | ||
Run checkbox instance as a agent | ||
|
@@ -91,76 +97,95 @@ class RemoteAgent(): | |
part should be run on system-under-test. | ||
""" | ||
|
||
name = 'agent' | ||
name = "agent" | ||
|
||
def invoked(self, ctx): | ||
if os.geteuid(): | ||
raise SystemExit(_("Checkbox agent must be run by root!")) | ||
if not is_passwordless_sudo(): | ||
raise SystemExit( | ||
_("System is not configured to run sudo without a password!")) | ||
_("System is not configured to run sudo without a password!") | ||
) | ||
if ctx.args.resume: | ||
msg = ( | ||
"--resume is deprecated and will be removed soon. " | ||
"Automated sessions are now always resumed. " | ||
"Manual sessions can be resumed from the welcome screen." | ||
) | ||
_logger.warning(msg) | ||
|
||
agent_port = ctx.args.port | ||
|
||
# Check if able to connect to the agent port as indicator of there | ||
# already being a agent running | ||
def agent_port_open(): | ||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
sock.settimeout(0.5) | ||
result = sock.connect_ex(('127.0.0.1', agent_port)) | ||
result = sock.connect_ex(("127.0.0.1", agent_port)) | ||
sock.close() | ||
return result | ||
|
||
if agent_port_open() == 0: | ||
raise SystemExit(_("Found port {} is open. Is Checkbox agent" | ||
" already running?").format(agent_port)) | ||
raise SystemExit( | ||
_( | ||
"Found port {} is open. Is Checkbox agent" | ||
" already running?" | ||
).format(agent_port) | ||
) | ||
|
||
SessionAssistantAgent.session_assistant = RemoteSessionAssistant( | ||
lambda s: [sys.argv[0] + 'agent']) | ||
snap_data = os.getenv('SNAP_DATA') | ||
snap_rev = os.getenv('SNAP_REVISION') | ||
remote_restart_strategy_debug = os.getenv('REMOTE_RESTART_DEBUG') | ||
if (snap_data and snap_rev) or ctx.args.resume: | ||
if remote_restart_strategy_debug: | ||
strategy = RemoteSnappyRestartStrategy(debug=True) | ||
else: | ||
strategy = RemoteSnappyRestartStrategy() | ||
if os.path.exists(strategy.session_resume_filename): | ||
with open(strategy.session_resume_filename, 'rt') as f: | ||
session_id = f.readline() | ||
SessionAssistantAgent.session_assistant.resume_by_id( | ||
session_id) | ||
elif ctx.args.resume: | ||
# XXX: explicitly passing None to not have to bump Remote API | ||
# TODO: remove on the next Remote API bump | ||
SessionAssistantAgent.session_assistant.resume_by_id(None) | ||
else: | ||
_logger.info("RemoteDebRestartStrategy") | ||
if remote_restart_strategy_debug: | ||
strategy = RemoteDebRestartStrategy(debug=True) | ||
else: | ||
strategy = RemoteDebRestartStrategy() | ||
if os.path.exists(strategy.session_resume_filename): | ||
with open(strategy.session_resume_filename, 'rt') as f: | ||
session_id = f.readline() | ||
_logger.info( | ||
"RemoteDebRestartStrategy resume_by_id %r", session_id) | ||
lambda s: [sys.argv[0] + "agent"] | ||
) | ||
|
||
# the agent is meant to be run only as a service, | ||
# and we always resume if the session was automated, | ||
# so we don't need to encode check whether we should resume | ||
|
||
sessions = list(ctx.sa.get_resumable_sessions()) | ||
if sessions: | ||
# the sessions are ordered by time, so the first one is the most | ||
# recent one | ||
if is_the_session_noninteractive(sessions[0]): | ||
SessionAssistantAgent.session_assistant.resume_by_id( | ||
session_id) | ||
sessions[0].id | ||
) | ||
|
||
self._server = ThreadedServer( | ||
SessionAssistantAgent, | ||
port=agent_port, | ||
protocol_config={ | ||
"allow_all_attrs": True, | ||
"allow_setattr": True, | ||
"sync_request_timeout": 1, | ||
"propagate_SystemExit_locally": True | ||
"propagate_SystemExit_locally": True, | ||
}, | ||
) | ||
SessionAssistantAgent.session_assistant.terminate_cb = ( | ||
self._server.close) | ||
self._server.close | ||
) | ||
self._server.start() | ||
|
||
def register_arguments(self, parser): | ||
parser.add_argument('--resume', action='store_true', help=_( | ||
"resume last session")) | ||
parser.add_argument('--port', type=int, default=18871, help=_( | ||
"port to listen on")) | ||
parser.add_argument( | ||
"--resume", action="store_true", help=_("resume last session") | ||
) | ||
parser.add_argument( | ||
"--port", type=int, default=18871, help=_("port to listen on") | ||
) | ||
|
||
|
||
def is_the_session_noninteractive( | ||
resumable_session: "ResumeCandidate", | ||
) -> bool: | ||
""" | ||
Check if given session is non-interactive. | ||
To determine that we need to take the original launcher that had been used | ||
when the session was started, recreate it as a proper Launcher object, and | ||
check if it's in fact non-interactive. | ||
""" | ||
# app blob is a bytes string with a utf-8 encoded json | ||
# let's decode it and parse it as json | ||
app_blob = json.loads(resumable_session.metadata.app_blob.decode("utf-8")) | ||
launcher = Configuration.from_text(app_blob["launcher"], "resumed session") | ||
return launcher.sections["ui"].get("type") == "silent" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.