Merge lp:~dooferlad/offspring/passing-ssh-key-to-slave2 into lp:offspring
- passing-ssh-key-to-slave2
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kevin McDermott |
Approved revision: | 100 |
Merge reported by: | Kevin McDermott |
Merged at revision: | not available |
Proposed branch: | lp:~dooferlad/offspring/passing-ssh-key-to-slave2 |
Merge into: | lp:offspring |
Diff against target: |
632 lines (+423/-17) 12 files modified
lib/offspring/build/bin/offspring-build (+4/-0) lib/offspring/build/functions/ssh.sh (+23/-0) lib/offspring/build/tests/offspring_test_key (+27/-0) lib/offspring/build/tests/offspring_test_key.pub (+1/-0) lib/offspring/build/tests/test.sh (+27/-0) lib/offspring/build/wrappers/test.sh (+52/-0) lib/offspring/master/models.py (+12/-3) lib/offspring/master/tests/helpers.py (+2/-0) lib/offspring/slave/slave.py (+67/-12) lib/offspring/slave/tests/test_slave.py (+197/-2) lib/offspring/web/queuemanager/models.py (+9/-0) migration/002-bzr-ssh-user-key.sql (+2/-0) |
To merge this branch: | bzr merge lp:~dooferlad/offspring/passing-ssh-key-to-slave2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kevin McDermott | Approve | ||
Review via email: mp+85165@code.launchpad.net |
This proposal supersedes a proposal from 2011-12-08.
Commit message
Description of the change
Merged on top of trunk, conflicts resolved.
---
Replaces the merge lp:~dooferlad/offspring/passing-ssh-key-to-slave2 now it contains changes from lp:~bigkevmcd/offspring/passing-ssh-key-to-slave and fixes as a result of code review.
Kevin McDermott (bigkevmcd) wrote : Posted in a previous version of this proposal | # |
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal | # |
On 9 December 2011 07:42, Kevin McDermott <email address hidden> wrote:
> #3
>
> + def test_currentBui
>
> Doesn't fit with the test naming conventions (pep8)
Please clarify. It fits with the naming convention of all the other
tests doesn't it?
Thanks,
--
James Tunnicliffe
Kevin McDermott (bigkevmcd) wrote : Posted in a previous version of this proposal | # |
Hi James,
def test_currentBui
compared with...
def test_slave_
def test_slave_
def test_slave_
def test_slave_
def test_slave_
def test_currentBui
def test_addUserToB
def test_start_
def test_check_
def test__check_
Also (and I think this was my fault), test__check_
thanks,
Kevin :-)
James Tunnicliffe (dooferlad) wrote : Posted in a previous version of this proposal | # |
I was mostly thinking about test_addUserToB
example :-) Would you suggest test_add_
I have already fixed up the __ problem.
James
On 9 December 2011 12:03, Kevin McDermott <email address hidden> wrote:
> Hi James,
>
> def test_currentBui
>
> compared with...
>
> def test_slave_
> def test_slave_
> def test_slave_
> def test_slave_
> def test_slave_
> def test_currentBui
> def test_addUserToB
> def test_start_
> def test_check_
> def test__check_
>
> Also (and I think this was my fault), test__check_
>
> thanks,
>
> Kevin :-)
> --
> https:/
> You are the owner of lp:~dooferlad/offspring/passing-ssh-key-to-slave2.
--
James Tunnicliffe
Kevin McDermott (bigkevmcd) : | # |
Preview Diff
1 | === modified file 'lib/offspring/build/bin/offspring-build' |
2 | --- lib/offspring/build/bin/offspring-build 2011-11-30 14:15:52 +0000 |
3 | +++ lib/offspring/build/bin/offspring-build 2011-12-09 17:18:46 +0000 |
4 | @@ -32,6 +32,7 @@ |
5 | source "${IBS_FUNC_DIR}/reports.sh" |
6 | source "${IBS_FUNC_DIR}/logging.sh" |
7 | source "${IBS_FUNC_DIR}/files.sh" |
8 | + source "${IBS_FUNC_DIR}/ssh.sh" |
9 | |
10 | [ -n "${IBS_PROXY}" ] && export http_proxy="${IBS_PROXY}" |
11 | |
12 | @@ -192,6 +193,9 @@ |
13 | echo "Performing initialization..." |
14 | _Initialize |
15 | |
16 | +# If any SSH keys have been passed to us, load them into ssh-agent |
17 | +AddKeys ${3} |
18 | + |
19 | # Setup and start logging |
20 | _Setup |
21 | |
22 | |
23 | === added file 'lib/offspring/build/functions/ssh.sh' |
24 | --- lib/offspring/build/functions/ssh.sh 1970-01-01 00:00:00 +0000 |
25 | +++ lib/offspring/build/functions/ssh.sh 2011-12-09 17:18:46 +0000 |
26 | @@ -0,0 +1,23 @@ |
27 | +#!/bin/bash |
28 | + |
29 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
30 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
31 | + |
32 | +AddKeys() { |
33 | + RUN_SSH_AGENT=1 |
34 | + |
35 | + if [ -z "${1}" ]; then |
36 | + let RUN_SSH_AGENT=-1 |
37 | + fi |
38 | + |
39 | + if [ $RUN_SSH_AGENT = 1 ]; then |
40 | + eval `ssh-agent` |
41 | + ssh-add $1 |
42 | + fi |
43 | +} |
44 | + |
45 | +StopSSHAgent() { |
46 | + if [ $RUN_SSH_AGENT = 1 ]; then |
47 | + kill $SSH_AGENT_PID |
48 | + fi |
49 | +} |
50 | \ No newline at end of file |
51 | |
52 | === added directory 'lib/offspring/build/tests' |
53 | === added file 'lib/offspring/build/tests/offspring_test_key' |
54 | --- lib/offspring/build/tests/offspring_test_key 1970-01-01 00:00:00 +0000 |
55 | +++ lib/offspring/build/tests/offspring_test_key 2011-12-09 17:18:46 +0000 |
56 | @@ -0,0 +1,27 @@ |
57 | +-----BEGIN RSA PRIVATE KEY----- |
58 | +MIIEowIBAAKCAQEAuzjspIVMkDrI+1MkDCFVh9XBbkc55hCpI/RqMz594jDc86iM |
59 | +4vRmq5R+HJszi/mbbklgsD8x0bGMz65u+yvp9AxScn0/d5GXfhZKHz3fROjT4WEf |
60 | +1NB2gqAink3e9YJZ+04sAJ1Xz81w8wkoTh7roU35GBXSjbH1rMl17qLny8pUsuv6 |
61 | +gehemzYAcvM9mAASfAzNvPR87NjrbizYMOwWsQ6stEm4Aemdcio42ZZBHXVm/QV0 |
62 | +hOTXLfopOH622iIuEFU5j1blUY/NXyDBU+hNUmMeO9DpL06oDu0XIdwb3nPaEQ3n |
63 | +U+I1VvTR8gz1sQgXYVGa3qGbAm01mcXWQ+Is8QIDAQABAoIBADFRnYT5WGHmGmua |
64 | +SzSm01ElDf9u4+GnIedGy3MUUzTyikHldLeUijdItq/ycnG9HyS+T6od+5Gxo9ZR |
65 | +rQqdVtPjKxTdyYpF4BJm7L+uHNKaQrZsT2ZQQ+fFJ1lsSf+ChxGcVhsTV7517/sV |
66 | +vnhVzNyBHc0qcnzBFGaf62EhqM4V2ogsj3bLd6nwRAyvpbUMrWWv057teKooxBsk |
67 | +j0QXP3WYEzAmdbphkG1qPCGJQNhYBbyjU7Cf4bJg/CbHENynM8zlbiFVUWgfwtNJ |
68 | +ZcddrFs3Y1P6nDa/Jd2ifqx6V7M7LAqpKjqwiuCNPiXVWyxmELaKu8Yvmqsv5qXF |
69 | +ixKcDAECgYEA9Ozj7r0PHkrrCcoGdIQTs1sZS0eV+yDKdOOlUeFpPMHi6PqROOwY |
70 | +cKr4RtkJC/AgjfA3tUEBemVKYoCOVWYeHmlTd3wQpl7lqqGN431A18/Z0psKpFh+ |
71 | +wLfMZXBslYybY3b+VguICuyICN/hksCxKJNurKalPow/EYqo0dspOkkCgYEAw7AZ |
72 | +QKCfnuYfGdGto8JFizkxS6uvTInXNTzRZPdkbgd/JhCmnXENg93QSo/GWe2dehQG |
73 | +NZkCpVnZFKkubbxCc/OBAF+es7dVRc8vy8uwa2nOW9SG6yLNlNKdTwJxmRlV+53H |
74 | +mzC0PZ4GyMnBFJ46+rem3siLLVMlkolbZzlnHWkCgYA8+e0VNsRYylYRrdZFk8xD |
75 | +zt5RO5U/XD6LM1GpPPEySyLu1dLp1P2Qrz/4g3gZHMM+ExwLaA+yJR2LwG2vHSlK |
76 | +cPZyvNR4Vw/elzH3/Orzz69vG2Je4BlOaXPdnUurP8I/1RQk3+IStih37ST/oDF6 |
77 | +5JmdKi/hjpD1EQxOkr2E4QKBgC9Y/3MsqhJ3WZUUr6/MxKjgCLZnbv3U6DZgZcXJ |
78 | +OgqJU9Fw++9iOEPsuoYf7X06yfyMtcfoIsTBTY37NVml0Gpfw5nEiRCwzjga3lSw |
79 | +DxqeOijr7k0cWaOlphxE2hmSEMTVs0MwcJvsDXYtosMLWffp0b1bxpkL4i5nf68l |
80 | +K3bpAoGBAJR5uzQGl5pXg7QKYPv1Sdi6pBp1wGSpFzzPatlpnrO8zZi9c0O29h7R |
81 | +3lMfBazjjNQmJF25uh+svgPCboNDh9dhqDBKf/ep0ju4wpXF2I0JM5xhYP2s242K |
82 | +mqsBXi4JWa9GAjVqVA48M3Vsu4r1YTOxIqDjnAFpnTU2tzzp2OFZ |
83 | +-----END RSA PRIVATE KEY----- |
84 | |
85 | === added file 'lib/offspring/build/tests/offspring_test_key.pub' |
86 | --- lib/offspring/build/tests/offspring_test_key.pub 1970-01-01 00:00:00 +0000 |
87 | +++ lib/offspring/build/tests/offspring_test_key.pub 2011-12-09 17:18:46 +0000 |
88 | @@ -0,0 +1,1 @@ |
89 | +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7OOykhUyQOsj7UyQMIVWH1cFuRznmEKkj9GozPn3iMNzzqIzi9GarlH4cmzOL+ZtuSWCwPzHRsYzPrm77K+n0DFJyfT93kZd+FkofPd9E6NPhYR/U0HaCoCKeTd71gln7TiwAnVfPzXDzCShOHuuhTfkYFdKNsfWsyXXuoufLylSy6/qB6F6bNgBy8z2YABJ8DM289Hzs2OtuLNgw7BaxDqy0SbgB6Z1yKjjZlkEddWb9BXSE5Nct+ik4frbaIi4QVTmPVuVRj81fIMFT6E1SYx470OkvTqgO7Rch3Bvec9oRDedT4jVW9NHyDPWxCBdhUZreoZsCbTWZxdZD4izx dooferlad@JT-Linaro-ThinkPad-T410 |
90 | |
91 | === added file 'lib/offspring/build/tests/test.sh' |
92 | --- lib/offspring/build/tests/test.sh 1970-01-01 00:00:00 +0000 |
93 | +++ lib/offspring/build/tests/test.sh 2011-12-09 17:18:46 +0000 |
94 | @@ -0,0 +1,27 @@ |
95 | +#!/bin/bash |
96 | + |
97 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
98 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
99 | + |
100 | +# Get the full directory path of the test script |
101 | +DIR="$( cd "$( dirname "$0" )" && pwd )" |
102 | + |
103 | +# Load functions under test |
104 | +source "${DIR}/../functions/ssh.sh" |
105 | + |
106 | +# Force key permissions to 600 to keep SSH happy |
107 | +chmod 600 "${DIR}/offspring_test_key" |
108 | + |
109 | +# Load test key |
110 | +AddKeys "${DIR}/offspring_test_key" |
111 | + |
112 | +# See what keys are loaded (expect lib/offspring/build/tests/offspring_test_key) |
113 | +echo After loading test key, keys are: |
114 | +ssh-add -l |
115 | + |
116 | +# Stop the loaded ssh-agent process |
117 | +StopSSHAgent |
118 | + |
119 | +# Check that no keys are listed: Expect "Could not open a connection to your authentication agent." |
120 | +echo "After killing ssh agent, we shouldn't be able to find any keys..." |
121 | +ssh-add -l |
122 | |
123 | === added file 'lib/offspring/build/wrappers/test.sh' |
124 | --- lib/offspring/build/wrappers/test.sh 1970-01-01 00:00:00 +0000 |
125 | +++ lib/offspring/build/wrappers/test.sh 2011-12-09 17:18:46 +0000 |
126 | @@ -0,0 +1,52 @@ |
127 | +#!/bin/bash |
128 | + |
129 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
130 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
131 | + |
132 | +Buildtool_setup() { |
133 | + Catch_faults permit_errors |
134 | + |
135 | + TARGETFS="${IBS_WORKDIR}/${PROJECT}/chroot" |
136 | + |
137 | + if [ -d "${IBS_WORKDIR}/${PROJECT}/config" ]; then |
138 | + rm -rf "${IBS_WORKDIR}/${PROJECT}/config" > /dev/null 2>&1 || true |
139 | + fi |
140 | + |
141 | + Action "Copying project configuration directory into work directory for build" |
142 | + Export_project_config "${PROJECT}" "${IBS_WORKDIR}/${PROJECT}/config" |
143 | + Action_done |
144 | + |
145 | + if [ "$?" != "0" ]; then |
146 | + Error "Unable to copy project build configuration to %s" "${IBS_WORKDIR}/${PROJECT}/config" |
147 | + Die |
148 | + fi |
149 | + Catch_faults disallow_errors |
150 | +} |
151 | + |
152 | +Buildtool_build() { |
153 | + Action "Test Build - Buildtool_build" |
154 | + Action_done |
155 | +} |
156 | + |
157 | +Buildtool_cleanup() { |
158 | + Action "Test Build - Buildtool_cleanup" |
159 | + Action_done |
160 | +} |
161 | + |
162 | +_Check_directory() { |
163 | + if [ "${PWD}" != "${IBS_WORKDIR}/${PROJECT}" ]; then |
164 | + Error "Working directory is '%s'; expected '%s'" "{$PWD}" "${IBS_WORKDIR}/${PROJECT}" |
165 | + Die |
166 | + fi |
167 | + Debug "Directory check performed and was successful; PWD: %s" "${PWD}" |
168 | +} |
169 | + |
170 | +_SaveConfig() { |
171 | + Action "Test Build - _SaveConfig" |
172 | + Action_done |
173 | +} |
174 | + |
175 | +_SaveBuildWorkDirectory() { |
176 | + Action "Test Build - _SaveBuildWorkDirectory" |
177 | + Action_done |
178 | +} |
179 | |
180 | === modified file 'lib/offspring/master/models.py' |
181 | --- lib/offspring/master/models.py 2011-12-01 16:02:08 +0000 |
182 | +++ lib/offspring/master/models.py 2011-12-09 17:18:46 +0000 |
183 | @@ -104,9 +104,12 @@ |
184 | """ |
185 | Pass BuildRequest to a slave server. |
186 | """ |
187 | - result = self.server_proxy.startBuild(request.project.name, |
188 | - request.project.config_url) |
189 | - if result == LexbuilderSlaveServer.REQUEST_OKAY: |
190 | + status = self.server_proxy.startBuild(request.project.name, |
191 | + request.project.config_url, |
192 | + request.project.lp_user, |
193 | + request.project.lp_ssh_key) |
194 | + |
195 | + if status == LexbuilderSlaveServer.REQUEST_OKAY: |
196 | self.previous_state = self.current_state |
197 | self.current_state = LexbuilderSlave.STATE_BUILDING |
198 | self.current_job = BuildResult(request, self) |
199 | @@ -174,6 +177,12 @@ |
200 | config_url = Unicode() |
201 | notes = Unicode() |
202 | |
203 | + # The Launchpad User and SSH key are stored per project. If we stored them |
204 | + # per LaunchpadProject, anyone who could create a Project referencing that |
205 | + # LaunchpadProject could get access to the private data in it. |
206 | + lp_user = Unicode() |
207 | + lp_ssh_key = Unicode() |
208 | + |
209 | def __init__(self, name, priority=40): |
210 | self.title = unicode(name.capitalize()) |
211 | self.name = unicode(name) |
212 | |
213 | === modified file 'lib/offspring/master/tests/helpers.py' |
214 | --- lib/offspring/master/tests/helpers.py 2011-11-30 12:31:01 +0000 |
215 | +++ lib/offspring/master/tests/helpers.py 2011-12-09 17:18:46 +0000 |
216 | @@ -128,6 +128,8 @@ |
217 | "suite VARCHAR(200) NOT NULL, " |
218 | "status VARCHAR(200), " |
219 | "notes TEXT, " |
220 | + "lp_user TEXT, " |
221 | + "lp_ssh_key text, " |
222 | "is_active BOOLEAN NOT NULL DEFAULT TRUE)") |
223 | |
224 | self.connection.execute( |
225 | |
226 | === modified file 'lib/offspring/slave/slave.py' |
227 | --- lib/offspring/slave/slave.py 2011-12-06 15:10:45 +0000 |
228 | +++ lib/offspring/slave/slave.py 2011-12-09 17:18:46 +0000 |
229 | @@ -2,6 +2,8 @@ |
230 | # GNU Affero General Public License version 3 (see the file LICENSE). |
231 | import logging |
232 | import platform |
233 | +import re |
234 | +import tempfile |
235 | import time |
236 | import xmlrpclib |
237 | |
238 | @@ -55,13 +57,36 @@ |
239 | self.server.stop() |
240 | self.server.join() |
241 | |
242 | - def startBuild(self, projectName, configBranchURL): |
243 | + def addUserToBzrSshUrl(self, configBranchURL, lpUser): |
244 | + """Adds lpUser to configBranchURL, returns updated configBranchURL.""" |
245 | + |
246 | + # check for lp: format URLs. Convert these to bzr+ssh ones |
247 | + configBranchURL = re.sub("lp:", "bzr+ssh://bazaar.launchpad.net/", |
248 | + configBranchURL) |
249 | + |
250 | + # The assumption is that this function will only be called with a |
251 | + # bzr+ssh URL (hence the name!) |
252 | + assert(re.search("bzr\+ssh://", configBranchURL)) |
253 | + |
254 | + # Add user to URL |
255 | + configBranchURL = re.sub("bzr\+ssh://", |
256 | + "bzr+ssh://" + lpUser + "@", configBranchURL) |
257 | + |
258 | + return configBranchURL |
259 | + |
260 | + def startBuild(self, projectName, configBranchURL, lpUser=None, |
261 | + lpSSHKey=None): |
262 | """ |
263 | Instigate a new build for projectName, using the configuration at |
264 | configBranchURL. |
265 | """ |
266 | + if lpUser: |
267 | + # We don't want to pass the user separately, we want it to be |
268 | + # integrated into the configBranchURL. |
269 | + configBranchURL = self.addUserToBzrSshUrl(configBranchURL, lpUser) |
270 | + |
271 | self.currentBuild = ProjectBuildProcess( |
272 | - self, projectName, configBranchURL) |
273 | + self, projectName, configBranchURL, lpSSHKey) |
274 | logging.info("Starting build: %s %s %s" % ( |
275 | self.currentBuild.getCommandPath(), projectName, configBranchURL)) |
276 | self.currentBuild.start() |
277 | @@ -75,9 +100,19 @@ |
278 | if self.currentBuild.process is not None: |
279 | if self.currentBuild.process.poll() is None: |
280 | self.currentBuild.process.terminate() |
281 | - # Sleep before sending kill. |
282 | - time.sleep(self.config.getint("slave", "child_wait_timeout")) |
283 | - self.currentBuild.process.kill() |
284 | + if self.currentBuild.process.poll() is None: |
285 | + # Sleep before sending kill. |
286 | + time.sleep(int(self.config.get("slave", |
287 | + "child_wait_timeout"))) |
288 | + if self.currentBuild.process.poll() is None: |
289 | + self.currentBuild.process.kill() |
290 | + self.currentBuild.tidyUp() |
291 | + |
292 | + def currentBuildIsActive(self): |
293 | + """Return True if current build is active, else False.""" |
294 | + if self.currentBuild is not None: |
295 | + return self.currentBuild.state == ProjectBuildProcess.STATE_ACTIVE |
296 | + return False |
297 | |
298 | |
299 | class LexbuilderSlaveServer(LexbuilderXMLRPCServer): |
300 | @@ -101,10 +136,8 @@ |
301 | """ |
302 | Start building the project on the LexbuilderSlave. |
303 | """ |
304 | - current_build = self.slave.currentBuild |
305 | - if current_build is not None: |
306 | - if current_build.state == ProjectBuildProcess.STATE_ACTIVE: |
307 | - return LexbuilderSlaveServer.REQUEST_ERROR |
308 | + if self.slave.currentBuildIsActive(): |
309 | + return LexbuilderSlaveServer.REQUEST_ERROR |
310 | self.slave.startBuild(projectName, configBranchURL) |
311 | return LexbuilderSlaveServer.REQUEST_OKAY |
312 | |
313 | @@ -229,11 +262,32 @@ |
314 | |
315 | class ProjectBuildProcess(MonitoredProcess): |
316 | |
317 | - def __init__(self, supervisor, projectName, configBranchURL): |
318 | - MonitoredProcess.__init__( |
319 | - self, supervisor, [projectName, configBranchURL]) |
320 | + def __init__(self, supervisor, projectName, configBranchURL, |
321 | + lpSSHKey=None): |
322 | + args = [projectName, configBranchURL] |
323 | + self.ssh_key_file = None |
324 | + |
325 | + if lpSSHKey: |
326 | + # Write SSH key to disk. tempfile will delete it after the build |
327 | + # when this class goes out of scope. |
328 | + # Need to have this here so we can pass the temp file name |
329 | + # containing the key to the builder process on its command line. |
330 | + self.ssh_key_file = tempfile.NamedTemporaryFile() |
331 | + args.append(self.ssh_key_file.name) |
332 | + self.ssh_key_file.write(lpSSHKey) |
333 | + self.ssh_key_file.flush() |
334 | + |
335 | + MonitoredProcess.__init__(self, supervisor, args) |
336 | self.projectName = projectName |
337 | |
338 | + def __del__(self): |
339 | + self.tidyUp() |
340 | + |
341 | + def tidyUp(self): |
342 | + if self.ssh_key_file: |
343 | + self.ssh_key_file.close() |
344 | + self.ssh_key_file = None |
345 | + |
346 | def setup(self): |
347 | self.buildName = None |
348 | |
349 | @@ -256,6 +310,7 @@ |
350 | logging.debug( |
351 | "Setting state of slave to %s", LexbuilderSlave.STATE_IDLE) |
352 | self.master.state = LexbuilderSlave.STATE_IDLE |
353 | + self.tidyUp() |
354 | |
355 | def getCommandPath(self): |
356 | return self.master.config.get("slave", "build_command") |
357 | |
358 | === modified file 'lib/offspring/slave/tests/test_slave.py' |
359 | --- lib/offspring/slave/tests/test_slave.py 2011-12-08 17:29:41 +0000 |
360 | +++ lib/offspring/slave/tests/test_slave.py 2011-12-09 17:18:46 +0000 |
361 | @@ -1,8 +1,12 @@ |
362 | # Copyright 2011 Canonical Ltd. This software is licensed under the |
363 | # GNU Affero General Public License version 3 (see the file LICENSE). |
364 | + |
365 | +from ConfigParser import ConfigParser |
366 | +from datetime import datetime |
367 | +import os |
368 | import platform |
369 | -from datetime import datetime |
370 | -from ConfigParser import ConfigParser |
371 | +import re |
372 | +import subprocess |
373 | import xmlrpclib |
374 | |
375 | from offspring.tests.helpers import CaptureLoggingTestCase |
376 | @@ -56,6 +60,9 @@ |
377 | def getBuildName(self): |
378 | return self.name |
379 | |
380 | + def tidyUp(self): |
381 | + self.tidied = True |
382 | + |
383 | |
384 | class LexbuilderSlaveTestMixin(object): |
385 | """ |
386 | @@ -78,6 +85,10 @@ |
387 | self.config.set("slave", "port", 8765) |
388 | |
389 | |
390 | +class FakeProjectBuildProcess(object): |
391 | + state = ProjectBuildProcess.STATE_ACTIVE |
392 | + |
393 | + |
394 | class TestLexbuilderSlave(LexbuilderSlaveTestMixin, CaptureLoggingTestCase): |
395 | """ |
396 | Tests for the LexbuilderSlave class. |
397 | @@ -135,6 +146,7 @@ |
398 | |
399 | self.assertTrue(fake_build.process.terminated) |
400 | self.assertTrue(fake_build.process.killed) |
401 | + self.assertTrue(fake_build.tidied) |
402 | self.assertEqual("Killing active build.\n", log_file.getvalue()) |
403 | |
404 | def test_slave_start_build(self): |
405 | @@ -148,10 +160,187 @@ |
406 | |
407 | fake_url = u"http://example.com/project" |
408 | self.slave.startBuild(u"testing", fake_url) |
409 | + self.assertEqual(["testing", fake_url], |
410 | + self.slave.currentBuild.arguments) |
411 | self.assertEqual( |
412 | "Starting build: /usr/bin/offspring-build testing %s\n" % fake_url, |
413 | log_file.getvalue()) |
414 | |
415 | + def test_current_build_is_active_no_current_build(self): |
416 | + """Test currentBuildIsActive returns False when currentBuild = False""" |
417 | + |
418 | + # To start with, currentBuild should be None, so active == False |
419 | + self.assertEqual(self.slave.currentBuild, None) |
420 | + self.assertEqual(self.slave.currentBuildIsActive(), False) |
421 | + |
422 | + def test_current_build_is_active_build_in_active_state(self): |
423 | + """Test currentBuildIsActive returns True when build is active""" |
424 | + # Set up a fake build process (starts off in STATE_ACTIVE) |
425 | + self.slave.currentBuild = FakeProjectBuildProcess() |
426 | + |
427 | + # Now we have started a build, currentBuild has been assigned. |
428 | + self.assertNotEqual(self.slave.currentBuild, None) |
429 | + self.assertEqual(self.slave.currentBuild.state, |
430 | + ProjectBuildProcess.STATE_ACTIVE) |
431 | + self.assertEqual(self.slave.currentBuildIsActive(), True) |
432 | + |
433 | + def test_current_build_is_active_build_state_not_active(self): |
434 | + """Test currentBuildIsActive when currentBuild state != STATE_ACTIVE |
435 | + |
436 | + currentBuildIsActive should return False when |
437 | + slave.currentBuild.state != STATE_ACTIVE. Clearly, currentBuild needs |
438 | + to be valid for this test. |
439 | + |
440 | + """ |
441 | + |
442 | + # Set up a fake build process (starts off in STATE_ACTIVE) |
443 | + self.slave.currentBuild = FakeProjectBuildProcess() |
444 | + |
445 | + self.slave.currentBuild.state = ProjectBuildProcess.STATE_SUCCESS |
446 | + self.assertEqual(self.slave.currentBuildIsActive(), False) |
447 | + |
448 | + self.slave.currentBuild.state = ProjectBuildProcess.STATE_FAILED |
449 | + self.assertEqual(self.slave.currentBuildIsActive(), False) |
450 | + |
451 | + def test_add_user_to_bzr_ssh_url(self): |
452 | + """Test lp:project URL form converted into bzr+ssh form adding user.""" |
453 | + |
454 | + config_url = u"lp:~dooferlad/offspring/config" |
455 | + user = "user" |
456 | + |
457 | + updated_url = self.slave.addUserToBzrSshUrl(config_url, user) |
458 | + expected_url = ("bzr+ssh://user@bazaar.launchpad.net/~dooferlad/" + |
459 | + "offspring/config") |
460 | + self.assertEqual(updated_url, expected_url) |
461 | + |
462 | + def test_start_build_with_custom_ssh_key(self): |
463 | + """Test slave converts lp:project URL into bzr+ssh form adding user. |
464 | + |
465 | + When sending a URL to the slave in the form lp:project, and |
466 | + a user and SSH key are sent as well, that the slave adds the user |
467 | + into the URL so a private config branch can be checked out (URL needs |
468 | + to be modified to send the user name through BZR to SSH). |
469 | + """ |
470 | + log_file = self.capture_logging() |
471 | + build_process_mock = self.mocker.patch(ProjectBuildProcess) |
472 | + build_process_mock.start() |
473 | + |
474 | + sshkey_filename = self.makeFile() |
475 | + tempfile_mock = self.mocker.replace("tempfile.NamedTemporaryFile") |
476 | + tempfile_mock() |
477 | + self.mocker.result(open(sshkey_filename, "w")) |
478 | + |
479 | + self.mocker.replay() |
480 | + |
481 | + config_url = u"lp:~dooferlad/offspring/config" |
482 | + args = ["project_name", config_url, "user", "key"] |
483 | + |
484 | + self.slave.startBuild(*args) |
485 | + |
486 | + new_url = re.sub("lp:", |
487 | + "bzr+ssh://" + args[2] + "@bazaar.launchpad.net/", |
488 | + args[1]) |
489 | + self.assertEqual(["project_name", new_url, sshkey_filename], |
490 | + self.slave.currentBuild.arguments) |
491 | + self.assertEqual( |
492 | + "Starting build: /usr/bin/offspring-build project_name %s\n" |
493 | + % new_url, log_file.getvalue()) |
494 | + |
495 | + def test_check_ssh_key_deleted_when_build_finished(self): |
496 | + """Test SSH key file lifetime management. |
497 | + |
498 | + The SSH key of a project is written to a temporary file when the |
499 | + build process starts, this is tested by the first assert. When the |
500 | + build has finished or is stopped, the key file is deleted. This is |
501 | + tested by the second assert. |
502 | + """ |
503 | + log_file = self.capture_logging() |
504 | + build_process_mock = self.mocker.patch(ProjectBuildProcess) |
505 | + build_process_mock.start() |
506 | + self.mocker.replay() |
507 | + |
508 | + config_url = u"lp:~dooferlad/offspring/config" |
509 | + args = ["project_name", config_url, "user", "key"] |
510 | + |
511 | + self.slave.startBuild(*args) |
512 | + |
513 | + file_path = self.slave.currentBuild.ssh_key_file.name |
514 | + read_key = open(file_path).read() |
515 | + |
516 | + # We should have a key written to a file for the slave to read... |
517 | + self.assertEqual(read_key, args[3]) |
518 | + |
519 | + # Can't use XMLRPC interface here because it checks to see if a build |
520 | + # has actually started. We just want to test the clean up logic. |
521 | + self.slave.stopBuild() |
522 | + # Having stopped the slave, the key should have been erased |
523 | + self.assertFalse(os.path.exists(file_path)) |
524 | + |
525 | + def test_check_ssh_agent(self): |
526 | + """Check that SSH agent is started and killed by build. |
527 | + |
528 | + In order to pass private keys to SSH, the build script starts |
529 | + ssh-agent if a private key file is passed to it. When the build has |
530 | + completed, the build script kills SSH agent. |
531 | + |
532 | + This test uses the called shell script to test the functions used |
533 | + by the build script to start ssh-agent, load the specified key and |
534 | + kill the started ssh-agent process. |
535 | + |
536 | + """ |
537 | + # This is a wrapper around a shell script that runs the commands and |
538 | + # we just check the output. |
539 | + |
540 | + dir_of_this_file = os.path.dirname(os.path.abspath(__file__)) |
541 | + test_script = os.path.join(dir_of_this_file, |
542 | + "../../build/tests/test.sh") |
543 | + |
544 | + test_script_process = subprocess.Popen(test_script, |
545 | + stdout=subprocess.PIPE, |
546 | + stderr=subprocess.STDOUT) |
547 | + |
548 | + stdout = test_script_process.communicate()[0] |
549 | + |
550 | + lines = stdout.split("\n") |
551 | + |
552 | + # Sample output: |
553 | + # Agent pid 9839 |
554 | + # Identity added: /bla/offspring_test_key (/bla/offspring_test_key) |
555 | + # After loading test key, keys are: |
556 | + # 2048 <fingerprint> /bla/offspring_test_key (RSA) |
557 | + # After killing ssh agent, we shouldn't be able to find any keys... |
558 | + # Could not open a connection to your authentication agent. |
559 | + |
560 | + # Expect the first line to be of the form "Agent pid [PID]" |
561 | + self.assertTrue(re.search("Agent pid \d+", lines[0]), |
562 | + "Test build ssh-agent check failed: Expected to find" + |
563 | + "\nAgent pid <PID>\nfound:\n" + lines[0]) |
564 | + |
565 | + # Expect the second line to be of the form: |
566 | + # Identity added: <path to...>/offspring_test_key |
567 | + self.assertTrue(re.search("Identity added:.*?offspring_test_key", |
568 | + lines[1]), |
569 | + "Test build ssh-agent check failed: Expected to find" + |
570 | + "\nIdentity added: <path to...>/offspring_test_key" + |
571 | + "\nfound:\n" + lines[1]) |
572 | + |
573 | + # Ignore the third line, it is just a fixed message |
574 | + # Expect the fourth line to be: |
575 | + # <key fingerprint> /path/to/offspring_test_key (RSA) |
576 | + self.assertTrue(re.search("offspring_test_key \(RSA\)", lines[3]), |
577 | + "Test build ssh-agent check failed: Expected to find" + |
578 | + "\noffspring_test_key (RSA)\nfound:\n" + lines[3]) |
579 | + |
580 | + # Ignore the fifth line, it is just a fixed message |
581 | + # Expect the sixth line to be: |
582 | + # "Could not open a connection to your authentication agent." |
583 | + search_string = ( |
584 | + "^Could not open a connection to your authentication agent\.$") |
585 | + message = ("Test build ssh-agent check failed: Expected to find" + |
586 | + "\nCould not open a connection to your authentication" + |
587 | + "agent\n, found:\n" + lines[5]) |
588 | + self.assertTrue(re.search(search_string, lines[5]), message) |
589 | + |
590 | |
591 | class FakeLexbuilderSlave(object): |
592 | """Fake slave for use in tests.""" |
593 | @@ -169,6 +358,12 @@ |
594 | def stopBuild(self): |
595 | self.stopped = True |
596 | |
597 | + def currentBuildIsActive(self): |
598 | + """Return True if current build is active, else False.""" |
599 | + if self.currentBuild is not None: |
600 | + return self.currentBuild.state == ProjectBuildProcess.STATE_ACTIVE |
601 | + return False |
602 | + |
603 | |
604 | class TestLexbuilderSlaveServer(LexbuilderSlaveTestMixin, |
605 | CaptureLoggingTestCase): |
606 | |
607 | === modified file 'lib/offspring/web/queuemanager/models.py' |
608 | --- lib/offspring/web/queuemanager/models.py 2011-11-30 14:15:52 +0000 |
609 | +++ lib/offspring/web/queuemanager/models.py 2011-12-09 17:18:46 +0000 |
610 | @@ -218,6 +218,15 @@ |
611 | launchpad_project = models.ForeignKey("LaunchpadProject", blank=True, null=True) |
612 | status = models.CharField('status', max_length=200, null=True, choices=STATUS_CHOICES) |
613 | config_url = models.CharField('config URL', max_length=200) # Add some kind of validator here. |
614 | + |
615 | + # The Launchpad User and SSH key are stored per project. If we stored them |
616 | + # per LaunchpadProject, anyone who could create a Project referencing that |
617 | + # LaunchpadProject could get access to the private data in it. |
618 | + lp_user = models.TextField("Launchpad User", null=True, editable=False, |
619 | + blank=True) |
620 | + lp_ssh_key = models.TextField("Launchpad User's SSH Key", blank=True, |
621 | + null=True, editable=False) |
622 | + |
623 | notes = models.TextField(blank=True, null=True) |
624 | |
625 | class Meta: |
626 | |
627 | === added file 'migration/002-bzr-ssh-user-key.sql' |
628 | --- migration/002-bzr-ssh-user-key.sql 1970-01-01 00:00:00 +0000 |
629 | +++ migration/002-bzr-ssh-user-key.sql 2011-12-09 17:18:46 +0000 |
630 | @@ -0,0 +1,2 @@ |
631 | +ALTER TABLE projects ADD COLUMN "lp_user" text; |
632 | +ALTER TABLE projects ADD COLUMN "lp_ssh_key" text; |
Some minor fixes needed and it's good to go...
#1
+ def test_currentBui ldIsActive( self):
This is actually four tests hidden in one...can you split them out, your comments are really the docstrings for the other tests...
#2 zrSshUrl( self):
+ def test_addUserToB
This is cool, but I'd prefer to see the comparison made more explicit, you know what the result should be, so it might be better to be explicit in the test...
expected_url = "bzr+ssh://<email address hidden> /~dooferlad/ offspring/ config"
Using the same technique as the implementation is no guarantee that the result is actually correct :-)
#3
+ def test_currentBui ldIsActive( self):
Doesn't fit with the test naming conventions (pep8)
#4
There are a couple of minor conflicts with trunk, can merge trunk and resolve?
Other than these minor issues, looks good, nice branch :-)