Print this page
IMAGE-1097 cloud-init doesn't use sdc:routes metadata
| Split |
Close |
| Expand all |
| Collapse all |
--- old/tests/unittests/test_datasource/test_smartos.py
+++ new/tests/unittests/test_datasource/test_smartos.py
1 1 # Copyright (C) 2013 Canonical Ltd.
2 2 # Copyright (c) 2018, Joyent, Inc.
3 3 #
4 4 # Author: Ben Howard <ben.howard@canonical.com>
5 5 #
6 6 # This file is part of cloud-init. See LICENSE file for license information.
7 7
8 8 '''This is a testcase for the SmartOS datasource.
9 9
10 10 It replicates a serial console and acts like the SmartOS console does in
11 11 order to validate return responses.
12 12
13 13 '''
14 14
15 15 from __future__ import print_function
16 16
17 17 from binascii import crc32
18 18 import json
19 19 import multiprocessing
20 20 import os
21 21 import os.path
22 22 import re
23 23 import shutil
24 24 import signal
25 25 import stat
26 26 import subprocess
27 27 import tempfile
28 28 import unittest
29 29 import uuid
30 30
31 31 from cloudinit import serial
32 32 from cloudinit.sources import DataSourceSmartOS
33 33 from cloudinit.sources.DataSourceSmartOS import (
34 34 convert_smartos_network_data as convert_net)
35 35
36 36 import six
37 37
38 38 from cloudinit import helpers as c_helpers
39 39 from cloudinit.util import b64e
40 40
41 41 from cloudinit.tests.helpers import mock, FilesystemMockingTestCase, TestCase
42 42
43 43 SDC_NICS = json.loads("""
44 44 [
45 45 {
46 46 "nic_tag": "external",
47 47 "primary": true,
48 48 "mtu": 1500,
49 49 "model": "virtio",
50 50 "gateway": "8.12.42.1",
51 51 "netmask": "255.255.255.0",
52 52 "ip": "8.12.42.102",
53 53 "network_uuid": "992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
54 54 "gateways": [
55 55 "8.12.42.1"
56 56 ],
57 57 "vlan_id": 324,
58 58 "mac": "90:b8:d0:f5:e4:f5",
59 59 "interface": "net0",
60 60 "ips": [
61 61 "8.12.42.102/24"
62 62 ]
63 63 },
64 64 {
65 65 "nic_tag": "sdc_overlay/16187209",
66 66 "gateway": "192.168.128.1",
67 67 "model": "virtio",
68 68 "mac": "90:b8:d0:a5:ff:cd",
69 69 "netmask": "255.255.252.0",
70 70 "ip": "192.168.128.93",
71 71 "network_uuid": "4cad71da-09bc-452b-986d-03562a03a0a9",
72 72 "gateways": [
73 73 "192.168.128.1"
74 74 ],
75 75 "vlan_id": 2,
76 76 "mtu": 8500,
77 77 "interface": "net1",
78 78 "ips": [
79 79 "192.168.128.93/22"
80 80 ]
81 81 }
82 82 ]
83 83 """)
84 84
85 85
86 86 SDC_NICS_ALT = json.loads("""
87 87 [
88 88 {
89 89 "interface": "net0",
90 90 "mac": "90:b8:d0:ae:64:51",
91 91 "vlan_id": 324,
92 92 "nic_tag": "external",
93 93 "gateway": "8.12.42.1",
94 94 "gateways": [
95 95 "8.12.42.1"
96 96 ],
97 97 "netmask": "255.255.255.0",
98 98 "ip": "8.12.42.51",
99 99 "ips": [
100 100 "8.12.42.51/24"
101 101 ],
102 102 "network_uuid": "992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
103 103 "model": "virtio",
104 104 "mtu": 1500,
105 105 "primary": true
106 106 },
107 107 {
108 108 "interface": "net1",
109 109 "mac": "90:b8:d0:bd:4f:9c",
110 110 "vlan_id": 600,
111 111 "nic_tag": "internal",
112 112 "netmask": "255.255.255.0",
113 113 "ip": "10.210.1.217",
114 114 "ips": [
115 115 "10.210.1.217/24"
116 116 ],
117 117 "network_uuid": "98657fdf-11f4-4ee2-88a4-ce7fe73e33a6",
118 118 "model": "virtio",
119 119 "mtu": 1500
120 120 }
121 121 ]
122 122 """)
123 123
124 124 SDC_NICS_DHCP = json.loads("""
125 125 [
126 126 {
127 127 "interface": "net0",
128 128 "mac": "90:b8:d0:ae:64:51",
129 129 "vlan_id": 324,
130 130 "nic_tag": "external",
131 131 "gateway": "8.12.42.1",
132 132 "gateways": [
133 133 "8.12.42.1"
134 134 ],
135 135 "netmask": "255.255.255.0",
136 136 "ip": "8.12.42.51",
137 137 "ips": [
138 138 "8.12.42.51/24"
139 139 ],
140 140 "network_uuid": "992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
141 141 "model": "virtio",
142 142 "mtu": 1500,
143 143 "primary": true
144 144 },
145 145 {
146 146 "interface": "net1",
147 147 "mac": "90:b8:d0:bd:4f:9c",
148 148 "vlan_id": 600,
149 149 "nic_tag": "internal",
150 150 "netmask": "255.255.255.0",
151 151 "ip": "10.210.1.217",
152 152 "ips": [
153 153 "dhcp"
154 154 ],
155 155 "network_uuid": "98657fdf-11f4-4ee2-88a4-ce7fe73e33a6",
156 156 "model": "virtio",
157 157 "mtu": 1500
158 158 }
159 159 ]
160 160 """)
161 161
162 162 SDC_NICS_MIP = json.loads("""
163 163 [
164 164 {
165 165 "interface": "net0",
166 166 "mac": "90:b8:d0:ae:64:51",
167 167 "vlan_id": 324,
168 168 "nic_tag": "external",
169 169 "gateway": "8.12.42.1",
170 170 "gateways": [
171 171 "8.12.42.1"
172 172 ],
173 173 "netmask": "255.255.255.0",
174 174 "ip": "8.12.42.51",
175 175 "ips": [
176 176 "8.12.42.51/24",
177 177 "8.12.42.52/24"
178 178 ],
179 179 "network_uuid": "992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
180 180 "model": "virtio",
181 181 "mtu": 1500,
182 182 "primary": true
183 183 },
184 184 {
185 185 "interface": "net1",
186 186 "mac": "90:b8:d0:bd:4f:9c",
187 187 "vlan_id": 600,
188 188 "nic_tag": "internal",
189 189 "netmask": "255.255.255.0",
190 190 "ip": "10.210.1.217",
191 191 "ips": [
192 192 "10.210.1.217/24",
193 193 "10.210.1.151/24"
194 194 ],
195 195 "network_uuid": "98657fdf-11f4-4ee2-88a4-ce7fe73e33a6",
196 196 "model": "virtio",
197 197 "mtu": 1500
198 198 }
199 199 ]
200 200 """)
201 201
202 202 SDC_NICS_MIP_IPV6 = json.loads("""
203 203 [
204 204 {
205 205 "interface": "net0",
206 206 "mac": "90:b8:d0:ae:64:51",
207 207 "vlan_id": 324,
208 208 "nic_tag": "external",
209 209 "gateway": "8.12.42.1",
210 210 "gateways": [
211 211 "8.12.42.1"
212 212 ],
213 213 "netmask": "255.255.255.0",
214 214 "ip": "8.12.42.51",
215 215 "ips": [
216 216 "2001:4800:78ff:1b:be76:4eff:fe06:96b3/64",
217 217 "8.12.42.51/24"
218 218 ],
219 219 "network_uuid": "992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
220 220 "model": "virtio",
221 221 "mtu": 1500,
222 222 "primary": true
223 223 },
224 224 {
225 225 "interface": "net1",
226 226 "mac": "90:b8:d0:bd:4f:9c",
227 227 "vlan_id": 600,
228 228 "nic_tag": "internal",
229 229 "netmask": "255.255.255.0",
230 230 "ip": "10.210.1.217",
231 231 "ips": [
232 232 "10.210.1.217/24"
233 233 ],
234 234 "network_uuid": "98657fdf-11f4-4ee2-88a4-ce7fe73e33a6",
235 235 "model": "virtio",
236 236 "mtu": 1500
237 237 }
238 238 ]
239 239 """)
240 240
241 241 SDC_NICS_IPV4_IPV6 = json.loads("""
242 242 [
243 243 {
244 244 "interface": "net0",
245 245 "mac": "90:b8:d0:ae:64:51",
246 246 "vlan_id": 324,
247 247 "nic_tag": "external",
248 248 "gateway": "8.12.42.1",
249 249 "gateways": ["8.12.42.1", "2001::1", "2001::2"],
250 250 "netmask": "255.255.255.0",
251 251 "ip": "8.12.42.51",
252 252 "ips": ["2001::10/64", "8.12.42.51/24", "2001::11/64",
253 253 "8.12.42.52/32"],
254 254 "network_uuid": "992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
255 255 "model": "virtio",
256 256 "mtu": 1500,
257 257 "primary": true
258 258 },
259 259 {
260 260 "interface": "net1",
261 261 "mac": "90:b8:d0:bd:4f:9c",
262 262 "vlan_id": 600,
263 263 "nic_tag": "internal",
264 264 "netmask": "255.255.255.0",
265 265 "ip": "10.210.1.217",
266 266 "ips": ["10.210.1.217/24"],
267 267 "gateways": ["10.210.1.210"],
268 268 "network_uuid": "98657fdf-11f4-4ee2-88a4-ce7fe73e33a6",
269 269 "model": "virtio",
270 270 "mtu": 1500
271 271 }
272 272 ]
273 273 """)
274 274
275 275 SDC_NICS_SINGLE_GATEWAY = json.loads("""
276 276 [
277 277 {
278 278 "interface":"net0",
279 279 "mac":"90:b8:d0:d8:82:b4",
280 280 "vlan_id":324,
281 281 "nic_tag":"external",
282 282 "gateway":"8.12.42.1",
283 283 "gateways":["8.12.42.1"],
284 284 "netmask":"255.255.255.0",
285 285 "ip":"8.12.42.26",
286 286 "ips":["8.12.42.26/24"],
287 287 "network_uuid":"992fc7ce-6aac-4b74-aed6-7b9d2c6c0bfe",
288 288 "model":"virtio",
289 289 "mtu":1500,
290 290 "primary":true
291 291 },
292 292 {
293 293 "interface":"net1",
294 294 "mac":"90:b8:d0:0a:51:31",
295 295 "vlan_id":600,
296 296 "nic_tag":"internal",
297 297 "netmask":"255.255.255.0",
298 298 "ip":"10.210.1.27",
299 299 "ips":["10.210.1.27/24"],
300 300 "network_uuid":"98657fdf-11f4-4ee2-88a4-ce7fe73e33a6",
301 301 "model":"virtio",
302 302 "mtu":1500
303 303 }
304 304 ]
305 305 """)
306 306
307 307
308 308 MOCK_RETURNS = {
309 309 'hostname': 'test-host',
310 310 'root_authorized_keys': 'ssh-rsa AAAAB3Nz...aC1yc2E= keyname',
311 311 'disable_iptables_flag': None,
312 312 'enable_motd_sys_info': None,
313 313 'test-var1': 'some data',
314 314 'cloud-init:user-data': '\n'.join(['#!/bin/sh', '/bin/true', '']),
315 315 'sdc:datacenter_name': 'somewhere2',
316 316 'sdc:operator-script': '\n'.join(['bin/true', '']),
317 317 'sdc:uuid': str(uuid.uuid4()),
318 318 'sdc:vendor-data': '\n'.join(['VENDOR_DATA', '']),
319 319 'user-data': '\n'.join(['something', '']),
320 320 'user-script': '\n'.join(['/bin/true', '']),
321 321 'sdc:nics': json.dumps(SDC_NICS),
322 322 }
323 323
324 324 DMI_DATA_RETURN = 'smartdc'
325 325
326 326
327 327 class PsuedoJoyentClient(object):
328 328 def __init__(self, data=None):
329 329 if data is None:
330 330 data = MOCK_RETURNS.copy()
331 331 self.data = data
332 332 self._is_open = False
333 333 return
334 334
335 335 def get(self, key, default=None, strip=False):
336 336 if key in self.data:
337 337 r = self.data[key]
338 338 if strip:
339 339 r = r.strip()
340 340 else:
341 341 r = default
342 342 return r
343 343
344 344 def get_json(self, key, default=None):
345 345 result = self.get(key, default=default)
346 346 if result is None:
347 347 return default
348 348 return json.loads(result)
349 349
350 350 def exists(self):
351 351 return True
352 352
353 353 def open_transport(self):
354 354 assert(not self._is_open)
355 355 self._is_open = True
356 356
357 357 def close_transport(self):
358 358 assert(self._is_open)
359 359 self._is_open = False
360 360
361 361
362 362 class TestSmartOSDataSource(FilesystemMockingTestCase):
363 363 def setUp(self):
364 364 super(TestSmartOSDataSource, self).setUp()
365 365
366 366 dsmos = 'cloudinit.sources.DataSourceSmartOS'
367 367 patcher = mock.patch(dsmos + ".jmc_client_factory")
368 368 self.jmc_cfact = patcher.start()
369 369 self.addCleanup(patcher.stop)
370 370 patcher = mock.patch(dsmos + ".get_smartos_environ")
371 371 self.get_smartos_environ = patcher.start()
372 372 self.addCleanup(patcher.stop)
373 373
374 374 self.tmp = tempfile.mkdtemp()
375 375 self.addCleanup(shutil.rmtree, self.tmp)
376 376 self.paths = c_helpers.Paths(
377 377 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
378 378
379 379 self.legacy_user_d = os.path.join(self.tmp, 'legacy_user_tmp')
380 380 os.mkdir(self.legacy_user_d)
381 381
382 382 self.orig_lud = DataSourceSmartOS.LEGACY_USER_D
383 383 DataSourceSmartOS.LEGACY_USER_D = self.legacy_user_d
384 384
385 385 def tearDown(self):
386 386 DataSourceSmartOS.LEGACY_USER_D = self.orig_lud
387 387 super(TestSmartOSDataSource, self).tearDown()
388 388
389 389 def _get_ds(self, mockdata=None, mode=DataSourceSmartOS.SMARTOS_ENV_KVM,
390 390 sys_cfg=None, ds_cfg=None):
391 391 self.jmc_cfact.return_value = PsuedoJoyentClient(mockdata)
392 392 self.get_smartos_environ.return_value = mode
393 393
394 394 if sys_cfg is None:
395 395 sys_cfg = {}
396 396
397 397 if ds_cfg is not None:
398 398 sys_cfg['datasource'] = sys_cfg.get('datasource', {})
399 399 sys_cfg['datasource']['SmartOS'] = ds_cfg
400 400
401 401 return DataSourceSmartOS.DataSourceSmartOS(
402 402 sys_cfg, distro=None, paths=self.paths)
403 403
404 404 def test_no_base64(self):
405 405 ds_cfg = {'no_base64_decode': ['test_var1'], 'all_base': True}
406 406 dsrc = self._get_ds(ds_cfg=ds_cfg)
|
↓ open down ↓ |
406 lines elided |
↑ open up ↑ |
407 407 ret = dsrc.get_data()
408 408 self.assertTrue(ret)
409 409
410 410 def test_uuid(self):
411 411 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
412 412 ret = dsrc.get_data()
413 413 self.assertTrue(ret)
414 414 self.assertEqual(MOCK_RETURNS['sdc:uuid'],
415 415 dsrc.metadata['instance-id'])
416 416
417 + def test_routes(self):
418 + dsrc = self._get_ds(mockdata=MOCK_RETURNS)
419 + ret = dsrc.get_data()
420 + self.assertTrue(ret)
421 + self.assertEqual(MOCK_RETURNS['sdc:routes'],
422 + dsrc.metadata['routes'])
423 +
417 424 def test_root_keys(self):
418 425 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
419 426 ret = dsrc.get_data()
420 427 self.assertTrue(ret)
421 428 self.assertEqual(MOCK_RETURNS['root_authorized_keys'],
422 429 dsrc.metadata['public-keys'])
423 430
424 431 def test_hostname_b64(self):
425 432 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
426 433 ret = dsrc.get_data()
427 434 self.assertTrue(ret)
428 435 self.assertEqual(MOCK_RETURNS['hostname'],
429 436 dsrc.metadata['local-hostname'])
430 437
431 438 def test_hostname(self):
432 439 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
433 440 ret = dsrc.get_data()
434 441 self.assertTrue(ret)
435 442 self.assertEqual(MOCK_RETURNS['hostname'],
436 443 dsrc.metadata['local-hostname'])
437 444
438 445 def test_userdata(self):
439 446 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
440 447 ret = dsrc.get_data()
441 448 self.assertTrue(ret)
442 449 self.assertEqual(MOCK_RETURNS['user-data'],
443 450 dsrc.metadata['legacy-user-data'])
444 451 self.assertEqual(MOCK_RETURNS['cloud-init:user-data'],
445 452 dsrc.userdata_raw)
446 453
447 454 def test_sdc_nics(self):
448 455 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
449 456 ret = dsrc.get_data()
450 457 self.assertTrue(ret)
451 458 self.assertEqual(json.loads(MOCK_RETURNS['sdc:nics']),
452 459 dsrc.metadata['network-data'])
453 460
454 461 def test_sdc_scripts(self):
455 462 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
456 463 ret = dsrc.get_data()
457 464 self.assertTrue(ret)
458 465 self.assertEqual(MOCK_RETURNS['user-script'],
459 466 dsrc.metadata['user-script'])
460 467
461 468 legacy_script_f = "%s/user-script" % self.legacy_user_d
462 469 self.assertTrue(os.path.exists(legacy_script_f))
463 470 self.assertTrue(os.path.islink(legacy_script_f))
464 471 user_script_perm = oct(os.stat(legacy_script_f)[stat.ST_MODE])[-3:]
465 472 self.assertEqual(user_script_perm, '700')
466 473
467 474 def test_scripts_shebanged(self):
468 475 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
469 476 ret = dsrc.get_data()
470 477 self.assertTrue(ret)
471 478 self.assertEqual(MOCK_RETURNS['user-script'],
472 479 dsrc.metadata['user-script'])
473 480
474 481 legacy_script_f = "%s/user-script" % self.legacy_user_d
475 482 self.assertTrue(os.path.exists(legacy_script_f))
476 483 self.assertTrue(os.path.islink(legacy_script_f))
477 484 shebang = None
478 485 with open(legacy_script_f, 'r') as f:
479 486 shebang = f.readlines()[0].strip()
480 487 self.assertEqual(shebang, "#!/bin/bash")
481 488 user_script_perm = oct(os.stat(legacy_script_f)[stat.ST_MODE])[-3:]
482 489 self.assertEqual(user_script_perm, '700')
483 490
484 491 def test_scripts_shebang_not_added(self):
485 492 """
486 493 Test that the SmartOS requirement that plain text scripts
487 494 are executable. This test makes sure that plain texts scripts
488 495 with out file magic have it added appropriately by cloud-init.
489 496 """
490 497
491 498 my_returns = MOCK_RETURNS.copy()
492 499 my_returns['user-script'] = '\n'.join(['#!/usr/bin/perl',
493 500 'print("hi")', ''])
494 501
495 502 dsrc = self._get_ds(mockdata=my_returns)
496 503 ret = dsrc.get_data()
497 504 self.assertTrue(ret)
498 505 self.assertEqual(my_returns['user-script'],
499 506 dsrc.metadata['user-script'])
500 507
501 508 legacy_script_f = "%s/user-script" % self.legacy_user_d
502 509 self.assertTrue(os.path.exists(legacy_script_f))
503 510 self.assertTrue(os.path.islink(legacy_script_f))
504 511 shebang = None
505 512 with open(legacy_script_f, 'r') as f:
506 513 shebang = f.readlines()[0].strip()
507 514 self.assertEqual(shebang, "#!/usr/bin/perl")
508 515
509 516 def test_userdata_removed(self):
510 517 """
511 518 User-data in the SmartOS world is supposed to be written to a file
512 519 each and every boot. This tests to make sure that in the event the
513 520 legacy user-data is removed, the existing user-data is backed-up
514 521 and there is no /var/db/user-data left.
515 522 """
516 523
517 524 user_data_f = "%s/mdata-user-data" % self.legacy_user_d
518 525 with open(user_data_f, 'w') as f:
519 526 f.write("PREVIOUS")
520 527
521 528 my_returns = MOCK_RETURNS.copy()
522 529 del my_returns['user-data']
523 530
524 531 dsrc = self._get_ds(mockdata=my_returns)
525 532 ret = dsrc.get_data()
526 533 self.assertTrue(ret)
527 534 self.assertFalse(dsrc.metadata.get('legacy-user-data'))
528 535
529 536 found_new = False
530 537 for root, _dirs, files in os.walk(self.legacy_user_d):
531 538 for name in files:
532 539 name_f = os.path.join(root, name)
533 540 permissions = oct(os.stat(name_f)[stat.ST_MODE])[-3:]
534 541 if re.match(r'.*\/mdata-user-data$', name_f):
535 542 found_new = True
536 543 print(name_f)
537 544 self.assertEqual(permissions, '400')
538 545
539 546 self.assertFalse(found_new)
540 547
541 548 def test_vendor_data_not_default(self):
542 549 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
543 550 ret = dsrc.get_data()
544 551 self.assertTrue(ret)
545 552 self.assertEqual(MOCK_RETURNS['sdc:vendor-data'],
546 553 dsrc.metadata['vendor-data'])
547 554
548 555 def test_default_vendor_data(self):
549 556 my_returns = MOCK_RETURNS.copy()
550 557 def_op_script = my_returns['sdc:vendor-data']
551 558 del my_returns['sdc:vendor-data']
552 559 dsrc = self._get_ds(mockdata=my_returns)
553 560 ret = dsrc.get_data()
554 561 self.assertTrue(ret)
555 562 self.assertNotEqual(def_op_script, dsrc.metadata['vendor-data'])
556 563
557 564 # we expect default vendor-data is a boothook
558 565 self.assertTrue(dsrc.vendordata_raw.startswith("#cloud-boothook"))
559 566
560 567 def test_disable_iptables_flag(self):
561 568 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
562 569 ret = dsrc.get_data()
563 570 self.assertTrue(ret)
564 571 self.assertEqual(MOCK_RETURNS['disable_iptables_flag'],
565 572 dsrc.metadata['iptables_disable'])
566 573
567 574 def test_motd_sys_info(self):
568 575 dsrc = self._get_ds(mockdata=MOCK_RETURNS)
569 576 ret = dsrc.get_data()
570 577 self.assertTrue(ret)
571 578 self.assertEqual(MOCK_RETURNS['enable_motd_sys_info'],
572 579 dsrc.metadata['motd_sys_info'])
573 580
574 581 def test_default_ephemeral(self):
575 582 # Test to make sure that the builtin config has the ephemeral
576 583 # configuration.
577 584 dsrc = self._get_ds()
578 585 cfg = dsrc.get_config_obj()
579 586
580 587 ret = dsrc.get_data()
581 588 self.assertTrue(ret)
582 589
583 590 assert 'disk_setup' in cfg
584 591 assert 'fs_setup' in cfg
585 592 self.assertIsInstance(cfg['disk_setup'], dict)
586 593 self.assertIsInstance(cfg['fs_setup'], list)
587 594
588 595 def test_override_disk_aliases(self):
589 596 # Test to make sure that the built-in DS is overriden
590 597 builtin = DataSourceSmartOS.BUILTIN_DS_CONFIG
591 598
592 599 mydscfg = {'disk_aliases': {'FOO': '/dev/bar'}}
593 600
594 601 # expect that these values are in builtin, or this is pointless
595 602 for k in mydscfg:
596 603 self.assertIn(k, builtin)
597 604
598 605 dsrc = self._get_ds(ds_cfg=mydscfg)
599 606 ret = dsrc.get_data()
600 607 self.assertTrue(ret)
601 608
602 609 self.assertEqual(mydscfg['disk_aliases']['FOO'],
603 610 dsrc.ds_cfg['disk_aliases']['FOO'])
604 611
605 612 self.assertEqual(dsrc.device_name_to_device('FOO'),
606 613 mydscfg['disk_aliases']['FOO'])
607 614
608 615
609 616 class TestJoyentMetadataClient(FilesystemMockingTestCase):
610 617
611 618 def setUp(self):
612 619 super(TestJoyentMetadataClient, self).setUp()
613 620
614 621 self.serial = mock.MagicMock(spec=serial.Serial)
615 622 self.request_id = 0xabcdef12
616 623 self.metadata_value = 'value'
617 624 self.response_parts = {
618 625 'command': 'SUCCESS',
619 626 'crc': 'b5a9ff00',
620 627 'length': 17 + len(b64e(self.metadata_value)),
621 628 'payload': b64e(self.metadata_value),
622 629 'request_id': '{0:08x}'.format(self.request_id),
623 630 }
624 631
625 632 def make_response():
626 633 payloadstr = ''
627 634 if 'payload' in self.response_parts:
628 635 payloadstr = ' {0}'.format(self.response_parts['payload'])
629 636 return ('V2 {length} {crc} {request_id} '
630 637 '{command}{payloadstr}\n'.format(
631 638 payloadstr=payloadstr,
632 639 **self.response_parts).encode('ascii'))
633 640
634 641 self.metasource_data = None
635 642
636 643 def read_response(length):
637 644 if not self.metasource_data:
638 645 self.metasource_data = make_response()
639 646 self.metasource_data_len = len(self.metasource_data)
640 647 resp = self.metasource_data[:length]
641 648 self.metasource_data = self.metasource_data[length:]
642 649 return resp
643 650
644 651 self.serial.read.side_effect = read_response
645 652 self.patched_funcs.enter_context(
646 653 mock.patch('cloudinit.sources.DataSourceSmartOS.random.randint',
647 654 mock.Mock(return_value=self.request_id)))
648 655
649 656 def _get_client(self):
650 657 return DataSourceSmartOS.JoyentMetadataClient(
651 658 fp=self.serial, smartos_type=DataSourceSmartOS.SMARTOS_ENV_KVM)
652 659
653 660 def assertEndsWith(self, haystack, prefix):
654 661 self.assertTrue(haystack.endswith(prefix),
655 662 "{0} does not end with '{1}'".format(
656 663 repr(haystack), prefix))
657 664
658 665 def assertStartsWith(self, haystack, prefix):
659 666 self.assertTrue(haystack.startswith(prefix),
660 667 "{0} does not start with '{1}'".format(
661 668 repr(haystack), prefix))
662 669
663 670 def test_get_metadata_writes_a_single_line(self):
664 671 client = self._get_client()
665 672 client.get('some_key')
666 673 self.assertEqual(1, self.serial.write.call_count)
667 674 written_line = self.serial.write.call_args[0][0]
668 675 print(type(written_line))
669 676 self.assertEndsWith(written_line.decode('ascii'),
670 677 b'\n'.decode('ascii'))
671 678 self.assertEqual(1, written_line.count(b'\n'))
672 679
673 680 def _get_written_line(self, key='some_key'):
674 681 client = self._get_client()
675 682 client.get(key)
676 683 return self.serial.write.call_args[0][0]
677 684
678 685 def test_get_metadata_writes_bytes(self):
679 686 self.assertIsInstance(self._get_written_line(), six.binary_type)
680 687
681 688 def test_get_metadata_line_starts_with_v2(self):
682 689 foo = self._get_written_line()
683 690 self.assertStartsWith(foo.decode('ascii'), b'V2'.decode('ascii'))
684 691
685 692 def test_get_metadata_uses_get_command(self):
686 693 parts = self._get_written_line().decode('ascii').strip().split(' ')
687 694 self.assertEqual('GET', parts[4])
688 695
689 696 def test_get_metadata_base64_encodes_argument(self):
690 697 key = 'my_key'
691 698 parts = self._get_written_line(key).decode('ascii').strip().split(' ')
692 699 self.assertEqual(b64e(key), parts[5])
693 700
694 701 def test_get_metadata_calculates_length_correctly(self):
695 702 parts = self._get_written_line().decode('ascii').strip().split(' ')
696 703 expected_length = len(' '.join(parts[3:]))
697 704 self.assertEqual(expected_length, int(parts[1]))
698 705
699 706 def test_get_metadata_uses_appropriate_request_id(self):
700 707 parts = self._get_written_line().decode('ascii').strip().split(' ')
701 708 request_id = parts[3]
702 709 self.assertEqual(8, len(request_id))
703 710 self.assertEqual(request_id, request_id.lower())
704 711
705 712 def test_get_metadata_uses_random_number_for_request_id(self):
706 713 line = self._get_written_line()
707 714 request_id = line.decode('ascii').strip().split(' ')[3]
708 715 self.assertEqual('{0:08x}'.format(self.request_id), request_id)
709 716
710 717 def test_get_metadata_checksums_correctly(self):
711 718 parts = self._get_written_line().decode('ascii').strip().split(' ')
712 719 expected_checksum = '{0:08x}'.format(
713 720 crc32(' '.join(parts[3:]).encode('utf-8')) & 0xffffffff)
714 721 checksum = parts[2]
715 722 self.assertEqual(expected_checksum, checksum)
716 723
717 724 def test_get_metadata_reads_a_line(self):
718 725 client = self._get_client()
719 726 client.get('some_key')
720 727 self.assertEqual(self.metasource_data_len, self.serial.read.call_count)
721 728
722 729 def test_get_metadata_returns_valid_value(self):
723 730 client = self._get_client()
724 731 value = client.get('some_key')
725 732 self.assertEqual(self.metadata_value, value)
726 733
727 734 def test_get_metadata_throws_exception_for_incorrect_length(self):
728 735 self.response_parts['length'] = 0
729 736 client = self._get_client()
730 737 self.assertRaises(DataSourceSmartOS.JoyentMetadataFetchException,
731 738 client.get, 'some_key')
732 739
733 740 def test_get_metadata_throws_exception_for_incorrect_crc(self):
734 741 self.response_parts['crc'] = 'deadbeef'
735 742 client = self._get_client()
736 743 self.assertRaises(DataSourceSmartOS.JoyentMetadataFetchException,
737 744 client.get, 'some_key')
738 745
739 746 def test_get_metadata_throws_exception_for_request_id_mismatch(self):
740 747 self.response_parts['request_id'] = 'deadbeef'
741 748 client = self._get_client()
742 749 client._checksum = lambda _: self.response_parts['crc']
743 750 self.assertRaises(DataSourceSmartOS.JoyentMetadataFetchException,
744 751 client.get, 'some_key')
745 752
746 753 def test_get_metadata_returns_None_if_value_not_found(self):
747 754 self.response_parts['payload'] = ''
748 755 self.response_parts['command'] = 'NOTFOUND'
749 756 self.response_parts['length'] = 17
750 757 client = self._get_client()
751 758 client._checksum = lambda _: self.response_parts['crc']
752 759 self.assertIsNone(client.get('some_key'))
753 760
754 761
755 762 class TestNetworkConversion(TestCase):
756 763 def test_convert_simple(self):
757 764 expected = {
758 765 'version': 1,
759 766 'config': [
760 767 {'name': 'net0', 'type': 'physical',
761 768 'subnets': [{'type': 'static', 'gateway': '8.12.42.1',
762 769 'address': '8.12.42.102/24'}],
763 770 'mtu': 1500, 'mac_address': '90:b8:d0:f5:e4:f5'},
764 771 {'name': 'net1', 'type': 'physical',
765 772 'subnets': [{'type': 'static',
766 773 'address': '192.168.128.93/22'}],
767 774 'mtu': 8500, 'mac_address': '90:b8:d0:a5:ff:cd'}]}
768 775 found = convert_net(SDC_NICS)
769 776 self.assertEqual(expected, found)
770 777
771 778 def test_convert_simple_alt(self):
772 779 expected = {
773 780 'version': 1,
774 781 'config': [
775 782 {'name': 'net0', 'type': 'physical',
776 783 'subnets': [{'type': 'static', 'gateway': '8.12.42.1',
777 784 'address': '8.12.42.51/24'}],
778 785 'mtu': 1500, 'mac_address': '90:b8:d0:ae:64:51'},
779 786 {'name': 'net1', 'type': 'physical',
780 787 'subnets': [{'type': 'static',
781 788 'address': '10.210.1.217/24'}],
782 789 'mtu': 1500, 'mac_address': '90:b8:d0:bd:4f:9c'}]}
783 790 found = convert_net(SDC_NICS_ALT)
784 791 self.assertEqual(expected, found)
785 792
786 793 def test_convert_simple_dhcp(self):
787 794 expected = {
788 795 'version': 1,
789 796 'config': [
790 797 {'name': 'net0', 'type': 'physical',
791 798 'subnets': [{'type': 'static', 'gateway': '8.12.42.1',
792 799 'address': '8.12.42.51/24'}],
793 800 'mtu': 1500, 'mac_address': '90:b8:d0:ae:64:51'},
794 801 {'name': 'net1', 'type': 'physical',
795 802 'subnets': [{'type': 'dhcp4'}],
796 803 'mtu': 1500, 'mac_address': '90:b8:d0:bd:4f:9c'}]}
797 804 found = convert_net(SDC_NICS_DHCP)
798 805 self.assertEqual(expected, found)
799 806
800 807 def test_convert_simple_multi_ip(self):
801 808 expected = {
802 809 'version': 1,
803 810 'config': [
804 811 {'name': 'net0', 'type': 'physical',
805 812 'subnets': [{'type': 'static', 'gateway': '8.12.42.1',
806 813 'address': '8.12.42.51/24'},
807 814 {'type': 'static',
808 815 'address': '8.12.42.52/24'}],
809 816 'mtu': 1500, 'mac_address': '90:b8:d0:ae:64:51'},
810 817 {'name': 'net1', 'type': 'physical',
811 818 'subnets': [{'type': 'static',
812 819 'address': '10.210.1.217/24'},
813 820 {'type': 'static',
814 821 'address': '10.210.1.151/24'}],
815 822 'mtu': 1500, 'mac_address': '90:b8:d0:bd:4f:9c'}]}
816 823 found = convert_net(SDC_NICS_MIP)
817 824 self.assertEqual(expected, found)
818 825
819 826 def test_convert_with_dns(self):
820 827 expected = {
821 828 'version': 1,
822 829 'config': [
823 830 {'name': 'net0', 'type': 'physical',
824 831 'subnets': [{'type': 'static', 'gateway': '8.12.42.1',
825 832 'address': '8.12.42.51/24'}],
826 833 'mtu': 1500, 'mac_address': '90:b8:d0:ae:64:51'},
827 834 {'name': 'net1', 'type': 'physical',
828 835 'subnets': [{'type': 'dhcp4'}],
829 836 'mtu': 1500, 'mac_address': '90:b8:d0:bd:4f:9c'},
830 837 {'type': 'nameserver',
831 838 'address': ['8.8.8.8', '8.8.8.1'], 'search': ["local"]}]}
832 839 found = convert_net(
833 840 network_data=SDC_NICS_DHCP, dns_servers=['8.8.8.8', '8.8.8.1'],
834 841 dns_domain="local")
835 842 self.assertEqual(expected, found)
836 843
837 844 def test_convert_simple_multi_ipv6(self):
838 845 expected = {
839 846 'version': 1,
840 847 'config': [
841 848 {'name': 'net0', 'type': 'physical',
842 849 'subnets': [{'type': 'static', 'address':
843 850 '2001:4800:78ff:1b:be76:4eff:fe06:96b3/64'},
844 851 {'type': 'static', 'gateway': '8.12.42.1',
845 852 'address': '8.12.42.51/24'}],
846 853 'mtu': 1500, 'mac_address': '90:b8:d0:ae:64:51'},
847 854 {'name': 'net1', 'type': 'physical',
848 855 'subnets': [{'type': 'static',
849 856 'address': '10.210.1.217/24'}],
850 857 'mtu': 1500, 'mac_address': '90:b8:d0:bd:4f:9c'}]}
851 858 found = convert_net(SDC_NICS_MIP_IPV6)
852 859 self.assertEqual(expected, found)
853 860
854 861 def test_convert_simple_both_ipv4_ipv6(self):
855 862 expected = {
856 863 'version': 1,
857 864 'config': [
858 865 {'mac_address': '90:b8:d0:ae:64:51', 'mtu': 1500,
859 866 'name': 'net0', 'type': 'physical',
860 867 'subnets': [{'address': '2001::10/64', 'gateway': '2001::1',
861 868 'type': 'static'},
862 869 {'address': '8.12.42.51/24',
863 870 'gateway': '8.12.42.1',
864 871 'type': 'static'},
865 872 {'address': '2001::11/64', 'type': 'static'},
866 873 {'address': '8.12.42.52/32', 'type': 'static'}]},
867 874 {'mac_address': '90:b8:d0:bd:4f:9c', 'mtu': 1500,
868 875 'name': 'net1', 'type': 'physical',
869 876 'subnets': [{'address': '10.210.1.217/24',
870 877 'type': 'static'}]}]}
871 878 found = convert_net(SDC_NICS_IPV4_IPV6)
872 879 self.assertEqual(expected, found)
873 880
874 881 def test_gateways_not_on_all_nics(self):
875 882 expected = {
876 883 'version': 1,
877 884 'config': [
878 885 {'mac_address': '90:b8:d0:d8:82:b4', 'mtu': 1500,
879 886 'name': 'net0', 'type': 'physical',
880 887 'subnets': [{'address': '8.12.42.26/24',
881 888 'gateway': '8.12.42.1', 'type': 'static'}]},
882 889 {'mac_address': '90:b8:d0:0a:51:31', 'mtu': 1500,
883 890 'name': 'net1', 'type': 'physical',
884 891 'subnets': [{'address': '10.210.1.27/24',
885 892 'type': 'static'}]}]}
886 893 found = convert_net(SDC_NICS_SINGLE_GATEWAY)
887 894 self.assertEqual(expected, found)
888 895
889 896
890 897 @unittest.skipUnless(DataSourceSmartOS.get_smartos_environ() ==
891 898 DataSourceSmartOS.SMARTOS_ENV_KVM,
892 899 "Only supported on KVM and bhyve guests under SmartOS")
893 900 @unittest.skipUnless(os.access(DataSourceSmartOS.SERIAL_DEVICE, os.W_OK),
894 901 "Requires write access to " +
895 902 DataSourceSmartOS.SERIAL_DEVICE)
896 903 class TestSerialConcurrency(TestCase):
897 904 """
898 905 This class tests locking on an actual serial port, and as such can only
899 906 be run in a kvm or bhyve guest running on a SmartOS host. A test run on
900 907 a metadata socket will not be valid because a metadata socket ensures
901 908 there is only one session over a connection. In contrast, in the
902 909 absence of proper locking multiple processes opening the same serial
903 910 port can corrupt each others' exchanges with the metadata server.
904 911 """
905 912 def setUp(self):
906 913 self.mdata_proc = multiprocessing.Process(target=self.start_mdata_loop)
907 914 self.mdata_proc.start()
908 915 super(TestSerialConcurrency, self).setUp()
909 916
910 917 def tearDown(self):
911 918 # os.kill() rather than mdata_proc.terminate() to avoid console spam.
912 919 os.kill(self.mdata_proc.pid, signal.SIGKILL)
913 920 self.mdata_proc.join()
914 921 super(TestSerialConcurrency, self).tearDown()
915 922
916 923 def start_mdata_loop(self):
917 924 """
918 925 The mdata-get command is repeatedly run in a separate process so
919 926 that it may try to race with metadata operations performed in the
920 927 main test process. Use of mdata-get is better than two processes
921 928 using the protocol implementation in DataSourceSmartOS because we
922 929 are testing to be sure that cloud-init and mdata-get respect each
923 930 others locks.
924 931 """
925 932 while True:
926 933 try:
927 934 subprocess.check_output(['/usr/sbin/mdata-get', 'sdc:routes'])
928 935 except subprocess.CalledProcessError:
929 936 pass
930 937
931 938 def test_all_keys(self):
932 939 self.assertIsNotNone(self.mdata_proc.pid)
933 940 ds = DataSourceSmartOS
934 941 keys = [tup[0] for tup in ds.SMARTOS_ATTRIB_MAP.values()]
935 942 keys.extend(ds.SMARTOS_ATTRIB_JSON.values())
936 943
937 944 client = ds.jmc_client_factory()
938 945 self.assertIsNotNone(client)
939 946
940 947 # The behavior that we are testing for was observed mdata-get running
941 948 # 10 times at roughly the same time as cloud-init fetched each key
942 949 # once. cloud-init would regularly see failures before making it
943 950 # through all keys once.
944 951 for it in range(0, 3):
945 952 for key in keys:
946 953 # We don't care about the return value, just that it doesn't
947 954 # thrown any exceptions.
948 955 client.get(key)
949 956
950 957 self.assertIsNone(self.mdata_proc.exitcode)
951 958
952 959 # vi: ts=4 expandtab
|
↓ open down ↓ |
526 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX