Merge lp:~oddbloke/cloud-init/lp1506187 into lp:~cloud-init-dev/cloud-init/trunk
- lp1506187
- Merge into trunk
Proposed by
Dan Watkins
Status: | Merged |
---|---|
Merged at revision: | 1156 |
Proposed branch: | lp:~oddbloke/cloud-init/lp1506187 |
Merge into: | lp:~cloud-init-dev/cloud-init/trunk |
Diff against target: |
379 lines (+23/-155) 4 files modified
cloudinit/sources/DataSourceAzure.py (+3/-35) cloudinit/sources/helpers/azure.py (+0/-21) tests/unittests/test_datasource/test_azure.py (+18/-59) tests/unittests/test_datasource/test_azure_helper.py (+2/-40) |
To merge this branch: | bzr merge lp:~oddbloke/cloud-init/lp1506187 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ben Howard (community) | Approve | ||
Scott Moser | Pending | ||
Review via email:
|
Commit message
Azure: Use DMI data to find instance IDs.
Replace the use of SharedConfig.xml in both the walinuxagent case,
and the case where we communicate with the Azure fabric ourselves.
Description of the change
To post a comment you must log in.
Revision history for this message

Ben Howard (darkmuggle-deactivatedaccount) wrote : | # |
review:
Approve
Revision history for this message

Scott Moser (smoser) wrote : | # |
This looks good. The one question I have is whether or not a instance that used sharedconfig.xml last boot will now think it is a new instance after an upgrade of cloud-init and reboot.
specifically, is the instance id in DMI the same as the instance id in SharedConfig.xml
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'cloudinit/sources/DataSourceAzure.py' |
2 | --- cloudinit/sources/DataSourceAzure.py 2015-10-15 00:27:07 +0000 |
3 | +++ cloudinit/sources/DataSourceAzure.py 2015-10-30 16:36:54 +0000 |
4 | @@ -31,8 +31,7 @@ |
5 | from cloudinit.settings import PER_ALWAYS |
6 | from cloudinit import sources |
7 | from cloudinit import util |
8 | -from cloudinit.sources.helpers.azure import ( |
9 | - get_metadata_from_fabric, iid_from_shared_config_content) |
10 | +from cloudinit.sources.helpers.azure import get_metadata_from_fabric |
11 | |
12 | LOG = logging.getLogger(__name__) |
13 | |
14 | @@ -41,7 +40,6 @@ |
15 | AGENT_START = ['service', 'walinuxagent', 'start'] |
16 | BOUNCE_COMMAND = ['sh', '-xc', |
17 | "i=$interface; x=0; ifdown $i || x=$?; ifup $i || x=$?; exit $x"] |
18 | -DATA_DIR_CLEAN_LIST = ['SharedConfig.xml'] |
19 | |
20 | BUILTIN_DS_CONFIG = { |
21 | 'agent_command': AGENT_START, |
22 | @@ -144,8 +142,6 @@ |
23 | self.ds_cfg['agent_command']) |
24 | |
25 | ddir = self.ds_cfg['data_dir'] |
26 | - shcfgxml = os.path.join(ddir, "SharedConfig.xml") |
27 | - wait_for = [shcfgxml] |
28 | |
29 | fp_files = [] |
30 | key_value = None |
31 | @@ -160,19 +156,11 @@ |
32 | |
33 | missing = util.log_time(logfunc=LOG.debug, msg="waiting for files", |
34 | func=wait_for_files, |
35 | - args=(wait_for + fp_files,)) |
36 | + args=(fp_files,)) |
37 | if len(missing): |
38 | LOG.warn("Did not find files, but going on: %s", missing) |
39 | |
40 | metadata = {} |
41 | - if shcfgxml in missing: |
42 | - LOG.warn("SharedConfig.xml missing, using static instance-id") |
43 | - else: |
44 | - try: |
45 | - metadata['instance-id'] = iid_from_shared_config(shcfgxml) |
46 | - except ValueError as e: |
47 | - LOG.warn("failed to get instance id in %s: %s", shcfgxml, e) |
48 | - |
49 | metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files) |
50 | return metadata |
51 | |
52 | @@ -229,21 +217,6 @@ |
53 | user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {}) |
54 | self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg]) |
55 | |
56 | - if found != ddir: |
57 | - cached_ovfenv = util.load_file( |
58 | - os.path.join(ddir, 'ovf-env.xml'), quiet=True, decode=False) |
59 | - if cached_ovfenv != files['ovf-env.xml']: |
60 | - # source was not walinux-agent's datadir, so we have to clean |
61 | - # up so 'wait_for_files' doesn't return early due to stale data |
62 | - cleaned = [] |
63 | - for f in [os.path.join(ddir, f) for f in DATA_DIR_CLEAN_LIST]: |
64 | - if os.path.exists(f): |
65 | - util.del_file(f) |
66 | - cleaned.append(f) |
67 | - if cleaned: |
68 | - LOG.info("removed stale file(s) in '%s': %s", |
69 | - ddir, str(cleaned)) |
70 | - |
71 | # walinux agent writes files world readable, but expects |
72 | # the directory to be protected. |
73 | write_files(ddir, files, dirmode=0o700) |
74 | @@ -259,6 +232,7 @@ |
75 | " on Azure.", exc_info=True) |
76 | return False |
77 | |
78 | + self.metadata['instance-id'] = util.read_dmi_data('system-uuid') |
79 | self.metadata.update(fabric_data) |
80 | |
81 | found_ephemeral = find_fabric_formatted_ephemeral_disk() |
82 | @@ -649,12 +623,6 @@ |
83 | return (md, ud, cfg, {'ovf-env.xml': contents}) |
84 | |
85 | |
86 | -def iid_from_shared_config(path): |
87 | - with open(path, "rb") as fp: |
88 | - content = fp.read() |
89 | - return iid_from_shared_config_content(content) |
90 | - |
91 | - |
92 | class BrokenAzureDataSource(Exception): |
93 | pass |
94 | |
95 | |
96 | === modified file 'cloudinit/sources/helpers/azure.py' |
97 | --- cloudinit/sources/helpers/azure.py 2015-05-08 15:52:58 +0000 |
98 | +++ cloudinit/sources/helpers/azure.py 2015-10-30 16:36:54 +0000 |
99 | @@ -79,12 +79,6 @@ |
100 | './Container/RoleInstanceList/RoleInstance/InstanceId') |
101 | |
102 | @property |
103 | - def shared_config_xml(self): |
104 | - url = self._text_from_xpath('./Container/RoleInstanceList/RoleInstance' |
105 | - '/Configuration/SharedConfig') |
106 | - return self.http_client.get(url).contents |
107 | - |
108 | - @property |
109 | def certificates_xml(self): |
110 | if self._certificates_xml is None: |
111 | url = self._text_from_xpath( |
112 | @@ -172,19 +166,6 @@ |
113 | return keys |
114 | |
115 | |
116 | -def iid_from_shared_config_content(content): |
117 | - """ |
118 | - find INSTANCE_ID in: |
119 | - <?xml version="1.0" encoding="utf-8"?> |
120 | - <SharedConfig version="1.0.0.0" goalStateIncarnation="1"> |
121 | - <Deployment name="INSTANCE_ID" guid="{...}" incarnation="0"> |
122 | - <Service name="..." guid="{00000000-0000-0000-0000-000000000000}"/> |
123 | - """ |
124 | - root = ElementTree.fromstring(content) |
125 | - depnode = root.find('Deployment') |
126 | - return depnode.get('name') |
127 | - |
128 | - |
129 | class WALinuxAgentShim(object): |
130 | |
131 | REPORT_READY_XML_TEMPLATE = '\n'.join([ |
132 | @@ -263,8 +244,6 @@ |
133 | public_keys = self.openssl_manager.parse_certificates( |
134 | goal_state.certificates_xml) |
135 | data = { |
136 | - 'instance-id': iid_from_shared_config_content( |
137 | - goal_state.shared_config_xml), |
138 | 'public-keys': public_keys, |
139 | } |
140 | self._report_ready(goal_state, http_client) |
141 | |
142 | === modified file 'tests/unittests/test_datasource/test_azure.py' |
143 | --- tests/unittests/test_datasource/test_azure.py 2015-10-14 23:32:35 +0000 |
144 | +++ tests/unittests/test_datasource/test_azure.py 2015-10-30 16:36:54 +0000 |
145 | @@ -115,10 +115,6 @@ |
146 | data['pubkey_files'] = flist |
147 | return ["pubkey_from: %s" % f for f in flist] |
148 | |
149 | - def _iid_from_shared_config(path): |
150 | - data['iid_from_shared_cfg'] = path |
151 | - return 'i-my-azure-id' |
152 | - |
153 | if data.get('ovfcontent') is not None: |
154 | populate_dir(os.path.join(self.paths.seed_dir, "azure"), |
155 | {'ovf-env.xml': data['ovfcontent']}) |
156 | @@ -127,20 +123,22 @@ |
157 | mod.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d |
158 | |
159 | self.get_metadata_from_fabric = mock.MagicMock(return_value={ |
160 | - 'instance-id': 'i-my-azure-id', |
161 | 'public-keys': [], |
162 | }) |
163 | |
164 | + self.instance_id = 'test-instance-id' |
165 | + |
166 | self.apply_patches([ |
167 | (mod, 'list_possible_azure_ds_devs', dsdevs), |
168 | (mod, 'invoke_agent', _invoke_agent), |
169 | (mod, 'wait_for_files', _wait_for_files), |
170 | (mod, 'pubkeys_from_crt_files', _pubkeys_from_crt_files), |
171 | - (mod, 'iid_from_shared_config', _iid_from_shared_config), |
172 | (mod, 'perform_hostname_bounce', mock.MagicMock()), |
173 | (mod, 'get_hostname', mock.MagicMock()), |
174 | (mod, 'set_hostname', mock.MagicMock()), |
175 | (mod, 'get_metadata_from_fabric', self.get_metadata_from_fabric), |
176 | + (mod.util, 'read_dmi_data', mock.MagicMock( |
177 | + return_value=self.instance_id)), |
178 | ]) |
179 | |
180 | dsrc = mod.DataSourceAzureNet( |
181 | @@ -193,7 +191,6 @@ |
182 | self.assertEqual(dsrc.metadata['local-hostname'], odata['HostName']) |
183 | self.assertTrue(os.path.isfile( |
184 | os.path.join(self.waagent_d, 'ovf-env.xml'))) |
185 | - self.assertEqual(dsrc.metadata['instance-id'], 'i-my-azure-id') |
186 | |
187 | def test_waagent_d_has_0700_perms(self): |
188 | # we expect /var/lib/waagent to be created 0700 |
189 | @@ -345,7 +342,6 @@ |
190 | for mypk in mypklist: |
191 | self.assertIn(mypk['value'], dsrc.metadata['public-keys']) |
192 | |
193 | - |
194 | def test_default_ephemeral(self): |
195 | # make sure the ephemeral device works |
196 | odata = {} |
197 | @@ -434,54 +430,6 @@ |
198 | dsrc = self._get_ds({'ovfcontent': xml}) |
199 | dsrc.get_data() |
200 | |
201 | - def test_existing_ovf_same(self): |
202 | - # waagent/SharedConfig left alone if found ovf-env.xml same as cached |
203 | - odata = {'UserData': b64e("SOMEUSERDATA")} |
204 | - data = {'ovfcontent': construct_valid_ovf_env(data=odata)} |
205 | - |
206 | - populate_dir(self.waagent_d, |
207 | - {'ovf-env.xml': data['ovfcontent'], |
208 | - 'otherfile': 'otherfile-content', |
209 | - 'SharedConfig.xml': 'mysharedconfig'}) |
210 | - |
211 | - dsrc = self._get_ds(data) |
212 | - ret = dsrc.get_data() |
213 | - self.assertTrue(ret) |
214 | - self.assertTrue(os.path.exists( |
215 | - os.path.join(self.waagent_d, 'ovf-env.xml'))) |
216 | - self.assertTrue(os.path.exists( |
217 | - os.path.join(self.waagent_d, 'otherfile'))) |
218 | - self.assertTrue(os.path.exists( |
219 | - os.path.join(self.waagent_d, 'SharedConfig.xml'))) |
220 | - |
221 | - def test_existing_ovf_diff(self): |
222 | - # waagent/SharedConfig must be removed if ovfenv is found elsewhere |
223 | - |
224 | - # 'get_data' should remove SharedConfig.xml in /var/lib/waagent |
225 | - # if ovf-env.xml differs. |
226 | - cached_ovfenv = construct_valid_ovf_env( |
227 | - {'userdata': b64e("FOO_USERDATA")}) |
228 | - new_ovfenv = construct_valid_ovf_env( |
229 | - {'userdata': b64e("NEW_USERDATA")}) |
230 | - |
231 | - populate_dir(self.waagent_d, |
232 | - {'ovf-env.xml': cached_ovfenv, |
233 | - 'SharedConfig.xml': "mysharedconfigxml", |
234 | - 'otherfile': 'otherfilecontent'}) |
235 | - |
236 | - dsrc = self._get_ds({'ovfcontent': new_ovfenv}) |
237 | - ret = dsrc.get_data() |
238 | - self.assertTrue(ret) |
239 | - self.assertEqual(dsrc.userdata_raw, b"NEW_USERDATA") |
240 | - self.assertTrue(os.path.exists( |
241 | - os.path.join(self.waagent_d, 'otherfile'))) |
242 | - self.assertFalse(os.path.exists( |
243 | - os.path.join(self.waagent_d, 'SharedConfig.xml'))) |
244 | - self.assertTrue(os.path.exists( |
245 | - os.path.join(self.waagent_d, 'ovf-env.xml'))) |
246 | - new_xml = load_file(os.path.join(self.waagent_d, 'ovf-env.xml')) |
247 | - self.xml_equals(new_ovfenv, new_xml) |
248 | - |
249 | def test_exception_fetching_fabric_data_doesnt_propagate(self): |
250 | ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) |
251 | ds.ds_cfg['agent_command'] = '__builtin__' |
252 | @@ -496,6 +444,17 @@ |
253 | self.assertTrue(ret) |
254 | self.assertEqual('value', ds.metadata['test']) |
255 | |
256 | + def test_instance_id_from_dmidecode_used(self): |
257 | + ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) |
258 | + ds.get_data() |
259 | + self.assertEqual(self.instance_id, ds.metadata['instance-id']) |
260 | + |
261 | + def test_instance_id_from_dmidecode_used_for_builtin(self): |
262 | + ds = self._get_ds({'ovfcontent': construct_valid_ovf_env()}) |
263 | + ds.ds_cfg['agent_command'] = '__builtin__' |
264 | + ds.get_data() |
265 | + self.assertEqual(self.instance_id, ds.metadata['instance-id']) |
266 | + |
267 | |
268 | class TestAzureBounce(TestCase): |
269 | |
270 | @@ -505,9 +464,6 @@ |
271 | self.patches.enter_context( |
272 | mock.patch.object(DataSourceAzure, 'wait_for_files')) |
273 | self.patches.enter_context( |
274 | - mock.patch.object(DataSourceAzure, 'iid_from_shared_config', |
275 | - mock.MagicMock(return_value='i-my-azure-id'))) |
276 | - self.patches.enter_context( |
277 | mock.patch.object(DataSourceAzure, 'list_possible_azure_ds_devs', |
278 | mock.MagicMock(return_value=[]))) |
279 | self.patches.enter_context( |
280 | @@ -521,6 +477,9 @@ |
281 | self.patches.enter_context( |
282 | mock.patch.object(DataSourceAzure, 'get_metadata_from_fabric', |
283 | mock.MagicMock(return_value={}))) |
284 | + self.patches.enter_context( |
285 | + mock.patch.object(DataSourceAzure.util, 'read_dmi_data', |
286 | + mock.MagicMock(return_value='test-instance-id'))) |
287 | |
288 | def setUp(self): |
289 | super(TestAzureBounce, self).setUp() |
290 | |
291 | === modified file 'tests/unittests/test_datasource/test_azure_helper.py' |
292 | --- tests/unittests/test_datasource/test_azure_helper.py 2015-05-15 20:28:24 +0000 |
293 | +++ tests/unittests/test_datasource/test_azure_helper.py 2015-10-30 16:36:54 +0000 |
294 | @@ -40,7 +40,7 @@ |
295 | <HostingEnvironmentConfig> |
296 | http://100.86.192.70:80/...hostingEnvironmentConfig... |
297 | </HostingEnvironmentConfig> |
298 | - <SharedConfig>{shared_config_url}</SharedConfig> |
299 | + <SharedConfig>http://100.86.192.70:80/..SharedConfig..</SharedConfig> |
300 | <ExtensionsConfig> |
301 | http://100.86.192.70:80/...extensionsConfig... |
302 | </ExtensionsConfig> |
303 | @@ -55,21 +55,6 @@ |
304 | """ |
305 | |
306 | |
307 | -class TestReadAzureSharedConfig(unittest.TestCase): |
308 | - |
309 | - def test_valid_content(self): |
310 | - xml = """<?xml version="1.0" encoding="utf-8"?> |
311 | - <SharedConfig> |
312 | - <Deployment name="MY_INSTANCE_ID"> |
313 | - <Service name="myservice"/> |
314 | - <ServiceInstance name="INSTANCE_ID.0" guid="{abcd-uuid}" /> |
315 | - </Deployment> |
316 | - <Incarnation number="1"/> |
317 | - </SharedConfig>""" |
318 | - ret = azure_helper.iid_from_shared_config_content(xml) |
319 | - self.assertEqual("MY_INSTANCE_ID", ret) |
320 | - |
321 | - |
322 | class TestFindEndpoint(TestCase): |
323 | |
324 | def setUp(self): |
325 | @@ -140,7 +125,6 @@ |
326 | 'incarnation': 1, |
327 | 'container_id': 'MyContainerId', |
328 | 'instance_id': 'MyInstanceId', |
329 | - 'shared_config_url': 'MySharedConfigUrl', |
330 | 'certificates_url': 'MyCertificatesUrl', |
331 | } |
332 | |
333 | @@ -174,20 +158,9 @@ |
334 | goal_state = self._get_goal_state(instance_id=instance_id) |
335 | self.assertEqual(instance_id, goal_state.instance_id) |
336 | |
337 | - def test_shared_config_xml_parsed_and_fetched_correctly(self): |
338 | - http_client = mock.MagicMock() |
339 | - shared_config_url = 'TestSharedConfigUrl' |
340 | - goal_state = self._get_goal_state( |
341 | - http_client=http_client, shared_config_url=shared_config_url) |
342 | - shared_config_xml = goal_state.shared_config_xml |
343 | - self.assertEqual(1, http_client.get.call_count) |
344 | - self.assertEqual(shared_config_url, http_client.get.call_args[0][0]) |
345 | - self.assertEqual(http_client.get.return_value.contents, |
346 | - shared_config_xml) |
347 | - |
348 | def test_certificates_xml_parsed_and_fetched_correctly(self): |
349 | http_client = mock.MagicMock() |
350 | - certificates_url = 'TestSharedConfigUrl' |
351 | + certificates_url = 'TestCertificatesUrl' |
352 | goal_state = self._get_goal_state( |
353 | http_client=http_client, certificates_url=certificates_url) |
354 | certificates_xml = goal_state.certificates_xml |
355 | @@ -324,8 +297,6 @@ |
356 | azure_helper.WALinuxAgentShim, 'find_endpoint')) |
357 | self.GoalState = patches.enter_context( |
358 | mock.patch.object(azure_helper, 'GoalState')) |
359 | - self.iid_from_shared_config_content = patches.enter_context( |
360 | - mock.patch.object(azure_helper, 'iid_from_shared_config_content')) |
361 | self.OpenSSLManager = patches.enter_context( |
362 | mock.patch.object(azure_helper, 'OpenSSLManager')) |
363 | patches.enter_context( |
364 | @@ -367,15 +338,6 @@ |
365 | data = shim.register_with_azure_and_fetch_data() |
366 | self.assertEqual([], data['public-keys']) |
367 | |
368 | - def test_instance_id_returned_in_data(self): |
369 | - shim = azure_helper.WALinuxAgentShim() |
370 | - data = shim.register_with_azure_and_fetch_data() |
371 | - self.assertEqual( |
372 | - [mock.call(self.GoalState.return_value.shared_config_xml)], |
373 | - self.iid_from_shared_config_content.call_args_list) |
374 | - self.assertEqual(self.iid_from_shared_config_content.return_value, |
375 | - data['instance-id']) |
376 | - |
377 | def test_correct_url_used_for_report_ready(self): |
378 | self.find_endpoint.return_value = 'test_endpoint' |
379 | shim = azure_helper.WALinuxAgentShim() |
Adding Scott to the reviewers for merge approval and merging.
This change looks sane to me and reduces the complexity of the Datasource. Microsoft has updated the public facing documentation to reflect the use of the system uuid as extracted from dmi data as the authoratative identification of an instance.
We are seeing this as a hot issue. So once this lands in Xenial, I'll run an SRU to get this backported.
+1