Merge lp:~julian-edwards/maas/touch-maasmeta into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 2599
Proposed branch: lp:~julian-edwards/maas/touch-maasmeta
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 91 lines (+51/-4)
2 files modified
src/provisioningserver/import_images/boot_resources.py (+9/-4)
src/provisioningserver/import_images/tests/test_boot_resources.py (+42/-0)
To merge this branch: bzr merge lp:~julian-edwards/maas/touch-maasmeta
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Raphaël Badin (community) Approve
Review via email: mp+228301@code.launchpad.net

Commit message

When checking maas.meta, update its utime so we can determine elsewhere the last time the import ran

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

Doesn't seem right to hide "and oh yes, modify its timestamp" in a function called meta_contains. Why not do this one level up, in import_images?

    if meta_contains(storage, meta_file_content):
        # No changes. Update the meta file's timestamp so that future
        # import runs can see that we checked, but otherwise we're done.
        update_meta_timestamp(storage)
        return

Jeroen

review: Needs Fixing
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

By the way, we have a testing helper called age_file that might save you some work.

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

Nice! Two tiny remarks (see inline).

review: Approve
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

You're cramming three tests into a single test method whose name says it will only do one of them. Probably just a sign that your setup is too expensive to do in-line.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Friday 25 Jul 2014 13:31:55 you wrote:
> Review: Needs Fixing
>
> Doesn't seem right to hide "and oh yes, modify its timestamp" in a function
> called meta_contains. Why not do this one level up, in import_images?

I disagree, I want the timestamp changed *every single time* someone even
looks at this file.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Friday 25 Jul 2014 13:32:58 you wrote:
> By the way, we have a testing helper called age_file that might save you
> some work.

Ah didn't know about that, thanks.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Friday 25 Jul 2014 13:34:48 you wrote:
> You're cramming three tests into a single test method whose name says it
> will only do one of them. Probably just a sign that your setup is too
> expensive to do in-line.

I'm doing expensive set up because there were no existing tests AT ALL for
this function.

Just for you, I've reworked the tests!

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Jeroen, I wanted you to re-check but you're not on IRC so since rvb approved it I'm going to land it. If you want more changes done I'm happy to oblige later.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Much better, thanks. If you really have to keep the side effect, rename the function to something that clearly expresses its new meaning: "update the meta file's modification timestamp, and return whether it contains exactly this data." Just don't blame me if that's messy. :-)

In the test, just to get rid of spurious failures at midnight UTC on those pesky filesystems that have a timestamp resolution of a full second, consider subtracting timestamps and checking that the difference is less than a timedelta of one day. Also, if getmtime is awkward, consider using the get_write_time helper that's defined just below age_file. I don't think the cast to int is needed.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Friday 25 Jul 2014 14:23:53 you wrote:
> Review: Approve
>
> Much better, thanks. If you really have to keep the side effect, rename the
> function to something that clearly expresses its new meaning: "update the
> meta file's modification timestamp, and return whether it contains exactly
> this data." Just don't blame me if that's messy. :-)

Yeah I did think of doing that and pushed the fsckit button too quickly.

> In the test, just to get rid of spurious failures at midnight UTC on those
> pesky filesystems that have a timestamp resolution of a full second,
> consider subtracting timestamps and checking that the difference is less
> than a timedelta of one day. Also, if getmtime is awkward, consider using
> the get_write_time helper that's defined just below age_file. I don't
> think the cast to int is needed.

I must admit after I landed it I wondered about the failures around midnight.
There's an assertAlmostEquals which I'll drop in on the next iteration!

Cheers.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/import_images/boot_resources.py'
2--- src/provisioningserver/import_images/boot_resources.py 2014-07-14 14:31:14 +0000
3+++ src/provisioningserver/import_images/boot_resources.py 2014-07-25 13:51:33 +0000
4@@ -143,12 +143,17 @@
5 """Does the `maas.meta` file match `content`?
6
7 If the file's contents match the latest data, there is no need to update.
8+
9+ The file's timestamp is also updated to now to reflect the last time
10+ that this import was run.
11 """
12 current_meta = os.path.join(storage, 'current', 'maas.meta')
13- return (
14- os.path.isfile(current_meta) and
15- content == read_text_file(current_meta)
16- )
17+ exists = os.path.isfile(current_meta)
18+ if exists:
19+ # Touch file to the current timestamp so that the last time this
20+ # import ran can be determined.
21+ os.utime(current_meta, None)
22+ return exists and content == read_text_file(current_meta)
23
24
25 def update_current_symlink(storage, latest_snapshot):
26
27=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
28--- src/provisioningserver/import_images/tests/test_boot_resources.py 2014-07-14 14:31:14 +0000
29+++ src/provisioningserver/import_images/tests/test_boot_resources.py 2014-07-25 13:51:33 +0000
30@@ -15,6 +15,10 @@
31 __all__ = []
32
33 import errno
34+from datetime import (
35+ datetime,
36+ timedelta,
37+ )
38 import hashlib
39 import json
40 import os
41@@ -30,6 +34,7 @@
42 MockCalledWith,
43 )
44 from maastesting.testcase import MAASTestCase
45+from maastesting.utils import age_file
46 import mock
47 from provisioningserver.boot import BootMethodRegistry
48 import provisioningserver.config
49@@ -388,6 +393,43 @@
50 boot_resources.main, self.make_args(sources="", sources_file=""))
51
52
53+class TestMetaContains(MAASTestCase):
54+ """Tests for the `meta_contains` function."""
55+
56+ def make_meta_file(self, content=None):
57+ if content is None:
58+ content = factory.make_string()
59+ storage = self.make_dir()
60+ current = os.path.join(storage, 'current')
61+ os.mkdir(current)
62+ return storage, factory.make_file(current, 'maas.meta', content)
63+
64+ def test_matching_content_is_compared_True(self):
65+ content = factory.make_string()
66+ storage, meta_file = self.make_meta_file(content)
67+ self.assertTrue(boot_resources.meta_contains(storage, content))
68+
69+ def test_mismatching_content_is_compared_False(self):
70+ content = factory.make_string()
71+ storage, meta_file = self.make_meta_file()
72+ self.assertFalse(boot_resources.meta_contains(storage, content))
73+
74+ def test_meta_contains_updates_file_timestamp(self):
75+ content = factory.make_string()
76+ storage, meta_file = self.make_meta_file(content)
77+
78+ # Change the file's timestamp to a week ago.
79+ one_week_ago = timedelta(weeks=1).total_seconds()
80+ age_file(meta_file, one_week_ago)
81+
82+ boot_resources.meta_contains(storage, content)
83+
84+ # Check the timestamp was updated.
85+ expected_date = datetime.now()
86+ actual_date = datetime.fromtimestamp(int(os.path.getmtime(meta_file)))
87+ self.assertEqual(expected_date.day, actual_date.day)
88+
89+
90 class TestParseSources(MAASTestCase):
91 """Tests for the `parse_sources` function."""
92