Merge lp:~oddbloke/cloud-init/lp1375252 into lp:~cloud-init-dev/cloud-init/trunk
- lp1375252
- Merge into trunk
Proposed by
Dan Watkins
Status: | Merged |
---|---|
Merged at revision: | 1098 |
Proposed branch: | lp:~oddbloke/cloud-init/lp1375252 |
Merge into: | lp:~cloud-init-dev/cloud-init/trunk |
Diff against target: |
425 lines (+247/-106) 2 files modified
cloudinit/sources/DataSourceAzure.py (+75/-61) tests/unittests/test_datasource/test_azure.py (+172/-45) |
To merge this branch: | bzr merge lp:~oddbloke/cloud-init/lp1375252 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+256291@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Dan Watkins (oddbloke) wrote : | # |
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-02-18 13:30:51 +0000 | |||
3 | +++ cloudinit/sources/DataSourceAzure.py 2015-04-15 11:26:23 +0000 | |||
4 | @@ -17,6 +17,7 @@ | |||
5 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
6 | 18 | 18 | ||
7 | 19 | import base64 | 19 | import base64 |
8 | 20 | import contextlib | ||
9 | 20 | import crypt | 21 | import crypt |
10 | 21 | import fnmatch | 22 | import fnmatch |
11 | 22 | import os | 23 | import os |
12 | @@ -66,6 +67,36 @@ | |||
13 | 66 | DEF_EPHEMERAL_LABEL = 'Temporary Storage' | 67 | DEF_EPHEMERAL_LABEL = 'Temporary Storage' |
14 | 67 | 68 | ||
15 | 68 | 69 | ||
16 | 70 | def get_hostname(hostname_command='hostname'): | ||
17 | 71 | return util.subp(hostname_command, capture=True)[0].strip() | ||
18 | 72 | |||
19 | 73 | |||
20 | 74 | def set_hostname(hostname, hostname_command='hostname'): | ||
21 | 75 | util.subp([hostname_command, hostname]) | ||
22 | 76 | |||
23 | 77 | |||
24 | 78 | @contextlib.contextmanager | ||
25 | 79 | def temporary_hostname(temp_hostname, cfg, hostname_command='hostname'): | ||
26 | 80 | """ | ||
27 | 81 | Set a temporary hostname, restoring the previous hostname on exit. | ||
28 | 82 | |||
29 | 83 | Will have the value of the previous hostname when used as a context | ||
30 | 84 | manager, or None if the hostname was not changed. | ||
31 | 85 | """ | ||
32 | 86 | policy = cfg['hostname_bounce']['policy'] | ||
33 | 87 | previous_hostname = get_hostname(hostname_command) | ||
34 | 88 | if (not util.is_true(cfg.get('set_hostname')) | ||
35 | 89 | or util.is_false(policy) | ||
36 | 90 | or (previous_hostname == temp_hostname and policy != 'force')): | ||
37 | 91 | yield None | ||
38 | 92 | return | ||
39 | 93 | set_hostname(temp_hostname, hostname_command) | ||
40 | 94 | try: | ||
41 | 95 | yield previous_hostname | ||
42 | 96 | finally: | ||
43 | 97 | set_hostname(previous_hostname, hostname_command) | ||
44 | 98 | |||
45 | 99 | |||
46 | 69 | class DataSourceAzureNet(sources.DataSource): | 100 | class DataSourceAzureNet(sources.DataSource): |
47 | 70 | def __init__(self, sys_cfg, distro, paths): | 101 | def __init__(self, sys_cfg, distro, paths): |
48 | 71 | sources.DataSource.__init__(self, sys_cfg, distro, paths) | 102 | sources.DataSource.__init__(self, sys_cfg, distro, paths) |
49 | @@ -154,33 +185,40 @@ | |||
50 | 154 | # the directory to be protected. | 185 | # the directory to be protected. |
51 | 155 | write_files(ddir, files, dirmode=0o700) | 186 | write_files(ddir, files, dirmode=0o700) |
52 | 156 | 187 | ||
80 | 157 | # handle the hostname 'publishing' | 188 | temp_hostname = self.metadata.get('local-hostname') |
81 | 158 | try: | 189 | hostname_command = mycfg['hostname_bounce']['hostname_command'] |
82 | 159 | handle_set_hostname(mycfg.get('set_hostname'), | 190 | with temporary_hostname(temp_hostname, mycfg, |
83 | 160 | self.metadata.get('local-hostname'), | 191 | hostname_command=hostname_command) \ |
84 | 161 | mycfg['hostname_bounce']) | 192 | as previous_hostname: |
85 | 162 | except Exception as e: | 193 | if (previous_hostname is not None |
86 | 163 | LOG.warn("Failed publishing hostname: %s", e) | 194 | and util.is_true(mycfg.get('set_hostname'))): |
87 | 164 | util.logexc(LOG, "handling set_hostname failed") | 195 | cfg = mycfg['hostname_bounce'] |
88 | 165 | 196 | try: | |
89 | 166 | try: | 197 | perform_hostname_bounce(hostname=temp_hostname, |
90 | 167 | invoke_agent(mycfg['agent_command']) | 198 | cfg=cfg, |
91 | 168 | except util.ProcessExecutionError: | 199 | prev_hostname=previous_hostname) |
92 | 169 | # claim the datasource even if the command failed | 200 | except Exception as e: |
93 | 170 | util.logexc(LOG, "agent command '%s' failed.", | 201 | LOG.warn("Failed publishing hostname: %s", e) |
94 | 171 | mycfg['agent_command']) | 202 | util.logexc(LOG, "handling set_hostname failed") |
95 | 172 | 203 | ||
96 | 173 | shcfgxml = os.path.join(ddir, "SharedConfig.xml") | 204 | try: |
97 | 174 | wait_for = [shcfgxml] | 205 | invoke_agent(mycfg['agent_command']) |
98 | 175 | 206 | except util.ProcessExecutionError: | |
99 | 176 | fp_files = [] | 207 | # claim the datasource even if the command failed |
100 | 177 | for pk in self.cfg.get('_pubkeys', []): | 208 | util.logexc(LOG, "agent command '%s' failed.", |
101 | 178 | bname = str(pk['fingerprint'] + ".crt") | 209 | mycfg['agent_command']) |
102 | 179 | fp_files += [os.path.join(ddir, bname)] | 210 | |
103 | 180 | 211 | shcfgxml = os.path.join(ddir, "SharedConfig.xml") | |
104 | 181 | missing = util.log_time(logfunc=LOG.debug, msg="waiting for files", | 212 | wait_for = [shcfgxml] |
105 | 182 | func=wait_for_files, | 213 | |
106 | 183 | args=(wait_for + fp_files,)) | 214 | fp_files = [] |
107 | 215 | for pk in self.cfg.get('_pubkeys', []): | ||
108 | 216 | bname = str(pk['fingerprint'] + ".crt") | ||
109 | 217 | fp_files += [os.path.join(ddir, bname)] | ||
110 | 218 | |||
111 | 219 | missing = util.log_time(logfunc=LOG.debug, msg="waiting for files", | ||
112 | 220 | func=wait_for_files, | ||
113 | 221 | args=(wait_for + fp_files,)) | ||
114 | 184 | if len(missing): | 222 | if len(missing): |
115 | 185 | LOG.warn("Did not find files, but going on: %s", missing) | 223 | LOG.warn("Did not find files, but going on: %s", missing) |
116 | 186 | 224 | ||
117 | @@ -299,39 +337,15 @@ | |||
118 | 299 | return mod_list | 337 | return mod_list |
119 | 300 | 338 | ||
120 | 301 | 339 | ||
137 | 302 | def handle_set_hostname(enabled, hostname, cfg): | 340 | def perform_hostname_bounce(hostname, cfg, prev_hostname): |
122 | 303 | if not util.is_true(enabled): | ||
123 | 304 | return | ||
124 | 305 | |||
125 | 306 | if not hostname: | ||
126 | 307 | LOG.warn("set_hostname was true but no local-hostname") | ||
127 | 308 | return | ||
128 | 309 | |||
129 | 310 | apply_hostname_bounce(hostname=hostname, policy=cfg['policy'], | ||
130 | 311 | interface=cfg['interface'], | ||
131 | 312 | command=cfg['command'], | ||
132 | 313 | hostname_command=cfg['hostname_command']) | ||
133 | 314 | |||
134 | 315 | |||
135 | 316 | def apply_hostname_bounce(hostname, policy, interface, command, | ||
136 | 317 | hostname_command="hostname"): | ||
138 | 318 | # set the hostname to 'hostname' if it is not already set to that. | 341 | # set the hostname to 'hostname' if it is not already set to that. |
139 | 319 | # then, if policy is not off, bounce the interface using command | 342 | # then, if policy is not off, bounce the interface using command |
155 | 320 | prev_hostname = util.subp(hostname_command, capture=True)[0].strip() | 343 | command = cfg['command'] |
156 | 321 | 344 | interface = cfg['interface'] | |
157 | 322 | util.subp([hostname_command, hostname]) | 345 | policy = cfg['policy'] |
158 | 323 | 346 | ||
159 | 324 | msg = ("phostname=%s hostname=%s policy=%s interface=%s" % | 347 | msg = ("hostname=%s policy=%s interface=%s" % |
160 | 325 | (prev_hostname, hostname, policy, interface)) | 348 | (hostname, policy, interface)) |
146 | 326 | |||
147 | 327 | if util.is_false(policy): | ||
148 | 328 | LOG.debug("pubhname: policy false, skipping [%s]", msg) | ||
149 | 329 | return | ||
150 | 330 | |||
151 | 331 | if prev_hostname == hostname and policy != "force": | ||
152 | 332 | LOG.debug("pubhname: no change, policy != force. skipping. [%s]", msg) | ||
153 | 333 | return | ||
154 | 334 | |||
161 | 335 | env = os.environ.copy() | 349 | env = os.environ.copy() |
162 | 336 | env['interface'] = interface | 350 | env['interface'] = interface |
163 | 337 | env['hostname'] = hostname | 351 | env['hostname'] = hostname |
164 | @@ -344,9 +358,9 @@ | |||
165 | 344 | shell = not isinstance(command, (list, tuple)) | 358 | shell = not isinstance(command, (list, tuple)) |
166 | 345 | # capture=False, see comments in bug 1202758 and bug 1206164. | 359 | # capture=False, see comments in bug 1202758 and bug 1206164. |
167 | 346 | util.log_time(logfunc=LOG.debug, msg="publishing hostname", | 360 | util.log_time(logfunc=LOG.debug, msg="publishing hostname", |
171 | 347 | get_uptime=True, func=util.subp, | 361 | get_uptime=True, func=util.subp, |
172 | 348 | kwargs={'args': command, 'shell': shell, 'capture': False, | 362 | kwargs={'args': command, 'shell': shell, 'capture': False, |
173 | 349 | 'env': env}) | 363 | 'env': env}) |
174 | 350 | 364 | ||
175 | 351 | 365 | ||
176 | 352 | def crtfile_to_pubkey(fname): | 366 | def crtfile_to_pubkey(fname): |
177 | 353 | 367 | ||
178 | === modified file 'tests/unittests/test_datasource/test_azure.py' | |||
179 | --- tests/unittests/test_datasource/test_azure.py 2015-02-24 16:58:22 +0000 | |||
180 | +++ tests/unittests/test_datasource/test_azure.py 2015-04-15 11:26:23 +0000 | |||
181 | @@ -116,9 +116,6 @@ | |||
182 | 116 | data['iid_from_shared_cfg'] = path | 116 | data['iid_from_shared_cfg'] = path |
183 | 117 | return 'i-my-azure-id' | 117 | return 'i-my-azure-id' |
184 | 118 | 118 | ||
185 | 119 | def _apply_hostname_bounce(**kwargs): | ||
186 | 120 | data['apply_hostname_bounce'] = kwargs | ||
187 | 121 | |||
188 | 122 | if data.get('ovfcontent') is not None: | 119 | if data.get('ovfcontent') is not None: |
189 | 123 | populate_dir(os.path.join(self.paths.seed_dir, "azure"), | 120 | populate_dir(os.path.join(self.paths.seed_dir, "azure"), |
190 | 124 | {'ovf-env.xml': data['ovfcontent']}) | 121 | {'ovf-env.xml': data['ovfcontent']}) |
191 | @@ -132,7 +129,9 @@ | |||
192 | 132 | (mod, 'wait_for_files', _wait_for_files), | 129 | (mod, 'wait_for_files', _wait_for_files), |
193 | 133 | (mod, 'pubkeys_from_crt_files', _pubkeys_from_crt_files), | 130 | (mod, 'pubkeys_from_crt_files', _pubkeys_from_crt_files), |
194 | 134 | (mod, 'iid_from_shared_config', _iid_from_shared_config), | 131 | (mod, 'iid_from_shared_config', _iid_from_shared_config), |
196 | 135 | (mod, 'apply_hostname_bounce', _apply_hostname_bounce), | 132 | (mod, 'perform_hostname_bounce', mock.MagicMock()), |
197 | 133 | (mod, 'get_hostname', mock.MagicMock()), | ||
198 | 134 | (mod, 'set_hostname', mock.MagicMock()), | ||
199 | 136 | ]) | 135 | ]) |
200 | 137 | 136 | ||
201 | 138 | dsrc = mod.DataSourceAzureNet( | 137 | dsrc = mod.DataSourceAzureNet( |
202 | @@ -272,47 +271,6 @@ | |||
203 | 272 | for mypk in mypklist: | 271 | for mypk in mypklist: |
204 | 273 | self.assertIn(mypk, dsrc.cfg['_pubkeys']) | 272 | self.assertIn(mypk, dsrc.cfg['_pubkeys']) |
205 | 274 | 273 | ||
206 | 275 | def test_disabled_bounce(self): | ||
207 | 276 | pass | ||
208 | 277 | |||
209 | 278 | def test_apply_bounce_call_1(self): | ||
210 | 279 | # hostname needs to get through to apply_hostname_bounce | ||
211 | 280 | odata = {'HostName': 'my-random-hostname'} | ||
212 | 281 | data = {'ovfcontent': construct_valid_ovf_env(data=odata)} | ||
213 | 282 | |||
214 | 283 | self._get_ds(data).get_data() | ||
215 | 284 | self.assertIn('hostname', data['apply_hostname_bounce']) | ||
216 | 285 | self.assertEqual(data['apply_hostname_bounce']['hostname'], | ||
217 | 286 | odata['HostName']) | ||
218 | 287 | |||
219 | 288 | def test_apply_bounce_call_configurable(self): | ||
220 | 289 | # hostname_bounce should be configurable in datasource cfg | ||
221 | 290 | cfg = {'hostname_bounce': {'interface': 'eth1', 'policy': 'off', | ||
222 | 291 | 'command': 'my-bounce-command', | ||
223 | 292 | 'hostname_command': 'my-hostname-command'}} | ||
224 | 293 | odata = {'HostName': "xhost", | ||
225 | 294 | 'dscfg': {'text': b64e(yaml.dump(cfg)), | ||
226 | 295 | 'encoding': 'base64'}} | ||
227 | 296 | data = {'ovfcontent': construct_valid_ovf_env(data=odata)} | ||
228 | 297 | self._get_ds(data).get_data() | ||
229 | 298 | |||
230 | 299 | for k in cfg['hostname_bounce']: | ||
231 | 300 | self.assertIn(k, data['apply_hostname_bounce']) | ||
232 | 301 | |||
233 | 302 | for k, v in cfg['hostname_bounce'].items(): | ||
234 | 303 | self.assertEqual(data['apply_hostname_bounce'][k], v) | ||
235 | 304 | |||
236 | 305 | def test_set_hostname_disabled(self): | ||
237 | 306 | # config specifying set_hostname off should not bounce | ||
238 | 307 | cfg = {'set_hostname': False} | ||
239 | 308 | odata = {'HostName': "xhost", | ||
240 | 309 | 'dscfg': {'text': b64e(yaml.dump(cfg)), | ||
241 | 310 | 'encoding': 'base64'}} | ||
242 | 311 | data = {'ovfcontent': construct_valid_ovf_env(data=odata)} | ||
243 | 312 | self._get_ds(data).get_data() | ||
244 | 313 | |||
245 | 314 | self.assertEqual(data.get('apply_hostname_bounce', "N/A"), "N/A") | ||
246 | 315 | |||
247 | 316 | def test_default_ephemeral(self): | 274 | def test_default_ephemeral(self): |
248 | 317 | # make sure the ephemeral device works | 275 | # make sure the ephemeral device works |
249 | 318 | odata = {} | 276 | odata = {} |
250 | @@ -425,6 +383,175 @@ | |||
251 | 425 | load_file(os.path.join(self.waagent_d, 'ovf-env.xml'))) | 383 | load_file(os.path.join(self.waagent_d, 'ovf-env.xml'))) |
252 | 426 | 384 | ||
253 | 427 | 385 | ||
254 | 386 | class TestAzureBounce(TestCase): | ||
255 | 387 | |||
256 | 388 | def mock_out_azure_moving_parts(self): | ||
257 | 389 | self.patches.enter_context( | ||
258 | 390 | mock.patch.object(DataSourceAzure, 'invoke_agent')) | ||
259 | 391 | self.patches.enter_context( | ||
260 | 392 | mock.patch.object(DataSourceAzure, 'wait_for_files')) | ||
261 | 393 | self.patches.enter_context( | ||
262 | 394 | mock.patch.object(DataSourceAzure, 'iid_from_shared_config', | ||
263 | 395 | mock.MagicMock(return_value='i-my-azure-id'))) | ||
264 | 396 | self.patches.enter_context( | ||
265 | 397 | mock.patch.object(DataSourceAzure, 'list_possible_azure_ds_devs', | ||
266 | 398 | mock.MagicMock(return_value=[]))) | ||
267 | 399 | self.patches.enter_context( | ||
268 | 400 | mock.patch.object(DataSourceAzure, 'find_ephemeral_disk', | ||
269 | 401 | mock.MagicMock(return_value=None))) | ||
270 | 402 | self.patches.enter_context( | ||
271 | 403 | mock.patch.object(DataSourceAzure, 'find_ephemeral_part', | ||
272 | 404 | mock.MagicMock(return_value=None))) | ||
273 | 405 | |||
274 | 406 | def setUp(self): | ||
275 | 407 | super(TestAzureBounce, self).setUp() | ||
276 | 408 | self.tmp = tempfile.mkdtemp() | ||
277 | 409 | self.waagent_d = os.path.join(self.tmp, 'var', 'lib', 'waagent') | ||
278 | 410 | self.paths = helpers.Paths({'cloud_dir': self.tmp}) | ||
279 | 411 | self.addCleanup(shutil.rmtree, self.tmp) | ||
280 | 412 | DataSourceAzure.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d | ||
281 | 413 | self.patches = ExitStack() | ||
282 | 414 | self.mock_out_azure_moving_parts() | ||
283 | 415 | self.get_hostname = self.patches.enter_context( | ||
284 | 416 | mock.patch.object(DataSourceAzure, 'get_hostname')) | ||
285 | 417 | self.set_hostname = self.patches.enter_context( | ||
286 | 418 | mock.patch.object(DataSourceAzure, 'set_hostname')) | ||
287 | 419 | self.subp = self.patches.enter_context( | ||
288 | 420 | mock.patch('cloudinit.sources.DataSourceAzure.util.subp')) | ||
289 | 421 | |||
290 | 422 | def tearDown(self): | ||
291 | 423 | self.patches.close() | ||
292 | 424 | |||
293 | 425 | def _get_ds(self, ovfcontent=None): | ||
294 | 426 | if ovfcontent is not None: | ||
295 | 427 | populate_dir(os.path.join(self.paths.seed_dir, "azure"), | ||
296 | 428 | {'ovf-env.xml': ovfcontent}) | ||
297 | 429 | return DataSourceAzure.DataSourceAzureNet( | ||
298 | 430 | {}, distro=None, paths=self.paths) | ||
299 | 431 | |||
300 | 432 | def get_ovf_env_with_dscfg(self, hostname, cfg): | ||
301 | 433 | odata = { | ||
302 | 434 | 'HostName': hostname, | ||
303 | 435 | 'dscfg': { | ||
304 | 436 | 'text': b64e(yaml.dump(cfg)), | ||
305 | 437 | 'encoding': 'base64' | ||
306 | 438 | } | ||
307 | 439 | } | ||
308 | 440 | return construct_valid_ovf_env(data=odata) | ||
309 | 441 | |||
310 | 442 | def test_disabled_bounce_does_not_change_hostname(self): | ||
311 | 443 | cfg = {'hostname_bounce': {'policy': 'off'}} | ||
312 | 444 | self._get_ds(self.get_ovf_env_with_dscfg('test-host', cfg)).get_data() | ||
313 | 445 | self.assertEqual(0, self.set_hostname.call_count) | ||
314 | 446 | |||
315 | 447 | @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce') | ||
316 | 448 | def test_disabled_bounce_does_not_perform_bounce( | ||
317 | 449 | self, perform_hostname_bounce): | ||
318 | 450 | cfg = {'hostname_bounce': {'policy': 'off'}} | ||
319 | 451 | self._get_ds(self.get_ovf_env_with_dscfg('test-host', cfg)).get_data() | ||
320 | 452 | self.assertEqual(0, perform_hostname_bounce.call_count) | ||
321 | 453 | |||
322 | 454 | def test_same_hostname_does_not_change_hostname(self): | ||
323 | 455 | host_name = 'unchanged-host-name' | ||
324 | 456 | self.get_hostname.return_value = host_name | ||
325 | 457 | cfg = {'hostname_bounce': {'policy': 'yes'}} | ||
326 | 458 | self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg)).get_data() | ||
327 | 459 | self.assertEqual(0, self.set_hostname.call_count) | ||
328 | 460 | |||
329 | 461 | @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce') | ||
330 | 462 | def test_unchanged_hostname_does_not_perform_bounce( | ||
331 | 463 | self, perform_hostname_bounce): | ||
332 | 464 | host_name = 'unchanged-host-name' | ||
333 | 465 | self.get_hostname.return_value = host_name | ||
334 | 466 | cfg = {'hostname_bounce': {'policy': 'yes'}} | ||
335 | 467 | self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg)).get_data() | ||
336 | 468 | self.assertEqual(0, perform_hostname_bounce.call_count) | ||
337 | 469 | |||
338 | 470 | @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce') | ||
339 | 471 | def test_force_performs_bounce_regardless(self, perform_hostname_bounce): | ||
340 | 472 | host_name = 'unchanged-host-name' | ||
341 | 473 | self.get_hostname.return_value = host_name | ||
342 | 474 | cfg = {'hostname_bounce': {'policy': 'force'}} | ||
343 | 475 | self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg)).get_data() | ||
344 | 476 | self.assertEqual(1, perform_hostname_bounce.call_count) | ||
345 | 477 | |||
346 | 478 | def test_different_hostnames_sets_hostname(self): | ||
347 | 479 | expected_hostname = 'azure-expected-host-name' | ||
348 | 480 | self.get_hostname.return_value = 'default-host-name' | ||
349 | 481 | self._get_ds( | ||
350 | 482 | self.get_ovf_env_with_dscfg(expected_hostname, {})).get_data() | ||
351 | 483 | self.assertEqual(expected_hostname, | ||
352 | 484 | self.set_hostname.call_args_list[0][0][0]) | ||
353 | 485 | |||
354 | 486 | @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce') | ||
355 | 487 | def test_different_hostnames_performs_bounce( | ||
356 | 488 | self, perform_hostname_bounce): | ||
357 | 489 | expected_hostname = 'azure-expected-host-name' | ||
358 | 490 | self.get_hostname.return_value = 'default-host-name' | ||
359 | 491 | self._get_ds( | ||
360 | 492 | self.get_ovf_env_with_dscfg(expected_hostname, {})).get_data() | ||
361 | 493 | self.assertEqual(1, perform_hostname_bounce.call_count) | ||
362 | 494 | |||
363 | 495 | def test_different_hostnames_sets_hostname_back(self): | ||
364 | 496 | initial_host_name = 'default-host-name' | ||
365 | 497 | self.get_hostname.return_value = initial_host_name | ||
366 | 498 | self._get_ds( | ||
367 | 499 | self.get_ovf_env_with_dscfg('some-host-name', {})).get_data() | ||
368 | 500 | self.assertEqual(initial_host_name, | ||
369 | 501 | self.set_hostname.call_args_list[-1][0][0]) | ||
370 | 502 | |||
371 | 503 | @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce') | ||
372 | 504 | def test_failure_in_bounce_still_resets_host_name( | ||
373 | 505 | self, perform_hostname_bounce): | ||
374 | 506 | perform_hostname_bounce.side_effect = Exception | ||
375 | 507 | initial_host_name = 'default-host-name' | ||
376 | 508 | self.get_hostname.return_value = initial_host_name | ||
377 | 509 | self._get_ds( | ||
378 | 510 | self.get_ovf_env_with_dscfg('some-host-name', {})).get_data() | ||
379 | 511 | self.assertEqual(initial_host_name, | ||
380 | 512 | self.set_hostname.call_args_list[-1][0][0]) | ||
381 | 513 | |||
382 | 514 | def test_environment_correct_for_bounce_command(self): | ||
383 | 515 | interface = 'int0' | ||
384 | 516 | hostname = 'my-new-host' | ||
385 | 517 | old_hostname = 'my-old-host' | ||
386 | 518 | self.get_hostname.return_value = old_hostname | ||
387 | 519 | cfg = {'hostname_bounce': {'interface': interface, 'policy': 'force'}} | ||
388 | 520 | data = self.get_ovf_env_with_dscfg(hostname, cfg) | ||
389 | 521 | self._get_ds(data).get_data() | ||
390 | 522 | self.assertEqual(1, self.subp.call_count) | ||
391 | 523 | bounce_env = self.subp.call_args[1]['env'] | ||
392 | 524 | self.assertEqual(interface, bounce_env['interface']) | ||
393 | 525 | self.assertEqual(hostname, bounce_env['hostname']) | ||
394 | 526 | self.assertEqual(old_hostname, bounce_env['old_hostname']) | ||
395 | 527 | |||
396 | 528 | def test_default_bounce_command_used_by_default(self): | ||
397 | 529 | cmd = 'default-bounce-command' | ||
398 | 530 | DataSourceAzure.BUILTIN_DS_CONFIG['hostname_bounce']['command'] = cmd | ||
399 | 531 | cfg = {'hostname_bounce': {'policy': 'force'}} | ||
400 | 532 | data = self.get_ovf_env_with_dscfg('some-hostname', cfg) | ||
401 | 533 | self._get_ds(data).get_data() | ||
402 | 534 | self.assertEqual(1, self.subp.call_count) | ||
403 | 535 | bounce_args = self.subp.call_args[1]['args'] | ||
404 | 536 | self.assertEqual(cmd, bounce_args) | ||
405 | 537 | |||
406 | 538 | @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce') | ||
407 | 539 | def test_set_hostname_option_can_disable_bounce( | ||
408 | 540 | self, perform_hostname_bounce): | ||
409 | 541 | cfg = {'set_hostname': False, 'hostname_bounce': {'policy': 'force'}} | ||
410 | 542 | data = self.get_ovf_env_with_dscfg('some-hostname', cfg) | ||
411 | 543 | self._get_ds(data).get_data() | ||
412 | 544 | |||
413 | 545 | self.assertEqual(0, perform_hostname_bounce.call_count) | ||
414 | 546 | |||
415 | 547 | def test_set_hostname_option_can_disable_hostname_set(self): | ||
416 | 548 | cfg = {'set_hostname': False, 'hostname_bounce': {'policy': 'force'}} | ||
417 | 549 | data = self.get_ovf_env_with_dscfg('some-hostname', cfg) | ||
418 | 550 | self._get_ds(data).get_data() | ||
419 | 551 | |||
420 | 552 | self.assertEqual(0, self.set_hostname.call_count) | ||
421 | 553 | |||
422 | 554 | |||
423 | 428 | class TestReadAzureOvf(TestCase): | 555 | class TestReadAzureOvf(TestCase): |
424 | 429 | def test_invalid_xml_raises_non_azure_ds(self): | 556 | def test_invalid_xml_raises_non_azure_ds(self): |
425 | 430 | invalid_xml = "<foo>" + construct_valid_ovf_env(data={}) | 557 | invalid_xml = "<foo>" + construct_valid_ovf_env(data={}) |
Review appreciated, but probably shouldn't be merged until vivid is out the door.