Merge ~smoser/cloud-init:bug/1717147-centos-cloudstack-dhclient into cloud-init:master

Proposed by Scott Moser
Status: Merged
Merged at revision: da1db792b2721d94ef85df8c136e78012c49c6e5
Proposed branch: ~smoser/cloud-init:bug/1717147-centos-cloudstack-dhclient
Merge into: cloud-init:master
Diff against target: 149 lines (+100/-13)
2 files modified
cloudinit/sources/DataSourceCloudStack.py (+24/-10)
tests/unittests/test_datasource/test_cloudstack.py (+76/-3)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Chad Smith Approve
Review via email: mp+330846@code.launchpad.net

Commit message

CloudStack: consider dhclient lease files named with a hyphen.

A regression in 'get_latest_lease' made it ignore files starting
with 'dhclient-' rather than just 'dhclient.'. The fix here is
to allow those files to be considered.

There is a lot more we could do here to better ensure that we pick
the most recent lease, but this change fixes the regression.

LP: #1717147

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:6b6dbcedb27d9c1fb99430ae54b217663933e4fc
https://jenkins.ubuntu.com/server/job/cloud-init-ci/306/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/306/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:36512b88fe4338ad3c6b6d1fc03905674d42fe8c
https://jenkins.ubuntu.com/server/job/cloud-init-ci/308/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/308/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:fce3e6f7d1fb5e80a2af9e8e15c3ad05e1c2da09
https://jenkins.ubuntu.com/server/job/cloud-init-ci/309/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/309/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

Looks good!

flake error and one line comment:
tests/unittests/test_datasource/test_cloudstack.py:113:5: E303 too many blank lines (2)

review: Approve
Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:2271ffbd0f2cba146ad938aeab876a9d9aca680e
https://jenkins.ubuntu.com/server/job/cloud-init-ci/311/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/311/rebuild

review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:0e16612b7c4f64e34d412634471ceee5b9efbbdf
https://jenkins.ubuntu.com/server/job/cloud-init-ci/313/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/313/rebuild

review: Approve (continuous-integration)

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py
2index 0188d89..7e0f9bb 100644
3--- a/cloudinit/sources/DataSourceCloudStack.py
4+++ b/cloudinit/sources/DataSourceCloudStack.py
5@@ -187,22 +187,36 @@ def get_dhclient_d():
6 return None
7
8
9-def get_latest_lease():
10+def get_latest_lease(lease_d=None):
11 # find latest lease file
12- lease_d = get_dhclient_d()
13+ if lease_d is None:
14+ lease_d = get_dhclient_d()
15 if not lease_d:
16 return None
17 lease_files = os.listdir(lease_d)
18 latest_mtime = -1
19 latest_file = None
20- for file_name in lease_files:
21- if file_name.startswith("dhclient.") and \
22- (file_name.endswith(".lease") or file_name.endswith(".leases")):
23- abs_path = os.path.join(lease_d, file_name)
24- mtime = os.path.getmtime(abs_path)
25- if mtime > latest_mtime:
26- latest_mtime = mtime
27- latest_file = abs_path
28+
29+ # lease files are named inconsistently across distros.
30+ # We assume that 'dhclient6' indicates ipv6 and ignore it.
31+ # ubuntu:
32+ # dhclient.<iface>.leases, dhclient.leases, dhclient6.leases
33+ # centos6:
34+ # dhclient-<iface>.leases, dhclient6.leases
35+ # centos7: ('--' is not a typo)
36+ # dhclient--<iface>.lease, dhclient6.leases
37+ for fname in lease_files:
38+ if fname.startswith("dhclient6"):
39+ # avoid files that start with dhclient6 assuming dhcpv6.
40+ continue
41+ if not (fname.endswith(".lease") or fname.endswith(".leases")):
42+ continue
43+
44+ abs_path = os.path.join(lease_d, fname)
45+ mtime = os.path.getmtime(abs_path)
46+ if mtime > latest_mtime:
47+ latest_mtime = mtime
48+ latest_file = abs_path
49 return latest_file
50
51
52diff --git a/tests/unittests/test_datasource/test_cloudstack.py b/tests/unittests/test_datasource/test_cloudstack.py
53index 2dc9030..8e98e1b 100644
54--- a/tests/unittests/test_datasource/test_cloudstack.py
55+++ b/tests/unittests/test_datasource/test_cloudstack.py
56@@ -1,12 +1,17 @@
57 # This file is part of cloud-init. See LICENSE file for license information.
58
59 from cloudinit import helpers
60-from cloudinit.sources.DataSourceCloudStack import DataSourceCloudStack
61+from cloudinit import util
62+from cloudinit.sources.DataSourceCloudStack import (
63+ DataSourceCloudStack, get_latest_lease)
64
65-from cloudinit.tests.helpers import TestCase, mock, ExitStack
66+from cloudinit.tests.helpers import CiTestCase, ExitStack, mock
67
68+import os
69+import time
70
71-class TestCloudStackPasswordFetching(TestCase):
72+
73+class TestCloudStackPasswordFetching(CiTestCase):
74
75 def setUp(self):
76 super(TestCloudStackPasswordFetching, self).setUp()
77@@ -89,4 +94,72 @@ class TestCloudStackPasswordFetching(TestCase):
78 def test_password_not_saved_if_bad_request(self):
79 self._check_password_not_saved_for('bad_request')
80
81+
82+class TestGetLatestLease(CiTestCase):
83+
84+ def _populate_dir_list(self, bdir, files):
85+ """populate_dir_list([(name, data), (name, data)])
86+
87+ writes files to bdir, and updates timestamps to ensure
88+ that their mtime increases with each file."""
89+
90+ start = int(time.time())
91+ for num, fname in enumerate(reversed(files)):
92+ fpath = os.path.sep.join((bdir, fname))
93+ util.write_file(fpath, fname.encode())
94+ os.utime(fpath, (start - num, start - num))
95+
96+ def _pop_and_test(self, files, expected):
97+ lease_d = self.tmp_dir()
98+ self._populate_dir_list(lease_d, files)
99+ self.assertEqual(self.tmp_path(expected, lease_d),
100+ get_latest_lease(lease_d))
101+
102+ def test_skips_dhcpv6_files(self):
103+ """files started with dhclient6 should be skipped."""
104+ expected = "dhclient.lease"
105+ self._pop_and_test([expected, "dhclient6.lease"], expected)
106+
107+ def test_selects_dhclient_dot_files(self):
108+ """files named dhclient.lease or dhclient.leases should be used.
109+
110+ Ubuntu names files dhclient.eth0.leases dhclient6.leases and
111+ sometimes dhclient.leases."""
112+ self._pop_and_test(["dhclient.lease"], "dhclient.lease")
113+ self._pop_and_test(["dhclient.leases"], "dhclient.leases")
114+
115+ def test_selects_dhclient_dash_files(self):
116+ """files named dhclient-lease or dhclient-leases should be used.
117+
118+ Redhat/Centos names files with dhclient--eth0.lease (centos 7) or
119+ dhclient-eth0.leases (centos 6).
120+ """
121+ self._pop_and_test(["dhclient-eth0.lease"], "dhclient-eth0.lease")
122+ self._pop_and_test(["dhclient--eth0.lease"], "dhclient--eth0.lease")
123+
124+ def test_ignores_by_extension(self):
125+ """only .lease or .leases file should be considered."""
126+
127+ self._pop_and_test(["dhclient.lease", "dhclient.lease.bk",
128+ "dhclient.lease-old", "dhclient.leaselease"],
129+ "dhclient.lease")
130+
131+ def test_selects_newest_matching(self):
132+ """If multiple files match, the newest written should be used."""
133+ lease_d = self.tmp_dir()
134+ valid_1 = "dhclient.leases"
135+ valid_2 = "dhclient.lease"
136+ valid_1_path = self.tmp_path(valid_1, lease_d)
137+ valid_2_path = self.tmp_path(valid_2, lease_d)
138+
139+ self._populate_dir_list(lease_d, [valid_1, valid_2])
140+ self.assertEqual(valid_2_path, get_latest_lease(lease_d))
141+
142+ # now update mtime on valid_2 to be older than valid_1 and re-check.
143+ mtime = int(os.path.getmtime(valid_1_path)) - 1
144+ os.utime(valid_2_path, (mtime, mtime))
145+
146+ self.assertEqual(valid_1_path, get_latest_lease(lease_d))
147+
148+
149 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches