Merge ~sylvain-pineau/checkbox-ng:remote-interact-fixes-rebased into checkbox-ng:master
- Git
- lp:~sylvain-pineau/checkbox-ng
- remote-interact-fixes-rebased
- Merge into master
Proposed by
Sylvain Pineau
Status: | Merged |
---|---|
Approved by: | Sylvain Pineau |
Approved revision: | 4ec41328950ead893c01145286984dea0cc1b2ab |
Merged at revision: | 2d621a85f3a798674f90f08563edd4f06f091e6b |
Proposed branch: | ~sylvain-pineau/checkbox-ng:remote-interact-fixes-rebased |
Merge into: | checkbox-ng:master |
Diff against target: |
268 lines (+101/-52) 2 files modified
checkbox_ng/launcher/master.py (+78/-34) plainbox/impl/session/remote_assistant.py (+23/-18) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sylvain Pineau (community) | Approve | ||
Review via email: mp+374940@code.launchpad.net |
Commit message
Description of the change
Multiple fixes to support all interactions type for manual jobs (skip, rerun).
Fix user interact types jobs where command output was not streamed resulting in a re-connection after 30s.
To post a comment you must log in.
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote : | # |
review:
Needs Resubmitting
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote : | # |
self-approved
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/checkbox_ng/launcher/master.py b/checkbox_ng/launcher/master.py |
2 | index 818e78d..82dc20d 100644 |
3 | --- a/checkbox_ng/launcher/master.py |
4 | +++ b/checkbox_ng/launcher/master.py |
5 | @@ -19,7 +19,9 @@ |
6 | This module contains implementation of the master end of the remote execution |
7 | functionality. |
8 | """ |
9 | +import getpass |
10 | import gettext |
11 | +import ipaddress |
12 | import logging |
13 | import os |
14 | import select |
15 | @@ -43,7 +45,7 @@ from checkbox_ng.urwid_ui import CategoryBrowser |
16 | from checkbox_ng.urwid_ui import ReRunBrowser |
17 | from checkbox_ng.urwid_ui import interrupt_dialog |
18 | from checkbox_ng.urwid_ui import resume_dialog |
19 | -from checkbox_ng.launcher.run import NormalUI |
20 | +from checkbox_ng.launcher.run import NormalUI, ReRunJob |
21 | from checkbox_ng.launcher.stages import MainLoopStage |
22 | from checkbox_ng.launcher.stages import ReportsStage |
23 | _ = gettext.gettext |
24 | @@ -122,6 +124,7 @@ class RemoteMaster(Command, ReportsStage, MainLoopStage): |
25 | self._is_bootstrapping = False |
26 | self._target_host = ctx.args.host |
27 | self._sudo_provider = None |
28 | + self._normal_user = '' |
29 | self.launcher = DefaultLauncherDefinition() |
30 | if ctx.args.launcher: |
31 | expanded_path = os.path.expanduser(ctx.args.launcher) |
32 | @@ -131,11 +134,18 @@ class RemoteMaster(Command, ReportsStage, MainLoopStage): |
33 | with open(expanded_path, 'rt') as f: |
34 | self._launcher_text = f.read() |
35 | self.launcher.read_string(self._launcher_text) |
36 | + if 'local' in ctx.args.host: |
37 | + ctx.args.host = '127.0.0.1' |
38 | + if ipaddress.ip_address(ctx.args.host).is_loopback: |
39 | + self._normal_user = getpass.getuser() |
40 | + if ctx.args.user: |
41 | + self._normal_user = ctx.args.user |
42 | timeout = 600 |
43 | deadline = time.time() + timeout |
44 | port = ctx.args.port |
45 | - print(_("Connecting to {}:{}. Timeout: {}s").format( |
46 | - ctx.args.host, port, timeout)) |
47 | + if not ipaddress.ip_address(ctx.args.host).is_loopback: |
48 | + print(_("Connecting to {}:{}. Timeout: {}s").format( |
49 | + ctx.args.host, port, timeout)) |
50 | while time.time() < deadline: |
51 | try: |
52 | self.connect_and_run(ctx.args.host, port) |
53 | @@ -218,6 +228,7 @@ class RemoteMaster(Command, ReportsStage, MainLoopStage): |
54 | _logger.info("master: Starting new session.") |
55 | configuration = dict() |
56 | configuration['launcher'] = self._launcher_text |
57 | + configuration['normal_user'] = self._normal_user |
58 | |
59 | tps = self.sa.start_session(configuration) |
60 | _logger.debug("master: Session started. Available TPs:\n%s", |
61 | @@ -295,6 +306,8 @@ class RemoteMaster(Command, ReportsStage, MainLoopStage): |
62 | "launcher definition file to use")) |
63 | parser.add_argument('--port', type=int, default=18871, help=_( |
64 | "port to connect to")) |
65 | + parser.add_argument('-u', '--user', help=_( |
66 | + "normal user to run non-root jobs")) |
67 | |
68 | def _handle_interrupt(self): |
69 | """ |
70 | @@ -477,37 +490,68 @@ class RemoteMaster(Command, ReportsStage, MainLoopStage): |
71 | print(_("Category: {0}").format(job['category_name'])) |
72 | SimpleUI.horiz_line() |
73 | next_job = False |
74 | - for interaction in self.sa.run_job(job['id']): |
75 | - if interaction.kind == 'sudo_input': |
76 | - self.sa.save_password( |
77 | - self._sudo_provider.encrypted_password) |
78 | - if interaction.kind == 'purpose': |
79 | - SimpleUI.description(_('Purpose:'), interaction.message) |
80 | - elif interaction.kind in ['description', 'steps']: |
81 | - SimpleUI.description(_('Steps:'), interaction.message) |
82 | - if job['command'] is None: |
83 | - cmd = 'run' |
84 | - else: |
85 | - cmd = SimpleUI(None).wait_for_interaction_prompt(None) |
86 | - if cmd == 'skip': |
87 | + while next_job is False: |
88 | + for interaction in self.sa.run_job(job['id']): |
89 | + if interaction.kind == 'sudo_input': |
90 | + self.sa.save_password( |
91 | + self._sudo_provider.encrypted_password) |
92 | + if interaction.kind == 'purpose': |
93 | + SimpleUI.description(_('Purpose:'), |
94 | + interaction.message) |
95 | + elif interaction.kind == 'description': |
96 | + SimpleUI.description(_('Description:'), |
97 | + interaction.message) |
98 | + if job['command'] is None: |
99 | + cmd = 'run' |
100 | + else: |
101 | + cmd = SimpleUI( |
102 | + None).wait_for_interaction_prompt(None) |
103 | + if cmd == 'skip': |
104 | + next_job = True |
105 | + self.sa.remember_users_response(cmd) |
106 | + self.wait_for_job(dont_finish=True) |
107 | + elif interaction.kind in 'steps': |
108 | + SimpleUI.description(_('Steps:'), interaction.message) |
109 | + if job['command'] is None: |
110 | + cmd = 'run' |
111 | + else: |
112 | + cmd = SimpleUI( |
113 | + None).wait_for_interaction_prompt(None) |
114 | + if cmd == 'skip': |
115 | + next_job = True |
116 | + self.sa.remember_users_response(cmd) |
117 | + elif interaction.kind == 'verification': |
118 | + self.wait_for_job(dont_finish=True) |
119 | + if interaction.message: |
120 | + SimpleUI.description( |
121 | + _('Verification:'), interaction.message) |
122 | + JobAdapter = namedtuple('job_adapter', ['command']) |
123 | + job_lite = JobAdapter(job['command']) |
124 | + try: |
125 | + cmd = SimpleUI(None)._interaction_callback( |
126 | + job_lite, interaction.extra._builder) |
127 | + self.sa.remember_users_response(cmd) |
128 | + self.finish_job( |
129 | + interaction.extra._builder.get_result()) |
130 | + next_job = True |
131 | + break |
132 | + except ReRunJob: |
133 | + next_job = False |
134 | + self.sa.rerun_job( |
135 | + job['id'], |
136 | + interaction.extra._builder.get_result()) |
137 | + break |
138 | + elif interaction.kind == 'comment': |
139 | + new_comment = input(SimpleUI.C.BLUE( |
140 | + _('Please enter your comments:') + '\n')) |
141 | + self.sa.remember_users_response(new_comment + '\n') |
142 | + elif interaction.kind == 'skip': |
143 | + self.finish_job( |
144 | + interaction.extra._builder.get_result()) |
145 | next_job = True |
146 | - self.sa.remember_users_response(cmd) |
147 | - elif interaction.kind == 'verification': |
148 | - self.wait_for_job(dont_finish=True) |
149 | - if interaction.message: |
150 | - SimpleUI.description( |
151 | - _('Verification:'), interaction.message) |
152 | - JobAdapter = namedtuple('job_adapter', ['command']) |
153 | - job = JobAdapter(job['command']) |
154 | - cmd = SimpleUI(None)._interaction_callback( |
155 | - job, interaction.extra) |
156 | - self.sa.remember_users_response(cmd) |
157 | - self.finish_job(interaction.extra.get_result()) |
158 | - next_job = True |
159 | - elif interaction.kind == 'comment': |
160 | - new_comment = input(SimpleUI.C.BLUE( |
161 | - _('Please enter your comments:') + '\n')) |
162 | - self.sa.remember_users_response(new_comment + '\n') |
163 | + break |
164 | + else: |
165 | + self.wait_for_job() |
166 | + break |
167 | if next_job: |
168 | continue |
169 | - self.wait_for_job() |
170 | diff --git a/plainbox/impl/session/remote_assistant.py b/plainbox/impl/session/remote_assistant.py |
171 | index 907bce2..bef07f3 100644 |
172 | --- a/plainbox/impl/session/remote_assistant.py |
173 | +++ b/plainbox/impl/session/remote_assistant.py |
174 | @@ -25,12 +25,11 @@ import time |
175 | import sys |
176 | from collections import namedtuple |
177 | from threading import Thread, Lock |
178 | -from subprocess import DEVNULL, CalledProcessError, check_call, check_output |
179 | +from subprocess import CalledProcessError, check_output |
180 | |
181 | from plainbox.impl.execution import UnifiedRunner |
182 | from plainbox.impl.session.assistant import SessionAssistant |
183 | from plainbox.impl.session.assistant import SA_RESTARTABLE |
184 | -from plainbox.impl.session.jobs import InhibitionCause |
185 | from plainbox.impl.secure.sudo_broker import SudoBroker, EphemeralKey |
186 | from plainbox.impl.secure.sudo_broker import is_passwordless_sudo |
187 | from plainbox.impl.secure.sudo_broker import validate_pass |
188 | @@ -210,7 +209,6 @@ class RemoteSessionAssistant(): |
189 | return self._prepare_display_without_psutil() |
190 | if ("DISPLAY" in p_environ and p_user != 'gdm'): # gdm uses :1024 |
191 | return {'DISPLAY': p_environ['DISPLAY']} |
192 | - break |
193 | |
194 | @allowed_when(Idle) |
195 | def start_session(self, configuration): |
196 | @@ -229,6 +227,8 @@ class RemoteSessionAssistant(): |
197 | self._sa.load_providers() |
198 | |
199 | self._normal_user = self._launcher.normal_user |
200 | + if configuration['normal_user']: |
201 | + self._normal_user = configuration['normal_user'] |
202 | pass_provider = (None if self._passwordless_sudo else |
203 | self.get_decrypted_password) |
204 | runner_kwargs = { |
205 | @@ -292,6 +292,13 @@ class RemoteSessionAssistant(): |
206 | self._jobs_count = len(self._sa.get_dynamic_todo_list()) |
207 | self._state = TestsSelected |
208 | |
209 | + @allowed_when(Interacting) |
210 | + def rerun_job(self, job_id, result): |
211 | + self._sa.use_job_result(job_id, result) |
212 | + self.session_change_lock.acquire(blocking=False) |
213 | + self.session_change_lock.release() |
214 | + self._state = TestsSelected |
215 | + |
216 | @allowed_when(TestsSelected) |
217 | def run_job(self, job_id): |
218 | """ |
219 | @@ -316,7 +323,8 @@ class RemoteSessionAssistant(): |
220 | yield from self.interact( |
221 | Interaction('purpose', job.tr_purpose())) |
222 | if job.tr_steps(): |
223 | - yield from self.interact(Interaction('steps', job.tr_steps())) |
224 | + yield from self.interact( |
225 | + Interaction('steps', job.tr_steps())) |
226 | if self._last_response == 'comment': |
227 | yield from self.interact(Interaction('comment')) |
228 | if self._last_response: |
229 | @@ -324,13 +332,16 @@ class RemoteSessionAssistant(): |
230 | may_comment = True |
231 | continue |
232 | if self._last_response == 'skip': |
233 | - result_builder = JobResultBuilder( |
234 | - outcome=IJobResult.OUTCOME_SKIP, |
235 | - comments=_("Explicitly skipped before execution")) |
236 | - if self._current_comments != "": |
237 | - result_builder.comments = self._current_comments |
238 | - self.finish_job(result_builder.get_result()) |
239 | - return |
240 | + def skipped_builder(*args, **kwargs): |
241 | + result_builder = JobResultBuilder( |
242 | + outcome=IJobResult.OUTCOME_SKIP, |
243 | + comments=_("Explicitly skipped before execution")) |
244 | + if self._current_comments != "": |
245 | + result_builder.comments = self._current_comments |
246 | + return result_builder |
247 | + self._be = BackgroundExecutor(self, job_id, skipped_builder) |
248 | + yield from self.interact( |
249 | + Interaction('skip', job.verification, self._be)) |
250 | if job.command: |
251 | if (job.user and not self._passwordless_sudo |
252 | and not self._sudo_password): |
253 | @@ -355,14 +366,8 @@ class RemoteSessionAssistant(): |
254 | self._be = BackgroundExecutor(self, job_id, undecided_builder) |
255 | if self._sa.get_job(self._currently_running_job).plugin in [ |
256 | 'manual', 'user-interact-verify']: |
257 | - rb = self._be.wait() |
258 | - # by this point the ui will handle adding comments via |
259 | - # ResultBuilder.add_comment method that adds \n in front |
260 | - # of the addition, let's rstrip it |
261 | - rb.comments = self._current_comments.rstrip() |
262 | yield from self.interact( |
263 | - Interaction('verification', job.verification, rb)) |
264 | - self.finish_job(rb.get_result()) |
265 | + Interaction('verification', job.verification, self._be)) |
266 | |
267 | @allowed_when(Started, Bootstrapping) |
268 | def run_bootstrapping_job(self, job_id): |
Rebased since several initial commits have been merged already.