Merge lp:~ricardokirkner/ols-jenkaas/land-approvals-without-container into lp:~ols-jenkaas-admins/ols-jenkaas/trunk

Proposed by Ricardo Kirkner
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: 517
Merged at revision: 515
Proposed branch: lp:~ricardokirkner/ols-jenkaas/land-approvals-without-container
Merge into: lp:~ols-jenkaas-admins/ols-jenkaas/trunk
Diff against target: 178 lines (+98/-19)
4 files modified
README.rst (+3/-1)
olsjenkaas/commands.py (+35/-16)
olsjenkaas/containers.py (+2/-2)
olsjenkaas/tests/test_commands.py (+58/-0)
To merge this branch: bzr merge lp:~ricardokirkner/ols-jenkaas/land-approvals-without-container
Reviewer Review Type Date Requested Status
Daniel Manrique (community) Approve
Review via email: mp+330681@code.launchpad.net

Commit message

support landing approved proposals by running landing tests in host (for docker projects)

if no container is specified, the land approval command will run the command in the host itself

To post a comment you must log in.
Revision history for this message
Daniel Manrique (roadmr) wrote :

Looks OK in principle/spirit. I'm a bit scared about running stuff not-in-a-container, though launching a docker image should be somewhat equivalent.

See a comment below, and also, could you document this magic behavior somewhere?

Example, the README says

All test jobs occur inside an lxd container, only the push to launchpad
occur on the slaves themselves so that the needed credentials are never
exposed to test jobs.

this is potentially no longer accurate.

review: Needs Information
516. By Ricardo Kirkner

flip condition for improved readability

517. By Ricardo Kirkner

updated README

Revision history for this message
Daniel Manrique (roadmr) wrote :

Looks OK, I'm still somewhat scared by this but it's worth a try.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.rst'
2--- README.rst 2017-06-22 01:18:29 +0000
3+++ README.rst 2017-09-13 15:29:22 +0000
4@@ -6,7 +6,9 @@
5
6 All test jobs occur inside an lxd container, only the push to launchpad
7 occur on the slaves themselves so that the needed credentials are never
8-exposed to test jobs.
9+exposed to test jobs. The only exception is when jobs use docker, in which case
10+the tests are run inside the docker containers but docker itself is run
11+in the host.
12
13 Another container is used to create and update the jobs from a version
14 controlled branch.
15
16=== modified file 'olsjenkaas/commands.py'
17--- olsjenkaas/commands.py 2017-08-10 17:48:33 +0000
18+++ olsjenkaas/commands.py 2017-09-13 15:29:22 +0000
19@@ -304,6 +304,11 @@
20 def run_in_container(self, command, with_creds=False):
21 return self.worker.raw_shell(command, with_agent=with_creds)
22
23+ def run_in_host(self, command):
24+ out = None if self.out == sys.stdout else self.out
25+ err = None if self.err == sys.stderr else self.err
26+ return containers.raw_run(command, out=out, err=err, shell=True)
27+
28
29 class RunInWorker(CommandWithContainer):
30
31@@ -483,22 +488,36 @@
32 raise Rejected('There was nothing to merge')
33 mounts, remote_work_dir = self.get_mounts_and_remote(
34 local_work_dir)
35- logger.info('Creating worker...')
36- with olsjenkaas.working_directory(local_work_dir):
37- self.create_worker(self.options.container_name, mounts=mounts)
38- logger.info('{} setup on top of {}'.format(
39- self.worker.name, self.worker.backing_name))
40- logger.info('Running {}'.format(self.options.setup_cmd))
41- if self.run_in_container(
42- ['cd', remote_work_dir, '&&', self.options.setup_cmd],
43- with_creds=True):
44- logger.error('{} failed'.format(self.options.setup_cmd))
45- raise Rejected('Project setup failed')
46- logger.info('Running {}'.format(self.options.test_cmd))
47- if self.run_in_container(
48- ['cd', remote_work_dir, '&&', self.options.test_cmd]):
49- logger.error('{} failed'.format(self.options.test_cmd))
50- raise Rejected('Running landing tests failed')
51+ if self.options.container_name == '':
52+ # no container name specified, run in host
53+ logger.info('Running {}'.format(self.options.setup_cmd))
54+ if self.run_in_host(
55+ ['cd', local_work_dir, '&&', self.options.setup_cmd]):
56+ logger.error('{} failed'.format(self.options.setup_cmd))
57+ raise Rejected('Project setup failed')
58+ logger.info('Running {}'.format(self.options.test_cmd))
59+ if self.run_in_host(
60+ ['cd', local_work_dir, '&&', self.options.test_cmd]):
61+ logger.error('{} failed'.format(self.options.test_cmd))
62+ raise Rejected('Running landing tests failed')
63+ else:
64+ # run in named container
65+ logger.info('Creating worker...')
66+ with olsjenkaas.working_directory(local_work_dir):
67+ self.create_worker(self.options.container_name, mounts=mounts)
68+ logger.info('{} setup on top of {}'.format(
69+ self.worker.name, self.worker.backing_name))
70+ logger.info('Running {}'.format(self.options.setup_cmd))
71+ if self.run_in_container(
72+ ['cd', remote_work_dir, '&&', self.options.setup_cmd],
73+ with_creds=True):
74+ logger.error('{} failed'.format(self.options.setup_cmd))
75+ raise Rejected('Project setup failed')
76+ logger.info('Running {}'.format(self.options.test_cmd))
77+ if self.run_in_container(
78+ ['cd', remote_work_dir, '&&', self.options.test_cmd]):
79+ logger.error('{} failed'.format(self.options.test_cmd))
80+ raise Rejected('Running landing tests failed')
81 self.commit_proposal(local_work_dir, proposal)
82 self.push_merged_proposal(local_work_dir, target_url)
83 # FIXME: Once webhooks are used, lp can be trusted to do that (with
84
85=== modified file 'olsjenkaas/containers.py'
86--- olsjenkaas/containers.py 2017-08-30 16:20:36 +0000
87+++ olsjenkaas/containers.py 2017-09-13 15:29:22 +0000
88@@ -43,7 +43,7 @@
89
90
91 # FIXME: Maybe worth backporting to ols-vms with tests -- vila 2017-01-25
92-def raw_run(args, out=None, err=None):
93+def raw_run(args, out=None, err=None, shell=False):
94 """Invoke an ols-vms command, possibly capturing the output.
95
96 The command line tool (and its API) are used to reduce the coupling.
97@@ -52,7 +52,7 @@
98 stdout = subprocess.PIPE if out is not None else None
99 stderr = subprocess.PIPE if err is not None else None
100 logger.debug('Running {}'.format(' '.join(args)))
101- proc = subprocess.Popen(args, stdout=stdout, stderr=stderr)
102+ proc = subprocess.Popen(args, stdout=stdout, stderr=stderr, shell=shell)
103 # If the output/error are not captured, communicate() will return empty
104 # strings (i.e. either output and errors pass through or they are captured
105 # by 'out' and 'err').
106
107=== modified file 'olsjenkaas/tests/test_commands.py'
108--- olsjenkaas/tests/test_commands.py 2017-08-10 17:48:33 +0000
109+++ olsjenkaas/tests/test_commands.py 2017-09-13 15:29:22 +0000
110@@ -525,6 +525,46 @@
111 self.err.getvalue())
112
113
114+class TestCommandInHost(unittest.TestCase):
115+
116+ def setUp(self):
117+ super(TestCommandInHost, self).setUp()
118+ fixtures.isolate_from_disk(self)
119+ self.out = io.StringIO()
120+ self.err = io.StringIO()
121+ self.log_stream = io.StringIO()
122+ fixtures.override_logging(self, self.log_stream)
123+ self.command = commands.CommandWithContainer(
124+ out=self.out, err=self.err)
125+ fixtures.define_uniq_container(self)
126+
127+ def test_command_success(self):
128+ ret = self.command.run_in_host(['/bin/true'])
129+ self.assertEqual(0, ret)
130+ self.assertEqual('', self.out.getvalue())
131+ self.assertEqual('', self.err.getvalue())
132+
133+ def test_command_fails(self):
134+ ret = self.command.run_in_host(['/bin/false'])
135+ self.assertEqual(1, ret)
136+ self.assertEqual('', self.out.getvalue())
137+ self.assertEqual('', self.err.getvalue())
138+
139+ def test_command_output(self):
140+ ret = self.command.run_in_host(['whoami'])
141+ self.assertEqual(0, ret)
142+ # This got captured by the command.out stream
143+ self.assertEqual('{}\n'.format(os.getlogin()), self.out.getvalue())
144+ self.assertEqual('', self.err.getvalue())
145+
146+ def test_command_with_shell_commands(self):
147+ ret = self.command.run_in_host(['cd /tmp && pwd'])
148+ self.assertEqual(0, ret)
149+ # This got captured by the command.out stream
150+ self.assertEqual('/tmp\n', self.out.getvalue())
151+ self.assertEqual('', self.err.getvalue())
152+
153+
154 class TestSetupVcs(unittest.TestCase):
155
156 def setUp(self):
157@@ -674,3 +714,21 @@
158 last_comment = list(proposal.all_comments)[-1]
159 self.assertEqual('Merging failed\n{}'.format(self.fake_url),
160 last_comment.message_body)
161+
162+ def test_landing_without_container_succeeds(self):
163+ # ensure no container is defined
164+ self.container_name = ''
165+ proposal = fixtures.create_proposal(self, 'approved', approved=True)
166+ ret = self.run_command([self.target_url, './work', '',
167+ '/bin/true', '/bin/true'])
168+ self.assertEqual(0, ret)
169+ self.assertLogEndsWith('{} has landed\n'.format(proposal.web_link))
170+ log = self.log_stream.getvalue()
171+ # ensure container was not involved
172+ self.assertNotIn('Creating worker...', log)
173+ self.assertNotIn('setup on top of', log)
174+ proposal.lp_refresh()
175+ # FIXME: Restore the following when webhooks are used and launchpad
176+ # trusted to mark the MP as merged (and decorate commits)
177+ # -- vila 2017-03-30
178+ self.assertEqual('Merged', proposal.queue_status)

Subscribers

People subscribed via source and target branches