Merge lp:~lamont/maas/bug-1553176-1.9 into lp:maas/1.9

Proposed by LaMont Jones
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4598
Proposed branch: lp:~lamont/maas/bug-1553176-1.9
Merge into: lp:maas/1.9
Diff against target: 210 lines (+32/-116)
2 files modified
src/provisioningserver/utils/fs.py (+16/-62)
src/provisioningserver/utils/tests/test_fs.py (+16/-54)
To merge this branch: bzr merge lp:~lamont/maas/bug-1553176-1.9
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+316384@code.launchpad.net

Commit message

Backport parts of r4750 from trunk: BIND compares zone file mtime to last load time and skips the load when the file is older, so our approach to slowly incrementing mtime breaks.

Description of the change

Stop trying to be smarter than BIND about zonefile modification times, since it uses last-load-time and file mtime to determine whether or not it's already loaded.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good; let's wait to land this until the new version of bind makes it from -proposed to -updates.

review: Approve
Revision history for this message
LaMont Jones (lamont) wrote :

To follow up on the discussion in IRC, bind9 introduced a change (that landed in trusty) wherein it compares the mtime of the zone file to the system time when the zone was last loaded, and declares the zone to be current if it isn't newer. By setting the mtime into the past and then slowly creeping forward 1 second per update, unless there is a flurry of activity that never stops (and we maintain roughly 1 update per second after that flurry), the DNS will generally be wrong. As such, it makes sense to land this even if bind9 hasn't arrived in -updates yet.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Sounds good. You can go ahead and land it.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (83.9 KiB)

The attempt to merge lp:~lamont/maas/bug-1553176-1.9 into lp:maas/1.9 failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Ign http://prodstack-zone-1.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports InRelease
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty Release
Get:3 http://security.ubuntu.com trusty-security/main Sources [124 kB]
Get:4 http://security.ubuntu.com trusty-security/universe Sources [48.6 kB]
Get:5 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/main Sources [390 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [579 kB]
Get:7 http://security.ubuntu.com trusty-security/universe amd64 Packages [150 kB]
Get:8 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/restricted Sources [5,911 B]
Get:9 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/universe Sources [173 kB]
Get:10 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/multiverse Sources [7,535 B]
Get:11 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [944 kB]
Get:12 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/restricted amd64 Packages [16.4 kB]
Get:13 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [397 kB]
Get:14 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/multiverse amd64 Packages [14.0 kB]
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/main Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/restricted Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/universe Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/multiverse Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/main amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/restricted amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/universe amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/multiverse amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/main Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/restricted Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/multiverse Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/restricted amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/multiverse amd64 Packages
Fetched 2,982 kB in 1s (1,638 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.4 MiB)

The attempt to merge lp:~lamont/maas/bug-1553176-1.9 into lp:maas/1.9 failed. Below is the output from the failed tests.

Ign http://prodstack-zone-1.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:2 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports InRelease
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty Release
Get:3 http://security.ubuntu.com trusty-security/main Sources [124 kB]
Get:4 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/main Sources [391 kB]
Get:5 http://security.ubuntu.com trusty-security/universe Sources [49.3 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [579 kB]
Get:7 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/restricted Sources [5,911 B]
Get:8 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/universe Sources [173 kB]
Get:9 http://security.ubuntu.com trusty-security/universe amd64 Packages [151 kB]
Get:10 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/multiverse Sources [7,535 B]
Get:11 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [947 kB]
Get:12 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/restricted amd64 Packages [16.4 kB]
Get:13 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [397 kB]
Get:14 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/multiverse amd64 Packages [14.0 kB]
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/main Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/restricted Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/universe Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/multiverse Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/main amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/restricted amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/universe amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/multiverse amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/main Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/restricted Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/multiverse Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/restricted amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/multiverse amd64 Packages
Fetched 2,987 kB in 1s (1,662 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.0 MiB)

The attempt to merge lp:~lamont/maas/bug-1553176-1.9 into lp:maas/1.9 failed. Below is the output from the failed tests.

Ign http://prodstack-zone-1.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Get:2 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports InRelease
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty Release
Get:3 http://security.ubuntu.com trusty-security/main Sources [125 kB]
Get:4 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/main Sources [392 kB]
Get:5 http://security.ubuntu.com trusty-security/universe Sources [49.6 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [580 kB]
Get:7 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/restricted Sources [5,911 B]
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [152 kB]
Get:9 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/universe Sources [174 kB]
Get:10 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/multiverse Sources [7,510 B]
Get:11 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [948 kB]
Get:12 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/restricted amd64 Packages [16.4 kB]
Get:13 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [398 kB]
Get:14 http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-updates/multiverse amd64 Packages [14.0 kB]
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/main Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/restricted Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/universe Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/multiverse Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/main amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/restricted amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/universe amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty-backports/multiverse amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/main Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/restricted Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/multiverse Sources
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/restricted amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://prodstack-zone-1.clouds.archive.ubuntu.com trusty/multiverse amd64 Packages
Fetched 2,995 kB in 1s (1,555 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/provisioningserver/utils/fs.py'
--- src/provisioningserver/utils/fs.py 2015-10-14 16:27:21 +0000
+++ src/provisioningserver/utils/fs.py 2017-02-08 18:01:00 +0000
@@ -52,10 +52,7 @@
52import sys52import sys
53import tempfile53import tempfile
54import threading54import threading
55from time import (55from time import sleep
56 sleep,
57 time,
58)
5956
60from provisioningserver.utils import sudo57from provisioningserver.utils import sudo
61from provisioningserver.utils.shell import ExternalProcessError58from provisioningserver.utils.shell import ExternalProcessError
@@ -207,69 +204,26 @@
207 raise204 raise
208205
209206
210def pick_new_mtime(old_mtime=None, starting_age=1000):
211 """Choose a new modification time for a file that needs it updated.
212
213 This function is used to manage the modification time of files
214 for which we need to see an increment in the modification time
215 each time the file is modified. This is the case for DNS zone
216 files which only get properly reloaded if BIND sees that the
217 modification time is > to the time it has in its database.
218
219 Modification time can have a resolution as low as one second in
220 some relevant environments (we have observed this with ext3).
221 To produce mtime changes regardless, we set a file's modification
222 time in the past when it is first written, and
223 increment it by 1 second on each subsequent write.
224
225 However we also want to be careful not to set the modification time
226 in the future, mostly because BIND does not deal with that very
227 well.
228
229 :param old_mtime: File's previous modification time, as a number
230 with a unity of one second, or None if it did not previously
231 exist.
232 :param starting_age: If the file did not exist previously, set its
233 modification time this many seconds in the past.
234 """
235 now = time()
236 if old_mtime is None:
237 # File is new. Set modification time in the past to have room for
238 # sub-second modifications.
239 return now - starting_age
240 elif old_mtime + 1 <= now:
241 # There is room to increment the file's mtime by one second
242 # without ending up in the future.
243 return old_mtime + 1
244 else:
245 # We can't increase the file's modification time. Give up and
246 # return the previous modification time.
247 return old_mtime
248
249
250def incremental_write(content, filename, mode=0o600):207def incremental_write(content, filename, mode=0o600):
251 """Write the given `content` into the file `filename` and208 """Write the given `content` into the file `filename`. In the past, this
252 increment the modification time by 1 sec.209 would potentially change modification time to arbitrary values.
253210
254 :param mode: Access permissions for the file.211 :param mode: Access permissions for the file.
255 """212 """
256 old_mtime = get_mtime(filename)213 # We used to change modification time on the files, in an attempt to out
214 # smart BIND into loading zones on file systems where time was not granular
215 # enough. BIND got smarter about how it loads zones and remembers when it
216 # last loaded the zone: either the then-current time, or the mtime of the
217 # file, whichever is later. When BIND then gets a reload request, it
218 # compares the current time to the loadtime for the domain, and skips it if
219 # the file is not new. If we set mtime in the past, then zones don't load.
220 # If we set it in the future, then we WILL sometimes hit the race condition
221 # where BIND looks at the time after we atomic_write, but before we manage
222 # to set the time into the future. N.B.: /etc on filesystems with 1-second
223 # granularity are no longer supported by MAAS. The good news is that since
224 # 2.6, linux has supported nanosecond-granular time. As of bind9
225 # 1:9.10.3.dfsg.P2-5, BIND even uses it.
257 atomic_write(content, filename, mode=mode)226 atomic_write(content, filename, mode=mode)
258 new_mtime = pick_new_mtime(old_mtime)
259 os.utime(filename, (new_mtime, new_mtime))
260
261
262def get_mtime(filename):
263 """Return a file's modification time, or None if it does not exist."""
264 try:
265 return os.stat(filename).st_mtime
266 except OSError as e:
267 if e.errno == errno.ENOENT:
268 # File does not exist. Be helpful, return None.
269 return None
270 else:
271 # Other failure. The caller will want to know.
272 raise
273227
274228
275def sudo_write_file(filename, contents, encoding='utf-8', mode=0o644):229def sudo_write_file(filename, contents, encoding='utf-8', mode=0o644):
276230
=== modified file 'src/provisioningserver/utils/tests/test_fs.py'
--- src/provisioningserver/utils/tests/test_fs.py 2015-10-14 18:00:50 +0000
+++ src/provisioningserver/utils/tests/test_fs.py 2017-02-08 18:01:00 +0000
@@ -17,7 +17,6 @@
17from base64 import urlsafe_b64encode17from base64 import urlsafe_b64encode
18import os18import os
19import os.path19import os.path
20from random import randint
21import re20import re
22from shutil import rmtree21from shutil import rmtree
23import stat22import stat
@@ -31,7 +30,6 @@
3130
32from maastesting import root31from maastesting import root
33from maastesting.factory import factory32from maastesting.factory import factory
34from maastesting.fakemethod import FakeMethod
35from maastesting.matchers import (33from maastesting.matchers import (
36 MockCalledOnceWith,34 MockCalledOnceWith,
37 MockCallsMatch,35 MockCallsMatch,
@@ -52,9 +50,7 @@
52 ensure_dir,50 ensure_dir,
53 FileLock,51 FileLock,
54 get_maas_provision_command,52 get_maas_provision_command,
55 get_mtime,
56 incremental_write,53 incremental_write,
57 pick_new_mtime,
58 read_text_file,54 read_text_file,
59 RunLock,55 RunLock,
60 sudo_delete_file,56 sudo_delete_file,
@@ -264,8 +260,22 @@
264 old_mtime = os.stat(filename).st_mtime - 10260 old_mtime = os.stat(filename).st_mtime - 10
265 os.utime(filename, (old_mtime, old_mtime))261 os.utime(filename, (old_mtime, old_mtime))
266 incremental_write(content, filename)262 incremental_write(content, filename)
267 self.assertAlmostEqual(263 # should be much closer to new_time than to old_mtime.
268 os.stat(filename).st_mtime, old_mtime + 1, delta=0.01)264 new_time = time.time()
265 self.assertAlmostEqual(
266 os.stat(filename).st_mtime, new_time, delta=2.0)
267
268 def test_incremental_write_does_not_set_future_time(self):
269 content = factory.make_bytes()
270 filename = self.make_file(contents=factory.make_string())
271 # Pretend that this file is older than it is. So that
272 # incrementing its mtime won't put it in the future.
273 old_mtime = os.stat(filename).st_mtime + 10
274 os.utime(filename, (old_mtime, old_mtime))
275 incremental_write(content, filename)
276 new_time = time.time()
277 self.assertAlmostEqual(
278 os.stat(filename).st_mtime, new_time, delta=2.0)
269279
270 def test_incremental_write_sets_permissions(self):280 def test_incremental_write_sets_permissions(self):
271 atomic_file = self.make_file()281 atomic_file = self.make_file()
@@ -274,54 +284,6 @@
274 self.assertEqual(mode, stat.S_IMODE(os.stat(atomic_file).st_mode))284 self.assertEqual(mode, stat.S_IMODE(os.stat(atomic_file).st_mode))
275285
276286
277class TestGetMTime(MAASTestCase):
278 """Test `get_mtime`."""
279
280 def test_get_mtime_returns_None_for_nonexistent_file(self):
281 nonexistent_file = os.path.join(
282 self.make_dir(), factory.make_name('nonexistent-file'))
283 self.assertIsNone(get_mtime(nonexistent_file))
284
285 def test_get_mtime_returns_mtime(self):
286 existing_file = self.make_file()
287 mtime = os.stat(existing_file).st_mtime - randint(0, 100)
288 os.utime(existing_file, (mtime, mtime))
289 # Some small rounding/representation errors can happen here.
290 # That's just the way of floating-point numbers. According to
291 # Gavin there's a conversion to fixed-point along the way, which
292 # would raise representability issues.
293 self.assertAlmostEqual(mtime, get_mtime(existing_file), delta=0.00001)
294
295 def test_get_mtime_passes_on_other_error(self):
296 forbidden_file = self.make_file()
297 self.patch(os, 'stat', FakeMethod(failure=OSError("Forbidden file")))
298 self.assertRaises(OSError, get_mtime, forbidden_file)
299
300
301class TestPickNewMTime(MAASTestCase):
302 """Test `pick_new_mtime`."""
303
304 def test_pick_new_mtime_applies_starting_age_to_new_file(self):
305 before = time.time()
306 starting_age = randint(0, 5)
307 recommended_age = pick_new_mtime(None, starting_age=starting_age)
308 now = time.time()
309 self.assertAlmostEqual(
310 now - starting_age,
311 recommended_age,
312 delta=(now - before))
313
314 def test_pick_new_mtime_increments_mtime_if_possible(self):
315 past = time.time() - 2
316 self.assertEqual(past + 1, pick_new_mtime(past))
317
318 def test_pick_new_mtime_refuses_to_move_mtime_into_the_future(self):
319 # Race condition: this will fail if the test gets held up for
320 # a second between readings of the clock.
321 now = time.time()
322 self.assertEqual(now, pick_new_mtime(now))
323
324
325class TestGetMAASProvisionCommand(MAASTestCase):287class TestGetMAASProvisionCommand(MAASTestCase):
326288
327 def test__returns_just_command_for_production(self):289 def test__returns_just_command_for_production(self):

Subscribers

People subscribed via source and target branches