Merge ~smoser/cloud-init:cleanup/ec2-initial-tests into cloud-init:master

Proposed by Scott Moser on 2017-07-17
Status: Merged
Approved by: Scott Moser on 2017-07-25
Approved revision: 7181d66d3693919615a884f4e9f43aa14b422244
Merged at revision: ebdbf30c0274f078f7a66f6dc9efc8a22a220757
Proposed branch: ~smoser/cloud-init:cleanup/ec2-initial-tests
Merge into: cloud-init:master
Diff against target: 233 lines (+212/-4)
2 files modified
cloudinit/sources/DataSourceEc2.py (+10/-4)
tests/unittests/test_datasource/test_ec2.py (+202/-0)
Reviewer Review Type Date Requested Status
Chad Smith 2017-07-17 Approve on 2017-07-18
Server Team CI bot continuous-integration Approve on 2017-07-18
Review via email: mp+327534@code.launchpad.net

Commit Message

tests: Add initial tests for EC2 and improve a docstring.

EC2 was the original, but this adds some initial tests for that datasource.
Also updates a docstring for an internal method.

To post a comment you must log in.

PASSED: Continuous integration, rev:fe6f267f52e3532693df0e7684012cd8b58792cc
https://jenkins.ubuntu.com/server/job/cloud-init-ci/43/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: CentOS 6 & 7: Build & Test
    IN_PROGRESS: Declarative: Post Actions

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

review: Approve (continuous-integration)
Ryan Harper (raharper) wrote :

Inline comment below

Scott Moser (smoser) :
8a99a17... by Scott Moser on 2017-07-18

clean up the 'register_helper' a bit per Ryan's feedback

Scott Moser (smoser) wrote :

i cleaned up that register_helper a bit, thanks for the review, Ryan.

PASSED: Continuous integration, rev:7181d66d3693919615a884f4e9f43aa14b422244
https://jenkins.ubuntu.com/server/job/cloud-init-ci/49/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: CentOS 6 & 7: Build & Test
    IN_PROGRESS: Declarative: Post Actions

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

review: Approve (continuous-integration)
Chad Smith (chad.smith) :
Chad Smith (chad.smith) :
Chad Smith (chad.smith) wrote :

Approve as is, just some minor suggestions to think about. Tackle them if you think it's worth it. Since Aliyun also does the same kindof mocking, it'd be nice if we could put mock_metadata function into a common tests.unittests.testing directory to avoid duplication and align both test_aliyun and test_ec2 testing with that solution. But, that doesn't have to happen in this branch.

review: Approve
Scott Moser (smoser) wrote :

i agree on the remove of duplication. i tried to get you to do that for me :)

Chad Smith (chad.smith) :
Scott Moser (smoser) :
Scott Moser (smoser) :
b37f805... by Scott Moser on 2017-07-25

fix Chad's suggestions in review.

Scott Moser (smoser) wrote :

you did ack this, so i'lll just pull this.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
2index 9e2fdc0..4ec9592 100644
3--- a/cloudinit/sources/DataSourceEc2.py
4+++ b/cloudinit/sources/DataSourceEc2.py
5@@ -316,10 +316,16 @@ def identify_platform():
6
7
8 def _collect_platform_data():
9- # returns a dictionary with all lower case values:
10- # uuid: system-uuid from dmi or /sys/hypervisor
11- # uuid_source: 'hypervisor' (/sys/hypervisor/uuid) or 'dmi'
12- # serial: dmi 'system-serial-number' (/sys/.../product_serial)
13+ """Returns a dictionary of platform info from dmi or /sys/hypervisor.
14+
15+ Keys in the dictionary are as follows:
16+ uuid: system-uuid from dmi or /sys/hypervisor
17+ uuid_source: 'hypervisor' (/sys/hypervisor/uuid) or 'dmi'
18+ serial: dmi 'system-serial-number' (/sys/.../product_serial)
19+
20+ On Ec2 instances experimentation is that product_serial is upper case,
21+ and product_uuid is lower case. This returns lower case values for both.
22+ """
23 data = {}
24 try:
25 uuid = util.load_file("/sys/hypervisor/uuid").strip()
26diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
27new file mode 100644
28index 0000000..12230ae
29--- /dev/null
30+++ b/tests/unittests/test_datasource/test_ec2.py
31@@ -0,0 +1,202 @@
32+# This file is part of cloud-init. See LICENSE file for license information.
33+
34+import httpretty
35+import mock
36+
37+from .. import helpers as test_helpers
38+from cloudinit import helpers
39+from cloudinit.sources import DataSourceEc2 as ec2
40+
41+
42+# collected from api version 2009-04-04/ with
43+# python3 -c 'import json
44+# from cloudinit.ec2_utils import get_instance_metadata as gm
45+# print(json.dumps(gm("2009-04-04"), indent=1, sort_keys=True))'
46+DEFAULT_METADATA = {
47+ "ami-id": "ami-80861296",
48+ "ami-launch-index": "0",
49+ "ami-manifest-path": "(unknown)",
50+ "block-device-mapping": {"ami": "/dev/sda1", "root": "/dev/sda1"},
51+ "hostname": "ip-10-0-0-149",
52+ "instance-action": "none",
53+ "instance-id": "i-0052913950685138c",
54+ "instance-type": "t2.micro",
55+ "local-hostname": "ip-10-0-0-149",
56+ "local-ipv4": "10.0.0.149",
57+ "placement": {"availability-zone": "us-east-1b"},
58+ "profile": "default-hvm",
59+ "public-hostname": "",
60+ "public-ipv4": "107.23.188.247",
61+ "public-keys": {"brickies": ["ssh-rsa AAAAB3Nz....w== brickies"]},
62+ "reservation-id": "r-00a2c173fb5782a08",
63+ "security-groups": "wide-open"
64+}
65+
66+
67+def _register_ssh_keys(rfunc, base_url, keys_data):
68+ """handle ssh key inconsistencies.
69+
70+ public-keys in the ec2 metadata is inconsistently formatted compared
71+ to other entries.
72+ Given keys_data of {name1: pubkey1, name2: pubkey2}
73+
74+ This registers the following urls:
75+ base_url 0={name1}\n1={name2} # (for each name)
76+ base_url/ 0={name1}\n1={name2} # (for each name)
77+ base_url/0 openssh-key
78+ base_url/0/ openssh-key
79+ base_url/0/openssh-key {pubkey1}
80+ base_url/0/openssh-key/ {pubkey1}
81+ ...
82+ """
83+
84+ base_url = base_url.rstrip("/")
85+ odd_index = '\n'.join(
86+ ["{0}={1}".format(n, name)
87+ for n, name in enumerate(sorted(keys_data))])
88+
89+ rfunc(base_url, odd_index)
90+ rfunc(base_url + "/", odd_index)
91+
92+ for n, name in enumerate(sorted(keys_data)):
93+ val = keys_data[name]
94+ if isinstance(val, list):
95+ val = '\n'.join(val)
96+ burl = base_url + "/%s" % n
97+ rfunc(burl, "openssh-key")
98+ rfunc(burl + "/", "openssh-key")
99+ rfunc(burl + "/%s/openssh-key" % name, val)
100+ rfunc(burl + "/%s/openssh-key/" % name, val)
101+
102+
103+def register_mock_metaserver(base_url, data):
104+ """Register with httpretty a ec2 metadata like service serving 'data'.
105+
106+ If given a dictionary, it will populate urls under base_url for
107+ that dictionary. For example, input of
108+ {"instance-id": "i-abc", "mac": "00:16:3e:00:00:00"}
109+ populates
110+ base_url with 'instance-id\nmac'
111+ base_url/ with 'instance-id\nmac'
112+ base_url/instance-id with i-abc
113+ base_url/mac with 00:16:3e:00:00:00
114+ In the index, references to lists or dictionaries have a trailing /.
115+ """
116+ def register_helper(register, base_url, body):
117+ base_url = base_url.rstrip("/")
118+ if isinstance(body, str):
119+ register(base_url, body)
120+ elif isinstance(body, list):
121+ register(base_url, '\n'.join(body) + '\n')
122+ register(base_url + '/', '\n'.join(body) + '\n')
123+ elif isinstance(body, dict):
124+ vals = []
125+ for k, v in body.items():
126+ if k == 'public-keys':
127+ _register_ssh_keys(
128+ register, base_url + '/public-keys/', v)
129+ continue
130+ suffix = k.rstrip("/")
131+ if not isinstance(v, (str, list)):
132+ suffix += "/"
133+ vals.append(suffix)
134+ url = base_url + '/' + suffix
135+ register_helper(register, url, v)
136+ register(base_url, '\n'.join(vals) + '\n')
137+ register(base_url + '/', '\n'.join(vals) + '\n')
138+ elif body is None:
139+ register(base_url, 'not found', status_code=404)
140+
141+ def myreg(*argc, **kwargs):
142+ # print("register_url(%s, %s)" % (argc, kwargs))
143+ return httpretty.register_uri(httpretty.GET, *argc, **kwargs)
144+
145+ register_helper(myreg, base_url, data)
146+
147+
148+class TestEc2(test_helpers.HttprettyTestCase):
149+ valid_platform_data = {
150+ 'uuid': 'ec212f79-87d1-2f1d-588f-d86dc0fd5412',
151+ 'uuid_source': 'dmi',
152+ 'serial': 'ec212f79-87d1-2f1d-588f-d86dc0fd5412',
153+ }
154+
155+ def setUp(self):
156+ super(TestEc2, self).setUp()
157+ self.metadata_addr = ec2.DataSourceEc2.metadata_urls[0]
158+ self.api_ver = '2009-04-04'
159+
160+ @property
161+ def metadata_url(self):
162+ return '/'.join([self.metadata_addr, self.api_ver, 'meta-data', ''])
163+
164+ @property
165+ def userdata_url(self):
166+ return '/'.join([self.metadata_addr, self.api_ver, 'user-data'])
167+
168+ def _patch_add_cleanup(self, mpath, *args, **kwargs):
169+ p = mock.patch(mpath, *args, **kwargs)
170+ p.start()
171+ self.addCleanup(p.stop)
172+
173+ def _setup_ds(self, sys_cfg, platform_data, md, ud=None):
174+ distro = {}
175+ paths = helpers.Paths({})
176+ if sys_cfg is None:
177+ sys_cfg = {}
178+ ds = ec2.DataSourceEc2(sys_cfg=sys_cfg, distro=distro, paths=paths)
179+ if platform_data is not None:
180+ self._patch_add_cleanup(
181+ "cloudinit.sources.DataSourceEc2._collect_platform_data",
182+ return_value=platform_data)
183+
184+ if md:
185+ register_mock_metaserver(self.metadata_url, md)
186+ register_mock_metaserver(self.userdata_url, ud)
187+
188+ return ds
189+
190+ @httpretty.activate
191+ def test_valid_platform_with_strict_true(self):
192+ """Valid platform data should return true with strict_id true."""
193+ ds = self._setup_ds(
194+ platform_data=self.valid_platform_data,
195+ sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
196+ md=DEFAULT_METADATA)
197+ ret = ds.get_data()
198+ self.assertEqual(True, ret)
199+
200+ @httpretty.activate
201+ def test_valid_platform_with_strict_false(self):
202+ """Valid platform data should return true with strict_id false."""
203+ ds = self._setup_ds(
204+ platform_data=self.valid_platform_data,
205+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
206+ md=DEFAULT_METADATA)
207+ ret = ds.get_data()
208+ self.assertEqual(True, ret)
209+
210+ @httpretty.activate
211+ def test_unknown_platform_with_strict_true(self):
212+ """Unknown platform data with strict_id true should return False."""
213+ uuid = 'ab439480-72bf-11d3-91fc-b8aded755F9a'
214+ ds = self._setup_ds(
215+ platform_data={'uuid': uuid, 'uuid_source': 'dmi', 'serial': ''},
216+ sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
217+ md=DEFAULT_METADATA)
218+ ret = ds.get_data()
219+ self.assertEqual(False, ret)
220+
221+ @httpretty.activate
222+ def test_unknown_platform_with_strict_false(self):
223+ """Unknown platform data with strict_id false should return True."""
224+ uuid = 'ab439480-72bf-11d3-91fc-b8aded755F9a'
225+ ds = self._setup_ds(
226+ platform_data={'uuid': uuid, 'uuid_source': 'dmi', 'serial': ''},
227+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
228+ md=DEFAULT_METADATA)
229+ ret = ds.get_data()
230+ self.assertEqual(True, ret)
231+
232+
233+# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches

to all changes: