Merge lp:~rvb/maas-test/apache-timeout into lp:maas-test

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: 98
Merged at revision: 97
Proposed branch: lp:~rvb/maas-test/apache-timeout
Merge into: lp:maas-test
Diff against target: 206 lines (+116/-12)
4 files modified
maastest/maasfixture.py (+10/-2)
maastest/tests/test_maasfixture.py (+78/-4)
maastest/tests/test_utils.py (+17/-0)
maastest/utils.py (+11/-6)
To merge this branch: bzr merge lp:~rvb/maas-test/apache-timeout
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+199257@code.launchpad.net

Commit message

Try to restart apache2 a couple of time before giving up.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

It's a shame we can't get to the root cause right now, but it does look as if it may be in the Apache package somewhere...

A few notes as always:

.

An alternative way to make tests ignore the "rewrite configuration" command might be to extract that command into a separate function, and have the test patch out that function. Probably not worth it here though, but it's a recurring problem when invoking shell commands.

.

I think test_configure_default_maas_url_fails_apache_cannot_restart is missing an "if" in the name. Should still just barely fit on a line!

.

Perhaps make_exception() could decode as UTF-8? It's a reasonable guess, and text that isn't UTF-8 is highly unlikely to decode cleanly as UTF-8. The disadvantage would be that non-ASCII errors might be handled well only as long as they were in UTF-8, which might end up hiding mistakes from us or confronting people far away with more difficult errors than we see.

review: Approve
lp:~rvb/maas-test/apache-timeout updated
98. By Raphaël Badin

Review fixes.

Revision history for this message
Raphaël Badin (rvb) wrote :

> It's a shame we can't get to the root cause right now, but it does look as if
> it may be in the Apache package somewhere...
>
> A few notes as always:
>
> .
>
> An alternative way to make tests ignore the "rewrite configuration" command
> might be to extract that command into a separate function, and have the test
> patch out that function. Probably not worth it here though, but it's a
> recurring problem when invoking shell commands.

Yeah, I thought about that but keeping the rewrite of the conf and the restart of apache under the same roof (i.e. in the same method) seemed to make more sense to me rather that refactor it for the sake of testing.

> I think test_configure_default_maas_url_fails_apache_cannot_restart is missing
> an "if" in the name. Should still just barely fit on a line!

Right, done.

> Perhaps make_exception() could decode as UTF-8? It's a reasonable guess, and
> text that isn't UTF-8 is highly unlikely to decode cleanly as UTF-8. The
> disadvantage would be that non-ASCII errors might be handled well only as long
> as they were in UTF-8, which might end up hiding mistakes from us or
> confronting people far away with more difficult errors than we see.

Good point, fixed.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'maastest/maasfixture.py'
2--- maastest/maasfixture.py 2013-11-26 22:48:30 +0000
3+++ maastest/maasfixture.py 2013-12-17 14:16:31 +0000
4@@ -122,8 +122,16 @@
5 'http://%s/MAAS' % self.kvm_fixture.direct_ip)
6 self.kvm_fixture.run_command(
7 ['sudo'] + rewrite_command, check_call=True)
8- self.kvm_fixture.run_command(
9- ['sudo', 'service', 'apache2', 'restart'], check_call=True)
10+ # Restarting apache2 sometimes times out: retry the command a
11+ # couple of times before giving up. See bug #1260363.
12+ args = ['sudo', 'service', 'apache2', 'restart']
13+ for retry in utils.retries(delay=10, timeout=60):
14+ retcode, stdout, stderr = self.kvm_fixture.run_command(
15+ args, check_call=False)
16+ if retcode == 0:
17+ break
18+ else:
19+ raise utils.make_exception(args, retcode, stdout, stderr)
20
21 def query_api_key(self, username):
22 """Return the API key for the given MAAS user."""
23
24=== modified file 'maastest/tests/test_maasfixture.py'
25--- maastest/tests/test_maasfixture.py 2013-11-26 22:43:41 +0000
26+++ maastest/tests/test_maasfixture.py 2013-12-17 14:16:31 +0000
27@@ -20,6 +20,7 @@
28 from random import randint
29 from subprocess import check_call
30 from textwrap import dedent
31+from itertools import chain, repeat
32
33 from apiclient.maas_client import MAASClient
34 from fixtures import TempDir
35@@ -28,6 +29,7 @@
36 maasfixture,
37 utils,
38 )
39+from six import text_type
40 from maastest.maas_enums import NODEGROUPINTERFACE_MANAGEMENT
41 import mock
42 import netaddr
43@@ -196,13 +198,85 @@
44 maas_fixture = self.make_maas_fixture(fixture)
45 maas_fixture.configure_default_maas_url()
46
47- self.assertEqual([
48- mock.call(['sudo'] + rewrite_command, check_call=True),
49- mock.call(
50- ['sudo', 'service', 'apache2', 'restart'], check_call=True),
51+ self.assertEqual(
52+ [
53+ mock.call(['sudo'] + rewrite_command, check_call=True),
54+ mock.call(
55+ ['sudo', 'service', 'apache2', 'restart'],
56+ check_call=False),
57 ],
58 fixture.run_command.mock_calls)
59
60+ def test_configure_default_maas_url_retries_if_restart_fails(self):
61+ self.patch(kvmfixture, 'run_command', mock.MagicMock())
62+ fixture = self.make_kvm_fixture()
63+ # Configure fixture.run_command to simulate a transient failure when
64+ # restarting apache.
65+ returns = [
66+ # Simluate response for the 'config rewrite' command.
67+ (0, 'stdout', 'stderr'),
68+ # Simulate first response for the 'apache2 restart' command:
69+ # failure.
70+ (1, 'stdout', 'an error!'),
71+ # Simulate second response for the 'apache2 restart' command:
72+ # success.
73+ (0, 'stdout', 'stderr'),
74+ ]
75+
76+ def side_effect(*args, **kwargs):
77+ return returns.pop(0)
78+
79+ fixture.run_command = mock.MagicMock(side_effect=side_effect)
80+ fixture.direct_ip = '127.99.88.77'
81+
82+ self.patch(
83+ utils, 'retries', mock.MagicMock(return_value=[(0, 1), (0, 1)]))
84+ maas_fixture = self.make_maas_fixture(fixture)
85+
86+ maas_fixture.configure_default_maas_url()
87+
88+ # The first call to fixture.run_command is the rewrite
89+ # configuration command, ignore it since it's tested elsewhere
90+ # and we're only testing that the 'apache2 restart' command is
91+ # retried when it fails here.
92+ run_command_calls = fixture.run_command.mock_calls[1:]
93+ self.assertEqual(
94+ [
95+ # 2 'apache restart' calls.
96+ mock.call(
97+ ['sudo', 'service', 'apache2', 'restart'],
98+ check_call=False),
99+ mock.call(
100+ ['sudo', 'service', 'apache2', 'restart'],
101+ check_call=False),
102+ ],
103+ run_command_calls)
104+
105+ def test_configure_default_maas_url_fails_if_apache_cannot_restart(self):
106+ self.patch(kvmfixture, 'run_command', mock.MagicMock())
107+ fixture = self.make_kvm_fixture()
108+ error_msg = self.getUniqueString()
109+ # Configure fixture.run_command to simulate a failure when
110+ # restarting apache.
111+ returns = chain([
112+ # Simluate response for the 'config rewrite' command.
113+ (0, 'stdout', 'stderr')],
114+ repeat((1, 'stdout', error_msg)))
115+
116+ def side_effect(*args, **kwargs):
117+ return returns.next()
118+
119+ fixture.run_command = mock.MagicMock(side_effect=side_effect)
120+ fixture.direct_ip = '127.99.88.77'
121+
122+ self.patch(
123+ utils, 'retries', mock.MagicMock(return_value=[(0, 1), (0, 1)]))
124+ maas_fixture = self.make_maas_fixture(fixture)
125+
126+ error = self.assertRaises(
127+ Exception, maas_fixture.configure_default_maas_url)
128+ self.assertIn(error_msg, text_type(error))
129+
130 def test_create_maas_admin_creates_admin(self):
131 fixture = self.make_kvm_fixture()
132
133
134=== modified file 'maastest/tests/test_utils.py'
135--- maastest/tests/test_utils.py 2013-12-06 07:47:07 +0000
136+++ maastest/tests/test_utils.py 2013-12-17 14:16:31 +0000
137@@ -13,6 +13,7 @@
138 __all__ = []
139
140 import os.path
141+from pipes import quote
142 import platform
143 from subprocess import PIPE
144 from textwrap import dedent
145@@ -26,6 +27,7 @@
146 from six import text_type
147 import testtools
148 from testtools.matchers import (
149+ ContainsAll,
150 MatchesRegex,
151 MatchesStructure,
152 )
153@@ -100,6 +102,21 @@
154 self.assertEqual(
155 (0, b'', input_string), (retcode, stderr, stdout))
156
157+ def test_make_exception_contains_details(self):
158+ args = ['ls', '/']
159+ retcode = 58723
160+ stdout = self.getUniqueString()
161+ stderr = self.getUniqueString()
162+ exception = utils.make_exception(args, retcode, stdout, stderr)
163+ self.assertThat(
164+ text_type(exception),
165+ ContainsAll([
166+ stdout,
167+ stderr,
168+ str(retcode),
169+ " ".join(quote(arg) for arg in args),
170+ ]))
171+
172
173 class TestBinaryContent(testtools.TestCase):
174 """Tests for `binary_content`."""
175
176=== modified file 'maastest/utils.py'
177--- maastest/utils.py 2013-12-06 07:47:07 +0000
178+++ maastest/utils.py 2013-12-17 14:16:31 +0000
179@@ -49,18 +49,23 @@
180 return f.read()
181
182
183+def make_exception(args, retcode, stdout, stderr):
184+ """Create an exception from the information about a failed command."""
185+ cmd = " ".join(quote(arg) for arg in args)
186+ return Exception(
187+ "Command '%s' failed (%d):\n%s\n%s" % (
188+ cmd, retcode,
189+ stdout.decode('utf8', errors='replace'),
190+ stderr.decode('utf8', errors='replace')))
191+
192+
193 def run_command(args, input=None, check_call=False):
194 """A wrapper to Popen to run commands in the command-line."""
195 process = Popen(args, stdout=PIPE, stderr=PIPE, stdin=PIPE, shell=False)
196 stdout, stderr = process.communicate(input)
197 retcode = process.returncode
198 if check_call and retcode != 0:
199- cmd = " ".join(quote(arg) for arg in args)
200- raise Exception(
201- "Command '%s' failed (%d):\n%s\n%s" % (
202- cmd, retcode,
203- stdout.decode('ascii'),
204- stderr.decode('ascii')))
205+ raise make_exception(args, retcode, stdout, stderr)
206 return retcode, stdout, stderr
207
208

Subscribers

People subscribed via source and target branches