Merge ~chad.smith/cloud-init:ubuntu/xenial into cloud-init:ubuntu/xenial
- Git
- lp:~chad.smith/cloud-init
- ubuntu/xenial
- Merge into ubuntu/xenial
Proposed by
Chad Smith
Status: | Merged |
---|---|
Merged at revision: | 83da1d94fbf51086db0b27d34f986832140297f6 |
Proposed branch: | ~chad.smith/cloud-init:ubuntu/xenial |
Merge into: | cloud-init:ubuntu/xenial |
Diff against target: |
2624 lines (+1361/-289) 42 files modified
.gitignore (+11/-0) Makefile (+3/-1) cloudinit/atomic_helper.py (+6/-0) cloudinit/net/__init__.py (+136/-3) cloudinit/net/tests/test_init.py (+327/-0) cloudinit/sources/DataSourceEc2.py (+1/-1) cloudinit/sources/DataSourceOVF.py (+20/-1) cloudinit/sources/DataSourceOracle.py (+61/-1) cloudinit/sources/helpers/vmware/imc/guestcust_error.py (+1/-0) cloudinit/sources/helpers/vmware/imc/guestcust_util.py (+37/-0) cloudinit/sources/tests/test_oracle.py (+147/-0) cloudinit/tests/helpers.py (+8/-0) debian/changelog (+20/-0) dev/null (+0/-13) doc/rtd/conf.py (+3/-10) doc/rtd/index.rst (+0/-9) doc/rtd/topics/availability.rst (+53/-10) doc/rtd/topics/datasources.rst (+92/-94) doc/rtd/topics/datasources/altcloud.rst (+12/-11) doc/rtd/topics/datasources/azure.rst (+2/-1) doc/rtd/topics/datasources/cloudstack.rst (+1/-1) doc/rtd/topics/datasources/configdrive.rst (+8/-8) doc/rtd/topics/datasources/digitalocean.rst (+4/-2) doc/rtd/topics/datasources/ec2.rst (+6/-5) doc/rtd/topics/datasources/exoscale.rst (+6/-6) doc/rtd/topics/datasources/nocloud.rst (+8/-8) doc/rtd/topics/datasources/opennebula.rst (+15/-15) doc/rtd/topics/datasources/openstack.rst (+2/-1) doc/rtd/topics/datasources/smartos.rst (+7/-5) doc/rtd/topics/debugging.rst (+7/-7) doc/rtd/topics/dir_layout.rst (+22/-17) doc/rtd/topics/docs.rst (+84/-0) doc/rtd/topics/examples.rst (+1/-1) doc/rtd/topics/faq.rst (+43/-0) doc/rtd/topics/format.rst (+53/-28) doc/rtd/topics/merging.rst (+12/-6) doc/rtd/topics/network-config-format-v2.rst (+9/-9) tests/unittests/test_datasource/test_ovf.py (+46/-9) tests/unittests/test_ds_identify.py (+14/-2) tests/unittests/test_vmware/test_guestcust_util.py (+65/-0) tools/ds-identify (+1/-2) tox.ini (+7/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Dan Watkins | Approve | ||
Review via email: mp+372891@code.launchpad.net |
Commit message
new-upstream snapshot for SRU into Xenial
Description of the change
To post a comment you must log in.
Revision history for this message
Dan Watkins (oddbloke) : | # |
review:
Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote : | # |
review:
Approve
(continuous-integration)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.gitignore b/.gitignore |
2 | index 80c509e..b9b98e7 100644 |
3 | --- a/.gitignore |
4 | +++ b/.gitignore |
5 | @@ -12,3 +12,14 @@ stage |
6 | *.snap |
7 | *.cover |
8 | .idea/ |
9 | + |
10 | +# Ignore packaging artifacts |
11 | +cloud-init.dsc |
12 | +cloud-init_*.build |
13 | +cloud-init_*.buildinfo |
14 | +cloud-init_*.changes |
15 | +cloud-init_*.deb |
16 | +cloud-init_*.dsc |
17 | +cloud-init_*.orig.tar.gz |
18 | +cloud-init_*.tar.xz |
19 | +cloud-init_*.upload |
20 | diff --git a/Makefile b/Makefile |
21 | index 4ace227..2c6d0c8 100644 |
22 | --- a/Makefile |
23 | +++ b/Makefile |
24 | @@ -106,7 +106,9 @@ deb-src: |
25 | echo sudo apt-get install devscripts; exit 1; } |
26 | $(PYVER) ./packages/bddeb -S -d |
27 | |
28 | +doc: |
29 | + tox -e doc |
30 | |
31 | .PHONY: test pyflakes pyflakes3 clean pep8 rpm srpm deb deb-src yaml |
32 | .PHONY: check_version pip-test-requirements pip-requirements clean_pyc |
33 | -.PHONY: unittest unittest3 style-check |
34 | +.PHONY: unittest unittest3 style-check doc |
35 | diff --git a/cloudinit/atomic_helper.py b/cloudinit/atomic_helper.py |
36 | index 587b994..1f61faa 100644 |
37 | --- a/cloudinit/atomic_helper.py |
38 | +++ b/cloudinit/atomic_helper.py |
39 | @@ -1,11 +1,13 @@ |
40 | # This file is part of cloud-init. See LICENSE file for license information. |
41 | |
42 | import json |
43 | +import logging |
44 | import os |
45 | import stat |
46 | import tempfile |
47 | |
48 | _DEF_PERMS = 0o644 |
49 | +LOG = logging.getLogger(__name__) |
50 | |
51 | |
52 | def write_file(filename, content, mode=_DEF_PERMS, |
53 | @@ -23,6 +25,10 @@ def write_file(filename, content, mode=_DEF_PERMS, |
54 | try: |
55 | tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(filename), |
56 | delete=False, mode=omode) |
57 | + LOG.debug( |
58 | + "Atomically writing to file %s (via temporary file %s) - %s: [%o]" |
59 | + " %d bytes/chars", |
60 | + filename, tf.name, omode, mode, len(content)) |
61 | tf.write(content) |
62 | tf.close() |
63 | os.chmod(tf.name, mode) |
64 | diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py |
65 | index ea707c0..5de5c6d 100644 |
66 | --- a/cloudinit/net/__init__.py |
67 | +++ b/cloudinit/net/__init__.py |
68 | @@ -109,6 +109,127 @@ def is_bond(devname): |
69 | return os.path.exists(sys_dev_path(devname, "bonding")) |
70 | |
71 | |
72 | +def has_master(devname): |
73 | + return os.path.exists(sys_dev_path(devname, path="master")) |
74 | + |
75 | + |
76 | +def is_netfailover(devname, driver=None): |
77 | + """ netfailover driver uses 3 nics, master, primary and standby. |
78 | + this returns True if the device is either the primary or standby |
79 | + as these devices are to be ignored. |
80 | + """ |
81 | + if driver is None: |
82 | + driver = device_driver(devname) |
83 | + if is_netfail_primary(devname, driver) or is_netfail_standby(devname, |
84 | + driver): |
85 | + return True |
86 | + return False |
87 | + |
88 | + |
89 | +def get_dev_features(devname): |
90 | + """ Returns a str from reading /sys/class/net/<devname>/device/features.""" |
91 | + features = '' |
92 | + try: |
93 | + features = read_sys_net(devname, 'device/features') |
94 | + except Exception: |
95 | + pass |
96 | + return features |
97 | + |
98 | + |
99 | +def has_netfail_standby_feature(devname): |
100 | + """ Return True if VIRTIO_NET_F_STANDBY bit (62) is set. |
101 | + |
102 | + https://github.com/torvalds/linux/blob/ \ |
103 | + 089cf7f6ecb266b6a4164919a2e69bd2f938374a/ \ |
104 | + include/uapi/linux/virtio_net.h#L60 |
105 | + """ |
106 | + features = get_dev_features(devname) |
107 | + if not features or len(features) < 64: |
108 | + return False |
109 | + return features[62] == "1" |
110 | + |
111 | + |
112 | +def is_netfail_master(devname, driver=None): |
113 | + """ A device is a "netfail master" device if: |
114 | + |
115 | + - The device does NOT have the 'master' sysfs attribute |
116 | + - The device driver is 'virtio_net' |
117 | + - The device has the standby feature bit set |
118 | + |
119 | + Return True if all of the above is True. |
120 | + """ |
121 | + if has_master(devname): |
122 | + return False |
123 | + |
124 | + if driver is None: |
125 | + driver = device_driver(devname) |
126 | + |
127 | + if driver != "virtio_net": |
128 | + return False |
129 | + |
130 | + if not has_netfail_standby_feature(devname): |
131 | + return False |
132 | + |
133 | + return True |
134 | + |
135 | + |
136 | +def is_netfail_primary(devname, driver=None): |
137 | + """ A device is a "netfail primary" device if: |
138 | + |
139 | + - the device has a 'master' sysfs file |
140 | + - the device driver is not 'virtio_net' |
141 | + - the 'master' sysfs file points to device with virtio_net driver |
142 | + - the 'master' device has the 'standby' feature bit set |
143 | + |
144 | + Return True if all of the above is True. |
145 | + """ |
146 | + # /sys/class/net/<devname>/master -> ../../<master devname> |
147 | + master_sysfs_path = sys_dev_path(devname, path='master') |
148 | + if not os.path.exists(master_sysfs_path): |
149 | + return False |
150 | + |
151 | + if driver is None: |
152 | + driver = device_driver(devname) |
153 | + |
154 | + if driver == "virtio_net": |
155 | + return False |
156 | + |
157 | + master_devname = os.path.basename(os.path.realpath(master_sysfs_path)) |
158 | + master_driver = device_driver(master_devname) |
159 | + if master_driver != "virtio_net": |
160 | + return False |
161 | + |
162 | + master_has_standby = has_netfail_standby_feature(master_devname) |
163 | + if not master_has_standby: |
164 | + return False |
165 | + |
166 | + return True |
167 | + |
168 | + |
169 | +def is_netfail_standby(devname, driver=None): |
170 | + """ A device is a "netfail standby" device if: |
171 | + |
172 | + - The device has a 'master' sysfs attribute |
173 | + - The device driver is 'virtio_net' |
174 | + - The device has the standby feature bit set |
175 | + |
176 | + Return True if all of the above is True. |
177 | + """ |
178 | + if not has_master(devname): |
179 | + return False |
180 | + |
181 | + if driver is None: |
182 | + driver = device_driver(devname) |
183 | + |
184 | + if driver != "virtio_net": |
185 | + return False |
186 | + |
187 | + if not has_netfail_standby_feature(devname): |
188 | + return False |
189 | + |
190 | + return True |
191 | + |
192 | + |
193 | def is_renamed(devname): |
194 | """ |
195 | /* interface name assignment types (sysfs name_assign_type attribute) */ |
196 | @@ -227,6 +348,9 @@ def find_fallback_nic(blacklist_drivers=None): |
197 | if is_bond(interface): |
198 | # skip any bonds |
199 | continue |
200 | + if is_netfailover(interface): |
201 | + # ignore netfailover primary/standby interfaces |
202 | + continue |
203 | carrier = read_sys_net_int(interface, 'carrier') |
204 | if carrier: |
205 | connected.append(interface) |
206 | @@ -273,9 +397,14 @@ def generate_fallback_config(blacklist_drivers=None, config_driver=None): |
207 | if not target_name: |
208 | # can't read any interfaces addresses (or there are none); give up |
209 | return None |
210 | - target_mac = read_sys_net_safe(target_name, 'address') |
211 | - cfg = {'dhcp4': True, 'set-name': target_name, |
212 | - 'match': {'macaddress': target_mac.lower()}} |
213 | + |
214 | + # netfail cannot use mac for matching, they have duplicate macs |
215 | + if is_netfail_master(target_name): |
216 | + match = {'name': target_name} |
217 | + else: |
218 | + match = { |
219 | + 'macaddress': read_sys_net_safe(target_name, 'address').lower()} |
220 | + cfg = {'dhcp4': True, 'set-name': target_name, 'match': match} |
221 | if config_driver: |
222 | driver = device_driver(target_name) |
223 | if driver: |
224 | @@ -661,6 +790,10 @@ def get_interfaces(): |
225 | continue |
226 | if is_bond(name): |
227 | continue |
228 | + if has_master(name): |
229 | + continue |
230 | + if is_netfailover(name): |
231 | + continue |
232 | mac = get_interface_mac(name) |
233 | # some devices may not have a mac (tun0) |
234 | if not mac: |
235 | diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py |
236 | index d2e38f0..999db98 100644 |
237 | --- a/cloudinit/net/tests/test_init.py |
238 | +++ b/cloudinit/net/tests/test_init.py |
239 | @@ -157,6 +157,12 @@ class TestReadSysNet(CiTestCase): |
240 | ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding')) |
241 | self.assertTrue(net.is_bond('eth0')) |
242 | |
243 | + def test_has_master(self): |
244 | + """has_master is True when /sys/net/devname/master exists.""" |
245 | + self.assertFalse(net.has_master('enP1s1')) |
246 | + ensure_file(os.path.join(self.sysdir, 'enP1s1', 'master')) |
247 | + self.assertTrue(net.has_master('enP1s1')) |
248 | + |
249 | def test_is_vlan(self): |
250 | """is_vlan is True when /sys/net/devname/uevent has DEVTYPE=vlan.""" |
251 | ensure_file(os.path.join(self.sysdir, 'eth0', 'uevent')) |
252 | @@ -204,6 +210,10 @@ class TestGenerateFallbackConfig(CiTestCase): |
253 | self.add_patch('cloudinit.net.util.is_container', 'm_is_container', |
254 | return_value=False) |
255 | self.add_patch('cloudinit.net.util.udevadm_settle', 'm_settle') |
256 | + self.add_patch('cloudinit.net.is_netfailover', 'm_netfail', |
257 | + return_value=False) |
258 | + self.add_patch('cloudinit.net.is_netfail_master', 'm_netfail_master', |
259 | + return_value=False) |
260 | |
261 | def test_generate_fallback_finds_connected_eth_with_mac(self): |
262 | """generate_fallback_config finds any connected device with a mac.""" |
263 | @@ -268,6 +278,61 @@ class TestGenerateFallbackConfig(CiTestCase): |
264 | ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding')) |
265 | self.assertIsNone(net.generate_fallback_config()) |
266 | |
267 | + def test_generate_fallback_config_skips_netfail_devs(self): |
268 | + """gen_fallback_config ignores netfail primary,sby no mac on master.""" |
269 | + mac = 'aa:bb:cc:aa:bb:cc' # netfailover devs share the same mac |
270 | + for iface in ['ens3', 'ens3sby', 'enP0s1f3']: |
271 | + write_file(os.path.join(self.sysdir, iface, 'carrier'), '1') |
272 | + write_file( |
273 | + os.path.join(self.sysdir, iface, 'addr_assign_type'), '0') |
274 | + write_file( |
275 | + os.path.join(self.sysdir, iface, 'address'), mac) |
276 | + |
277 | + def is_netfail(iface, _driver=None): |
278 | + # ens3 is the master |
279 | + if iface == 'ens3': |
280 | + return False |
281 | + return True |
282 | + self.m_netfail.side_effect = is_netfail |
283 | + |
284 | + def is_netfail_master(iface, _driver=None): |
285 | + # ens3 is the master |
286 | + if iface == 'ens3': |
287 | + return True |
288 | + return False |
289 | + self.m_netfail_master.side_effect = is_netfail_master |
290 | + expected = { |
291 | + 'ethernets': { |
292 | + 'ens3': {'dhcp4': True, 'match': {'name': 'ens3'}, |
293 | + 'set-name': 'ens3'}}, |
294 | + 'version': 2} |
295 | + result = net.generate_fallback_config() |
296 | + self.assertEqual(expected, result) |
297 | + |
298 | + |
299 | +class TestNetFindFallBackNic(CiTestCase): |
300 | + |
301 | + with_logs = True |
302 | + |
303 | + def setUp(self): |
304 | + super(TestNetFindFallBackNic, self).setUp() |
305 | + sys_mock = mock.patch('cloudinit.net.get_sys_class_path') |
306 | + self.m_sys_path = sys_mock.start() |
307 | + self.sysdir = self.tmp_dir() + '/' |
308 | + self.m_sys_path.return_value = self.sysdir |
309 | + self.addCleanup(sys_mock.stop) |
310 | + self.add_patch('cloudinit.net.util.is_container', 'm_is_container', |
311 | + return_value=False) |
312 | + self.add_patch('cloudinit.net.util.udevadm_settle', 'm_settle') |
313 | + |
314 | + def test_generate_fallback_finds_first_connected_eth_with_mac(self): |
315 | + """find_fallback_nic finds any connected device with a mac.""" |
316 | + write_file(os.path.join(self.sysdir, 'eth0', 'carrier'), '1') |
317 | + write_file(os.path.join(self.sysdir, 'eth1', 'carrier'), '1') |
318 | + mac = 'aa:bb:cc:aa:bb:cc' |
319 | + write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac) |
320 | + self.assertEqual('eth1', net.find_fallback_nic()) |
321 | + |
322 | |
323 | class TestGetDeviceList(CiTestCase): |
324 | |
325 | @@ -365,6 +430,37 @@ class TestGetInterfaceMAC(CiTestCase): |
326 | expected = [('eth2', 'aa:bb:cc:aa:bb:cc', None, None)] |
327 | self.assertEqual(expected, net.get_interfaces()) |
328 | |
329 | + def test_get_interfaces_by_mac_skips_master_devs(self): |
330 | + """Ignore interfaces with a master device which would have dup mac.""" |
331 | + mac1 = mac2 = 'aa:bb:cc:aa:bb:cc' |
332 | + write_file(os.path.join(self.sysdir, 'eth1', 'addr_assign_type'), '0') |
333 | + write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac1) |
334 | + write_file(os.path.join(self.sysdir, 'eth1', 'master'), "blah") |
335 | + write_file(os.path.join(self.sysdir, 'eth2', 'addr_assign_type'), '0') |
336 | + write_file(os.path.join(self.sysdir, 'eth2', 'address'), mac2) |
337 | + expected = [('eth2', mac2, None, None)] |
338 | + self.assertEqual(expected, net.get_interfaces()) |
339 | + |
340 | + @mock.patch('cloudinit.net.is_netfailover') |
341 | + def test_get_interfaces_by_mac_skips_netfailvoer(self, m_netfail): |
342 | + """Ignore interfaces if netfailover primary or standby.""" |
343 | + mac = 'aa:bb:cc:aa:bb:cc' # netfailover devs share the same mac |
344 | + for iface in ['ens3', 'ens3sby', 'enP0s1f3']: |
345 | + write_file( |
346 | + os.path.join(self.sysdir, iface, 'addr_assign_type'), '0') |
347 | + write_file( |
348 | + os.path.join(self.sysdir, iface, 'address'), mac) |
349 | + |
350 | + def is_netfail(iface, _driver=None): |
351 | + # ens3 is the master |
352 | + if iface == 'ens3': |
353 | + return False |
354 | + else: |
355 | + return True |
356 | + m_netfail.side_effect = is_netfail |
357 | + expected = [('ens3', mac, None, None)] |
358 | + self.assertEqual(expected, net.get_interfaces()) |
359 | + |
360 | |
361 | class TestInterfaceHasOwnMAC(CiTestCase): |
362 | |
363 | @@ -922,3 +1018,234 @@ class TestWaitForPhysdevs(CiTestCase): |
364 | self.m_get_iface_mac.return_value = {} |
365 | net.wait_for_physdevs(netcfg, strict=False) |
366 | self.assertEqual(5 * len(physdevs), self.m_udev_settle.call_count) |
367 | + |
368 | + |
369 | +class TestNetFailOver(CiTestCase): |
370 | + |
371 | + with_logs = True |
372 | + |
373 | + def setUp(self): |
374 | + super(TestNetFailOver, self).setUp() |
375 | + self.add_patch('cloudinit.net.util', 'm_util') |
376 | + self.add_patch('cloudinit.net.read_sys_net', 'm_read_sys_net') |
377 | + self.add_patch('cloudinit.net.device_driver', 'm_device_driver') |
378 | + |
379 | + def test_get_dev_features(self): |
380 | + devname = self.random_string() |
381 | + features = self.random_string() |
382 | + self.m_read_sys_net.return_value = features |
383 | + |
384 | + self.assertEqual(features, net.get_dev_features(devname)) |
385 | + self.assertEqual(1, self.m_read_sys_net.call_count) |
386 | + self.assertEqual(mock.call(devname, 'device/features'), |
387 | + self.m_read_sys_net.call_args_list[0]) |
388 | + |
389 | + def test_get_dev_features_none_returns_empty_string(self): |
390 | + devname = self.random_string() |
391 | + self.m_read_sys_net.side_effect = Exception('error') |
392 | + self.assertEqual('', net.get_dev_features(devname)) |
393 | + self.assertEqual(1, self.m_read_sys_net.call_count) |
394 | + self.assertEqual(mock.call(devname, 'device/features'), |
395 | + self.m_read_sys_net.call_args_list[0]) |
396 | + |
397 | + @mock.patch('cloudinit.net.get_dev_features') |
398 | + def test_has_netfail_standby_feature(self, m_dev_features): |
399 | + devname = self.random_string() |
400 | + standby_features = ('0' * 62) + '1' + '0' |
401 | + m_dev_features.return_value = standby_features |
402 | + self.assertTrue(net.has_netfail_standby_feature(devname)) |
403 | + |
404 | + @mock.patch('cloudinit.net.get_dev_features') |
405 | + def test_has_netfail_standby_feature_short_is_false(self, m_dev_features): |
406 | + devname = self.random_string() |
407 | + standby_features = self.random_string() |
408 | + m_dev_features.return_value = standby_features |
409 | + self.assertFalse(net.has_netfail_standby_feature(devname)) |
410 | + |
411 | + @mock.patch('cloudinit.net.get_dev_features') |
412 | + def test_has_netfail_standby_feature_not_present_is_false(self, |
413 | + m_dev_features): |
414 | + devname = self.random_string() |
415 | + standby_features = '0' * 64 |
416 | + m_dev_features.return_value = standby_features |
417 | + self.assertFalse(net.has_netfail_standby_feature(devname)) |
418 | + |
419 | + @mock.patch('cloudinit.net.get_dev_features') |
420 | + def test_has_netfail_standby_feature_no_features_is_false(self, |
421 | + m_dev_features): |
422 | + devname = self.random_string() |
423 | + standby_features = None |
424 | + m_dev_features.return_value = standby_features |
425 | + self.assertFalse(net.has_netfail_standby_feature(devname)) |
426 | + |
427 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
428 | + @mock.patch('cloudinit.net.os.path.exists') |
429 | + def test_is_netfail_master(self, m_exists, m_standby): |
430 | + devname = self.random_string() |
431 | + driver = 'virtio_net' |
432 | + m_exists.return_value = False # no master sysfs attr |
433 | + m_standby.return_value = True # has standby feature flag |
434 | + self.assertTrue(net.is_netfail_master(devname, driver)) |
435 | + |
436 | + @mock.patch('cloudinit.net.sys_dev_path') |
437 | + def test_is_netfail_master_checks_master_attr(self, m_sysdev): |
438 | + devname = self.random_string() |
439 | + driver = 'virtio_net' |
440 | + m_sysdev.return_value = self.random_string() |
441 | + self.assertFalse(net.is_netfail_master(devname, driver)) |
442 | + self.assertEqual(1, m_sysdev.call_count) |
443 | + self.assertEqual(mock.call(devname, path='master'), |
444 | + m_sysdev.call_args_list[0]) |
445 | + |
446 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
447 | + @mock.patch('cloudinit.net.os.path.exists') |
448 | + def test_is_netfail_master_wrong_driver(self, m_exists, m_standby): |
449 | + devname = self.random_string() |
450 | + driver = self.random_string() |
451 | + self.assertFalse(net.is_netfail_master(devname, driver)) |
452 | + |
453 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
454 | + @mock.patch('cloudinit.net.os.path.exists') |
455 | + def test_is_netfail_master_has_master_attr(self, m_exists, m_standby): |
456 | + devname = self.random_string() |
457 | + driver = 'virtio_net' |
458 | + m_exists.return_value = True # has master sysfs attr |
459 | + self.assertFalse(net.is_netfail_master(devname, driver)) |
460 | + |
461 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
462 | + @mock.patch('cloudinit.net.os.path.exists') |
463 | + def test_is_netfail_master_no_standby_feat(self, m_exists, m_standby): |
464 | + devname = self.random_string() |
465 | + driver = 'virtio_net' |
466 | + m_exists.return_value = False # no master sysfs attr |
467 | + m_standby.return_value = False # no standby feature flag |
468 | + self.assertFalse(net.is_netfail_master(devname, driver)) |
469 | + |
470 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
471 | + @mock.patch('cloudinit.net.os.path.exists') |
472 | + @mock.patch('cloudinit.net.sys_dev_path') |
473 | + def test_is_netfail_primary(self, m_sysdev, m_exists, m_standby): |
474 | + devname = self.random_string() |
475 | + driver = self.random_string() # device not virtio_net |
476 | + master_devname = self.random_string() |
477 | + m_sysdev.return_value = "%s/%s" % (self.random_string(), |
478 | + master_devname) |
479 | + m_exists.return_value = True # has master sysfs attr |
480 | + self.m_device_driver.return_value = 'virtio_net' # master virtio_net |
481 | + m_standby.return_value = True # has standby feature flag |
482 | + self.assertTrue(net.is_netfail_primary(devname, driver)) |
483 | + self.assertEqual(1, self.m_device_driver.call_count) |
484 | + self.assertEqual(mock.call(master_devname), |
485 | + self.m_device_driver.call_args_list[0]) |
486 | + self.assertEqual(1, m_standby.call_count) |
487 | + self.assertEqual(mock.call(master_devname), |
488 | + m_standby.call_args_list[0]) |
489 | + |
490 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
491 | + @mock.patch('cloudinit.net.os.path.exists') |
492 | + @mock.patch('cloudinit.net.sys_dev_path') |
493 | + def test_is_netfail_primary_wrong_driver(self, m_sysdev, m_exists, |
494 | + m_standby): |
495 | + devname = self.random_string() |
496 | + driver = 'virtio_net' |
497 | + self.assertFalse(net.is_netfail_primary(devname, driver)) |
498 | + |
499 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
500 | + @mock.patch('cloudinit.net.os.path.exists') |
501 | + @mock.patch('cloudinit.net.sys_dev_path') |
502 | + def test_is_netfail_primary_no_master(self, m_sysdev, m_exists, m_standby): |
503 | + devname = self.random_string() |
504 | + driver = self.random_string() # device not virtio_net |
505 | + m_exists.return_value = False # no master sysfs attr |
506 | + self.assertFalse(net.is_netfail_primary(devname, driver)) |
507 | + |
508 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
509 | + @mock.patch('cloudinit.net.os.path.exists') |
510 | + @mock.patch('cloudinit.net.sys_dev_path') |
511 | + def test_is_netfail_primary_bad_master(self, m_sysdev, m_exists, |
512 | + m_standby): |
513 | + devname = self.random_string() |
514 | + driver = self.random_string() # device not virtio_net |
515 | + master_devname = self.random_string() |
516 | + m_sysdev.return_value = "%s/%s" % (self.random_string(), |
517 | + master_devname) |
518 | + m_exists.return_value = True # has master sysfs attr |
519 | + self.m_device_driver.return_value = 'XXXX' # master not virtio_net |
520 | + self.assertFalse(net.is_netfail_primary(devname, driver)) |
521 | + |
522 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
523 | + @mock.patch('cloudinit.net.os.path.exists') |
524 | + @mock.patch('cloudinit.net.sys_dev_path') |
525 | + def test_is_netfail_primary_no_standby(self, m_sysdev, m_exists, |
526 | + m_standby): |
527 | + devname = self.random_string() |
528 | + driver = self.random_string() # device not virtio_net |
529 | + master_devname = self.random_string() |
530 | + m_sysdev.return_value = "%s/%s" % (self.random_string(), |
531 | + master_devname) |
532 | + m_exists.return_value = True # has master sysfs attr |
533 | + self.m_device_driver.return_value = 'virtio_net' # master virtio_net |
534 | + m_standby.return_value = False # master has no standby feature flag |
535 | + self.assertFalse(net.is_netfail_primary(devname, driver)) |
536 | + |
537 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
538 | + @mock.patch('cloudinit.net.os.path.exists') |
539 | + def test_is_netfail_standby(self, m_exists, m_standby): |
540 | + devname = self.random_string() |
541 | + driver = 'virtio_net' |
542 | + m_exists.return_value = True # has master sysfs attr |
543 | + m_standby.return_value = True # has standby feature flag |
544 | + self.assertTrue(net.is_netfail_standby(devname, driver)) |
545 | + |
546 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
547 | + @mock.patch('cloudinit.net.os.path.exists') |
548 | + def test_is_netfail_standby_wrong_driver(self, m_exists, m_standby): |
549 | + devname = self.random_string() |
550 | + driver = self.random_string() |
551 | + self.assertFalse(net.is_netfail_standby(devname, driver)) |
552 | + |
553 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
554 | + @mock.patch('cloudinit.net.os.path.exists') |
555 | + def test_is_netfail_standby_no_master(self, m_exists, m_standby): |
556 | + devname = self.random_string() |
557 | + driver = 'virtio_net' |
558 | + m_exists.return_value = False # has master sysfs attr |
559 | + self.assertFalse(net.is_netfail_standby(devname, driver)) |
560 | + |
561 | + @mock.patch('cloudinit.net.has_netfail_standby_feature') |
562 | + @mock.patch('cloudinit.net.os.path.exists') |
563 | + def test_is_netfail_standby_no_standby_feature(self, m_exists, m_standby): |
564 | + devname = self.random_string() |
565 | + driver = 'virtio_net' |
566 | + m_exists.return_value = True # has master sysfs attr |
567 | + m_standby.return_value = False # has standby feature flag |
568 | + self.assertFalse(net.is_netfail_standby(devname, driver)) |
569 | + |
570 | + @mock.patch('cloudinit.net.is_netfail_standby') |
571 | + @mock.patch('cloudinit.net.is_netfail_primary') |
572 | + def test_is_netfailover_primary(self, m_primary, m_standby): |
573 | + devname = self.random_string() |
574 | + driver = self.random_string() |
575 | + m_primary.return_value = True |
576 | + m_standby.return_value = False |
577 | + self.assertTrue(net.is_netfailover(devname, driver)) |
578 | + |
579 | + @mock.patch('cloudinit.net.is_netfail_standby') |
580 | + @mock.patch('cloudinit.net.is_netfail_primary') |
581 | + def test_is_netfailover_standby(self, m_primary, m_standby): |
582 | + devname = self.random_string() |
583 | + driver = self.random_string() |
584 | + m_primary.return_value = False |
585 | + m_standby.return_value = True |
586 | + self.assertTrue(net.is_netfailover(devname, driver)) |
587 | + |
588 | + @mock.patch('cloudinit.net.is_netfail_standby') |
589 | + @mock.patch('cloudinit.net.is_netfail_primary') |
590 | + def test_is_netfailover_returns_false(self, m_primary, m_standby): |
591 | + devname = self.random_string() |
592 | + driver = self.random_string() |
593 | + m_primary.return_value = False |
594 | + m_standby.return_value = False |
595 | + self.assertFalse(net.is_netfailover(devname, driver)) |
596 | + |
597 | +# vi: ts=4 expandtab |
598 | diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py |
599 | index 5c017bf..1010745 100644 |
600 | --- a/cloudinit/sources/DataSourceEc2.py |
601 | +++ b/cloudinit/sources/DataSourceEc2.py |
602 | @@ -473,7 +473,7 @@ def identify_aws(data): |
603 | |
604 | |
605 | def identify_brightbox(data): |
606 | - if data['serial'].endswith('brightbox.com'): |
607 | + if data['serial'].endswith('.brightbox.com'): |
608 | return CloudNames.BRIGHTBOX |
609 | |
610 | |
611 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
612 | index dd941d2..b156189 100644 |
613 | --- a/cloudinit/sources/DataSourceOVF.py |
614 | +++ b/cloudinit/sources/DataSourceOVF.py |
615 | @@ -40,11 +40,15 @@ from cloudinit.sources.helpers.vmware.imc.guestcust_state \ |
616 | from cloudinit.sources.helpers.vmware.imc.guestcust_util import ( |
617 | enable_nics, |
618 | get_nics_to_enable, |
619 | - set_customization_status |
620 | + set_customization_status, |
621 | + get_tools_config |
622 | ) |
623 | |
624 | LOG = logging.getLogger(__name__) |
625 | |
626 | +CONFGROUPNAME_GUESTCUSTOMIZATION = "deployPkg" |
627 | +GUESTCUSTOMIZATION_ENABLE_CUST_SCRIPTS = "enable-custom-scripts" |
628 | + |
629 | |
630 | class DataSourceOVF(sources.DataSource): |
631 | |
632 | @@ -148,6 +152,21 @@ class DataSourceOVF(sources.DataSource): |
633 | product_marker, os.path.join(self.paths.cloud_dir, 'data')) |
634 | special_customization = product_marker and not hasmarkerfile |
635 | customscript = self._vmware_cust_conf.custom_script_name |
636 | + custScriptConfig = get_tools_config( |
637 | + CONFGROUPNAME_GUESTCUSTOMIZATION, |
638 | + GUESTCUSTOMIZATION_ENABLE_CUST_SCRIPTS, |
639 | + "true") |
640 | + if custScriptConfig.lower() == "false": |
641 | + # Update the customization status if there is a |
642 | + # custom script is disabled |
643 | + if special_customization and customscript: |
644 | + msg = "Custom script is disabled by VM Administrator" |
645 | + LOG.debug(msg) |
646 | + set_customization_status( |
647 | + GuestCustStateEnum.GUESTCUST_STATE_RUNNING, |
648 | + GuestCustErrorEnum.GUESTCUST_ERROR_SCRIPT_DISABLED) |
649 | + raise RuntimeError(msg) |
650 | + |
651 | ccScriptsDir = os.path.join( |
652 | self.paths.get_cpath("scripts"), |
653 | "per-instance") |
654 | diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py |
655 | index 1cb0636..eec8740 100644 |
656 | --- a/cloudinit/sources/DataSourceOracle.py |
657 | +++ b/cloudinit/sources/DataSourceOracle.py |
658 | @@ -16,7 +16,7 @@ Notes: |
659 | """ |
660 | |
661 | from cloudinit.url_helper import combine_url, readurl, UrlError |
662 | -from cloudinit.net import dhcp, get_interfaces_by_mac |
663 | +from cloudinit.net import dhcp, get_interfaces_by_mac, is_netfail_master |
664 | from cloudinit import net |
665 | from cloudinit import sources |
666 | from cloudinit import util |
667 | @@ -108,6 +108,56 @@ def _add_network_config_from_opc_imds(network_config): |
668 | 'match': {'macaddress': mac_address}} |
669 | |
670 | |
671 | +def _ensure_netfailover_safe(network_config): |
672 | + """ |
673 | + Search network config physical interfaces to see if any of them are |
674 | + a netfailover master. If found, we prevent matching by MAC as the other |
675 | + failover devices have the same MAC but need to be ignored. |
676 | + |
677 | + Note: we rely on cloudinit.net changes which prevent netfailover devices |
678 | + from being present in the provided network config. For more details about |
679 | + netfailover devices, refer to cloudinit.net module. |
680 | + |
681 | + :param network_config |
682 | + A v1 or v2 network config dict with the primary NIC, and possibly |
683 | + secondary nic configured. This dict will be mutated. |
684 | + |
685 | + """ |
686 | + # ignore anything that's not an actual network-config |
687 | + if 'version' not in network_config: |
688 | + return |
689 | + |
690 | + if network_config['version'] not in [1, 2]: |
691 | + LOG.debug('Ignoring unknown network config version: %s', |
692 | + network_config['version']) |
693 | + return |
694 | + |
695 | + mac_to_name = get_interfaces_by_mac() |
696 | + if network_config['version'] == 1: |
697 | + for cfg in [c for c in network_config['config'] if 'type' in c]: |
698 | + if cfg['type'] == 'physical': |
699 | + if 'mac_address' in cfg: |
700 | + mac = cfg['mac_address'] |
701 | + cur_name = mac_to_name.get(mac) |
702 | + if not cur_name: |
703 | + continue |
704 | + elif is_netfail_master(cur_name): |
705 | + del cfg['mac_address'] |
706 | + |
707 | + elif network_config['version'] == 2: |
708 | + for _, cfg in network_config.get('ethernets', {}).items(): |
709 | + if 'match' in cfg: |
710 | + macaddr = cfg.get('match', {}).get('macaddress') |
711 | + if macaddr: |
712 | + cur_name = mac_to_name.get(macaddr) |
713 | + if not cur_name: |
714 | + continue |
715 | + elif is_netfail_master(cur_name): |
716 | + del cfg['match']['macaddress'] |
717 | + del cfg['set-name'] |
718 | + cfg['match']['name'] = cur_name |
719 | + |
720 | + |
721 | class DataSourceOracle(sources.DataSource): |
722 | |
723 | dsname = 'Oracle' |
724 | @@ -208,9 +258,13 @@ class DataSourceOracle(sources.DataSource): |
725 | We nonetheless return cmdline provided config if present |
726 | and fallback to generate fallback.""" |
727 | if self._network_config == sources.UNSET: |
728 | + # this is v1 |
729 | self._network_config = cmdline.read_initramfs_config() |
730 | + |
731 | if not self._network_config: |
732 | + # this is now v2 |
733 | self._network_config = self.distro.generate_fallback_config() |
734 | + |
735 | if self.ds_cfg.get('configure_secondary_nics'): |
736 | try: |
737 | # Mutate self._network_config to include secondary VNICs |
738 | @@ -219,6 +273,12 @@ class DataSourceOracle(sources.DataSource): |
739 | util.logexc( |
740 | LOG, |
741 | "Failed to fetch secondary network configuration!") |
742 | + |
743 | + # we need to verify that the nic selected is not a netfail over |
744 | + # device and, if it is a netfail master, then we need to avoid |
745 | + # emitting any match by mac |
746 | + _ensure_netfailover_safe(self._network_config) |
747 | + |
748 | return self._network_config |
749 | |
750 | |
751 | diff --git a/cloudinit/sources/helpers/vmware/imc/guestcust_error.py b/cloudinit/sources/helpers/vmware/imc/guestcust_error.py |
752 | index db5a00d..65ae739 100644 |
753 | --- a/cloudinit/sources/helpers/vmware/imc/guestcust_error.py |
754 | +++ b/cloudinit/sources/helpers/vmware/imc/guestcust_error.py |
755 | @@ -10,5 +10,6 @@ class GuestCustErrorEnum(object): |
756 | """Specifies different errors of Guest Customization engine""" |
757 | |
758 | GUESTCUST_ERROR_SUCCESS = 0 |
759 | + GUESTCUST_ERROR_SCRIPT_DISABLED = 6 |
760 | |
761 | # vi: ts=4 expandtab |
762 | diff --git a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py |
763 | index a590f32..eb78172 100644 |
764 | --- a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py |
765 | +++ b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py |
766 | @@ -7,6 +7,7 @@ |
767 | |
768 | import logging |
769 | import os |
770 | +import re |
771 | import time |
772 | |
773 | from cloudinit import util |
774 | @@ -117,4 +118,40 @@ def enable_nics(nics): |
775 | logger.warning("Can't connect network interfaces after %d attempts", |
776 | enableNicsWaitRetries) |
777 | |
778 | + |
779 | +def get_tools_config(section, key, defaultVal): |
780 | + """ Return the value of [section] key from VMTools configuration. |
781 | + |
782 | + @param section: String of section to read from VMTools config |
783 | + @returns: String value from key in [section] or defaultVal if |
784 | + [section] is not present or vmware-toolbox-cmd is |
785 | + not installed. |
786 | + """ |
787 | + |
788 | + if not util.which('vmware-toolbox-cmd'): |
789 | + logger.debug( |
790 | + 'vmware-toolbox-cmd not installed, returning default value') |
791 | + return defaultVal |
792 | + |
793 | + retValue = defaultVal |
794 | + cmd = ['vmware-toolbox-cmd', 'config', 'get', section, key] |
795 | + |
796 | + try: |
797 | + (outText, _) = util.subp(cmd) |
798 | + m = re.match(r'([a-zA-Z0-9 ]+)=(.*)', outText) |
799 | + if m: |
800 | + retValue = m.group(2).strip() |
801 | + logger.debug("Get tools config: [%s] %s = %s", |
802 | + section, key, retValue) |
803 | + else: |
804 | + logger.debug( |
805 | + "Tools config: [%s] %s is not found, return default value: %s", |
806 | + section, key, retValue) |
807 | + except util.ProcessExecutionError as e: |
808 | + logger.error("Failed running %s[%s]", cmd, e.exit_code) |
809 | + logger.exception(e) |
810 | + |
811 | + return retValue |
812 | + |
813 | + |
814 | # vi: ts=4 expandtab |
815 | diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py |
816 | index 2a70bbc..85b6db9 100644 |
817 | --- a/cloudinit/sources/tests/test_oracle.py |
818 | +++ b/cloudinit/sources/tests/test_oracle.py |
819 | @@ -8,6 +8,7 @@ from cloudinit.tests import helpers as test_helpers |
820 | |
821 | from textwrap import dedent |
822 | import argparse |
823 | +import copy |
824 | import httpretty |
825 | import json |
826 | import mock |
827 | @@ -586,4 +587,150 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase): |
828 | self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0]) |
829 | |
830 | |
831 | +class TestNetworkConfigFiltersNetFailover(test_helpers.CiTestCase): |
832 | + |
833 | + with_logs = True |
834 | + |
835 | + def setUp(self): |
836 | + super(TestNetworkConfigFiltersNetFailover, self).setUp() |
837 | + self.add_patch(DS_PATH + '.get_interfaces_by_mac', |
838 | + 'm_get_interfaces_by_mac') |
839 | + self.add_patch(DS_PATH + '.is_netfail_master', 'm_netfail_master') |
840 | + |
841 | + def test_ignore_bogus_network_config(self): |
842 | + netcfg = {'something': 'here'} |
843 | + passed_netcfg = copy.copy(netcfg) |
844 | + oracle._ensure_netfailover_safe(passed_netcfg) |
845 | + self.assertEqual(netcfg, passed_netcfg) |
846 | + |
847 | + def test_ignore_network_config_unknown_versions(self): |
848 | + netcfg = {'something': 'here', 'version': 3} |
849 | + passed_netcfg = copy.copy(netcfg) |
850 | + oracle._ensure_netfailover_safe(passed_netcfg) |
851 | + self.assertEqual(netcfg, passed_netcfg) |
852 | + |
853 | + def test_checks_v1_type_physical_interfaces(self): |
854 | + mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' |
855 | + self.m_get_interfaces_by_mac.return_value = { |
856 | + mac_addr: nic_name, |
857 | + } |
858 | + netcfg = {'version': 1, 'config': [ |
859 | + {'type': 'physical', 'name': nic_name, 'mac_address': mac_addr, |
860 | + 'subnets': [{'type': 'dhcp4'}]}]} |
861 | + passed_netcfg = copy.copy(netcfg) |
862 | + self.m_netfail_master.return_value = False |
863 | + oracle._ensure_netfailover_safe(passed_netcfg) |
864 | + self.assertEqual(netcfg, passed_netcfg) |
865 | + self.assertEqual([mock.call(nic_name)], |
866 | + self.m_netfail_master.call_args_list) |
867 | + |
868 | + def test_checks_v1_skips_non_phys_interfaces(self): |
869 | + mac_addr, nic_name = '00:00:17:02:2b:b1', 'bond0' |
870 | + self.m_get_interfaces_by_mac.return_value = { |
871 | + mac_addr: nic_name, |
872 | + } |
873 | + netcfg = {'version': 1, 'config': [ |
874 | + {'type': 'bond', 'name': nic_name, 'mac_address': mac_addr, |
875 | + 'subnets': [{'type': 'dhcp4'}]}]} |
876 | + passed_netcfg = copy.copy(netcfg) |
877 | + oracle._ensure_netfailover_safe(passed_netcfg) |
878 | + self.assertEqual(netcfg, passed_netcfg) |
879 | + self.assertEqual(0, self.m_netfail_master.call_count) |
880 | + |
881 | + def test_removes_master_mac_property_v1(self): |
882 | + nic_master, mac_master = 'ens3', self.random_string() |
883 | + nic_other, mac_other = 'ens7', self.random_string() |
884 | + nic_extra, mac_extra = 'enp0s1f2', self.random_string() |
885 | + self.m_get_interfaces_by_mac.return_value = { |
886 | + mac_master: nic_master, |
887 | + mac_other: nic_other, |
888 | + mac_extra: nic_extra, |
889 | + } |
890 | + netcfg = {'version': 1, 'config': [ |
891 | + {'type': 'physical', 'name': nic_master, |
892 | + 'mac_address': mac_master}, |
893 | + {'type': 'physical', 'name': nic_other, 'mac_address': mac_other}, |
894 | + {'type': 'physical', 'name': nic_extra, 'mac_address': mac_extra}, |
895 | + ]} |
896 | + |
897 | + def _is_netfail_master(iface): |
898 | + if iface == 'ens3': |
899 | + return True |
900 | + return False |
901 | + self.m_netfail_master.side_effect = _is_netfail_master |
902 | + expected_cfg = {'version': 1, 'config': [ |
903 | + {'type': 'physical', 'name': nic_master}, |
904 | + {'type': 'physical', 'name': nic_other, 'mac_address': mac_other}, |
905 | + {'type': 'physical', 'name': nic_extra, 'mac_address': mac_extra}, |
906 | + ]} |
907 | + oracle._ensure_netfailover_safe(netcfg) |
908 | + self.assertEqual(expected_cfg, netcfg) |
909 | + |
910 | + def test_checks_v2_type_ethernet_interfaces(self): |
911 | + mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' |
912 | + self.m_get_interfaces_by_mac.return_value = { |
913 | + mac_addr: nic_name, |
914 | + } |
915 | + netcfg = {'version': 2, 'ethernets': { |
916 | + nic_name: {'dhcp4': True, 'critical': True, 'set-name': nic_name, |
917 | + 'match': {'macaddress': mac_addr}}}} |
918 | + passed_netcfg = copy.copy(netcfg) |
919 | + self.m_netfail_master.return_value = False |
920 | + oracle._ensure_netfailover_safe(passed_netcfg) |
921 | + self.assertEqual(netcfg, passed_netcfg) |
922 | + self.assertEqual([mock.call(nic_name)], |
923 | + self.m_netfail_master.call_args_list) |
924 | + |
925 | + def test_skips_v2_non_ethernet_interfaces(self): |
926 | + mac_addr, nic_name = '00:00:17:02:2b:b1', 'wlps0' |
927 | + self.m_get_interfaces_by_mac.return_value = { |
928 | + mac_addr: nic_name, |
929 | + } |
930 | + netcfg = {'version': 2, 'wifis': { |
931 | + nic_name: {'dhcp4': True, 'critical': True, 'set-name': nic_name, |
932 | + 'match': {'macaddress': mac_addr}}}} |
933 | + passed_netcfg = copy.copy(netcfg) |
934 | + oracle._ensure_netfailover_safe(passed_netcfg) |
935 | + self.assertEqual(netcfg, passed_netcfg) |
936 | + self.assertEqual(0, self.m_netfail_master.call_count) |
937 | + |
938 | + def test_removes_master_mac_property_v2(self): |
939 | + nic_master, mac_master = 'ens3', self.random_string() |
940 | + nic_other, mac_other = 'ens7', self.random_string() |
941 | + nic_extra, mac_extra = 'enp0s1f2', self.random_string() |
942 | + self.m_get_interfaces_by_mac.return_value = { |
943 | + mac_master: nic_master, |
944 | + mac_other: nic_other, |
945 | + mac_extra: nic_extra, |
946 | + } |
947 | + netcfg = {'version': 2, 'ethernets': { |
948 | + nic_extra: {'dhcp4': True, 'set-name': nic_extra, |
949 | + 'match': {'macaddress': mac_extra}}, |
950 | + nic_other: {'dhcp4': True, 'set-name': nic_other, |
951 | + 'match': {'macaddress': mac_other}}, |
952 | + nic_master: {'dhcp4': True, 'set-name': nic_master, |
953 | + 'match': {'macaddress': mac_master}}, |
954 | + }} |
955 | + |
956 | + def _is_netfail_master(iface): |
957 | + if iface == 'ens3': |
958 | + return True |
959 | + return False |
960 | + self.m_netfail_master.side_effect = _is_netfail_master |
961 | + |
962 | + expected_cfg = {'version': 2, 'ethernets': { |
963 | + nic_master: {'dhcp4': True, 'match': {'name': nic_master}}, |
964 | + nic_extra: {'dhcp4': True, 'set-name': nic_extra, |
965 | + 'match': {'macaddress': mac_extra}}, |
966 | + nic_other: {'dhcp4': True, 'set-name': nic_other, |
967 | + 'match': {'macaddress': mac_other}}, |
968 | + }} |
969 | + oracle._ensure_netfailover_safe(netcfg) |
970 | + import pprint |
971 | + pprint.pprint(netcfg) |
972 | + print('---- ^^ modified ^^ ---- vv original vv ----') |
973 | + pprint.pprint(expected_cfg) |
974 | + self.assertEqual(expected_cfg, netcfg) |
975 | + |
976 | + |
977 | # vi: ts=4 expandtab |
978 | diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py |
979 | index 23fddd0..4dad2af 100644 |
980 | --- a/cloudinit/tests/helpers.py |
981 | +++ b/cloudinit/tests/helpers.py |
982 | @@ -6,7 +6,9 @@ import functools |
983 | import httpretty |
984 | import logging |
985 | import os |
986 | +import random |
987 | import shutil |
988 | +import string |
989 | import sys |
990 | import tempfile |
991 | import time |
992 | @@ -243,6 +245,12 @@ class CiTestCase(TestCase): |
993 | myds.metadata.update(metadata) |
994 | return cloud.Cloud(myds, self.paths, sys_cfg, mydist, None) |
995 | |
996 | + @classmethod |
997 | + def random_string(cls, length=8): |
998 | + """ return a random lowercase string with default length of 8""" |
999 | + return ''.join( |
1000 | + random.choice(string.ascii_lowercase) for _ in range(length)) |
1001 | + |
1002 | |
1003 | class ResourceUsingTestCase(CiTestCase): |
1004 | |
1005 | diff --git a/debian/changelog b/debian/changelog |
1006 | index 93c1751..5b03045 100644 |
1007 | --- a/debian/changelog |
1008 | +++ b/debian/changelog |
1009 | @@ -1,3 +1,23 @@ |
1010 | +cloud-init (19.2-36-g059d049c-0ubuntu1~16.04.1) xenial; urgency=medium |
1011 | + |
1012 | + * New upstream snapshot. (LP: #1844334) |
1013 | + - net: add is_master check for filtering device list |
1014 | + - docs: more complete list of availability [Joshua Powers] |
1015 | + - docs: start FAQ page [Joshua Powers] |
1016 | + - docs: cleanup output & order of datasource page [Joshua Powers] |
1017 | + - Brightbox: restrict detection to require full domain match |
1018 | + .brightbox.com [Scott Moser] |
1019 | + - VMWware: add option into VMTools config to enable/disable custom script. |
1020 | + [Xiaofeng Wang] |
1021 | + - net,Oracle: Add support for netfailover detection |
1022 | + - atomic_helper: add DEBUG logging to write_file |
1023 | + - doc: document doc, create makefile and tox target [Joshua Powers] |
1024 | + - .gitignore: ignore files produced by package builds |
1025 | + - docs: fix whitespace, spelling, and line length [Joshua Powers] |
1026 | + - docs: remove unnecessary file in doc directory [Joshua Powers] |
1027 | + |
1028 | + -- Chad Smith <chad.smith@canonical.com> Tue, 17 Sep 2019 08:18:06 -0600 |
1029 | + |
1030 | cloud-init (19.2-24-ge7881d5c-0ubuntu1~16.04.1) xenial; urgency=medium |
1031 | |
1032 | * New upstream snapshot. (LP: #1841099) |
1033 | diff --git a/doc/README b/doc/README |
1034 | deleted file mode 100644 |
1035 | index 8355919..0000000 |
1036 | --- a/doc/README |
1037 | +++ /dev/null |
1038 | @@ -1,4 +0,0 @@ |
1039 | -This project is cloud-init it is hosted on launchpad at |
1040 | -https://launchpad.net/cloud-init |
1041 | - |
1042 | -The package was previously named ec2-init. |
1043 | diff --git a/doc/rtd/conf.py b/doc/rtd/conf.py |
1044 | index 4174477..9b27484 100644 |
1045 | --- a/doc/rtd/conf.py |
1046 | +++ b/doc/rtd/conf.py |
1047 | @@ -17,7 +17,8 @@ from cloudinit.config.schema import get_schema_doc |
1048 | # ] |
1049 | |
1050 | # General information about the project. |
1051 | -project = 'Cloud-Init' |
1052 | +project = 'cloud-init' |
1053 | +copyright = '2019, Canonical Ltd.' |
1054 | |
1055 | # -- General configuration ---------------------------------------------------- |
1056 | |
1057 | @@ -59,15 +60,7 @@ show_authors = False |
1058 | |
1059 | # The theme to use for HTML and HTML Help pages. See the documentation for |
1060 | # a list of builtin themes. |
1061 | -html_theme = 'default' |
1062 | - |
1063 | -# Theme options are theme-specific and customize the look and feel of a theme |
1064 | -# further. For a list of options available for each theme, see the |
1065 | -# documentation. |
1066 | -html_theme_options = { |
1067 | - "bodyfont": "Ubuntu, Arial, sans-serif", |
1068 | - "headfont": "Ubuntu, Arial, sans-serif" |
1069 | -} |
1070 | +html_theme = 'sphinx_rtd_theme' |
1071 | |
1072 | # The name of an image file (relative to this directory) to place at the top |
1073 | # of the sidebar. |
1074 | diff --git a/doc/rtd/index.rst b/doc/rtd/index.rst |
1075 | index 20a99a3..c670b20 100644 |
1076 | --- a/doc/rtd/index.rst |
1077 | +++ b/doc/rtd/index.rst |
1078 | @@ -1,14 +1,5 @@ |
1079 | .. _index: |
1080 | |
1081 | -.. http://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html |
1082 | -.. As suggested at link above for headings use: |
1083 | -.. # with overline, for parts |
1084 | -.. * with overline, for chapters |
1085 | -.. =, for sections |
1086 | -.. -, for subsections |
1087 | -.. ^, for subsubsections |
1088 | -.. “, for paragraphs |
1089 | - |
1090 | ############# |
1091 | Documentation |
1092 | ############# |
1093 | diff --git a/doc/rtd/topics/availability.rst b/doc/rtd/topics/availability.rst |
1094 | index ef5ae7b..3f215b1 100644 |
1095 | --- a/doc/rtd/topics/availability.rst |
1096 | +++ b/doc/rtd/topics/availability.rst |
1097 | @@ -1,21 +1,64 @@ |
1098 | -************ |
1099 | +.. _availability: |
1100 | + |
1101 | Availability |
1102 | ************ |
1103 | |
1104 | -It is currently installed in the `Ubuntu Cloud Images`_ and also in the official `Ubuntu`_ images available on EC2, Azure, GCE and many other clouds. |
1105 | +Below outlines the current availability of cloud-init across |
1106 | +distributions and clouds, both public and private. |
1107 | + |
1108 | +.. note:: |
1109 | + |
1110 | + If a distribution or cloud does not show up in the list below contact |
1111 | + them and ask for images to be generated using cloud-init! |
1112 | |
1113 | -Versions for other systems can be (or have been) created for the following distributions: |
1114 | +Distributions |
1115 | +============= |
1116 | + |
1117 | +Cloud-init has support across all major Linux distributions and |
1118 | +FreeBSD: |
1119 | |
1120 | - Ubuntu |
1121 | +- SLES/openSUSE |
1122 | +- RHEL/CentOS |
1123 | - Fedora |
1124 | +- Gentoo Linux |
1125 | - Debian |
1126 | -- RHEL |
1127 | -- CentOS |
1128 | -- *and more...* |
1129 | +- ArchLinux |
1130 | +- FreeBSD |
1131 | + |
1132 | +Clouds |
1133 | +====== |
1134 | + |
1135 | +Cloud-init provides support across a wide ranging list of execution |
1136 | +environments in the public cloud: |
1137 | + |
1138 | +- Amazon Web Services |
1139 | +- Microsoft Azure |
1140 | +- Google Cloud Platform |
1141 | +- Oracle Cloud Infrastructure |
1142 | +- Softlayer |
1143 | +- Rackspace Public Cloud |
1144 | +- IBM Cloud |
1145 | +- Digital Ocean |
1146 | +- Bigstep |
1147 | +- Hetzner |
1148 | +- Joyent |
1149 | +- CloudSigma |
1150 | +- Alibaba Cloud |
1151 | +- OVH |
1152 | +- OpenNebula |
1153 | +- Exoscale |
1154 | +- Scaleway |
1155 | +- CloudStack |
1156 | +- AltCloud |
1157 | +- SmartOS |
1158 | |
1159 | -So ask your distribution provider where you can obtain an image with it built-in if one is not already available ☺ |
1160 | +Additionally, cloud-init is supported on these private clouds: |
1161 | |
1162 | +- Bare metal installs |
1163 | +- OpenStack |
1164 | +- LXD |
1165 | +- KVM |
1166 | +- Metal-as-a-Service (MAAS) |
1167 | |
1168 | -.. _Ubuntu Cloud Images: http://cloud-images.ubuntu.com/ |
1169 | -.. _Ubuntu: http://www.ubuntu.com/ |
1170 | -.. vi: textwidth=78 |
1171 | +.. vi: textwidth=79 |
1172 | diff --git a/doc/rtd/topics/datasources.rst b/doc/rtd/topics/datasources.rst |
1173 | index 2148cd5..8e58be9 100644 |
1174 | --- a/doc/rtd/topics/datasources.rst |
1175 | +++ b/doc/rtd/topics/datasources.rst |
1176 | @@ -1,89 +1,57 @@ |
1177 | .. _datasources: |
1178 | |
1179 | -*********** |
1180 | Datasources |
1181 | *********** |
1182 | |
1183 | -What is a datasource? |
1184 | -===================== |
1185 | - |
1186 | Datasources are sources of configuration data for cloud-init that typically |
1187 | -come from the user (aka userdata) or come from the stack that created the |
1188 | -configuration drive (aka metadata). Typical userdata would include files, |
1189 | +come from the user (e.g. userdata) or come from the cloud that created the |
1190 | +configuration drive (e.g. metadata). Typical userdata would include files, |
1191 | yaml, and shell scripts while typical metadata would include server name, |
1192 | -instance id, display name and other cloud specific details. Since there are |
1193 | -multiple ways to provide this data (each cloud solution seems to prefer its |
1194 | -own way) internally a datasource abstract class was created to allow for a |
1195 | -single way to access the different cloud systems methods to provide this data |
1196 | -through the typical usage of subclasses. |
1197 | - |
1198 | -Any metadata processed by cloud-init's datasources is persisted as |
1199 | -``/run/cloud-init/instance-data.json``. Cloud-init provides tooling |
1200 | -to quickly introspect some of that data. See :ref:`instance_metadata` for |
1201 | -more information. |
1202 | - |
1203 | - |
1204 | -Datasource API |
1205 | --------------- |
1206 | -The current interface that a datasource object must provide is the following: |
1207 | - |
1208 | -.. sourcecode:: python |
1209 | +instance id, display name and other cloud specific details. |
1210 | |
1211 | - # returns a mime multipart message that contains |
1212 | - # all the various fully-expanded components that |
1213 | - # were found from processing the raw userdata string |
1214 | - # - when filtering only the mime messages targeting |
1215 | - # this instance id will be returned (or messages with |
1216 | - # no instance id) |
1217 | - def get_userdata(self, apply_filter=False) |
1218 | - |
1219 | - # returns the raw userdata string (or none) |
1220 | - def get_userdata_raw(self) |
1221 | +Since there are multiple ways to provide this data (each cloud solution seems |
1222 | +to prefer its own way) internally a datasource abstract class was created to |
1223 | +allow for a single way to access the different cloud systems methods to provide |
1224 | +this data through the typical usage of subclasses. |
1225 | |
1226 | - # returns a integer (or none) which can be used to identify |
1227 | - # this instance in a group of instances which are typically |
1228 | - # created from a single command, thus allowing programmatic |
1229 | - # filtering on this launch index (or other selective actions) |
1230 | - @property |
1231 | - def launch_index(self) |
1232 | - |
1233 | - # the data sources' config_obj is a cloud-config formatted |
1234 | - # object that came to it from ways other than cloud-config |
1235 | - # because cloud-config content would be handled elsewhere |
1236 | - def get_config_obj(self) |
1237 | - |
1238 | - #returns a list of public ssh keys |
1239 | - def get_public_ssh_keys(self) |
1240 | - |
1241 | - # translates a device 'short' name into the actual physical device |
1242 | - # fully qualified name (or none if said physical device is not attached |
1243 | - # or does not exist) |
1244 | - def device_name_to_device(self, name) |
1245 | +Any metadata processed by cloud-init's datasources is persisted as |
1246 | +``/run/cloud-init/instance-data.json``. Cloud-init provides tooling to quickly |
1247 | +introspect some of that data. See :ref:`instance_metadata` for more |
1248 | +information. |
1249 | |
1250 | - # gets the locale string this instance should be applying |
1251 | - # which typically used to adjust the instances locale settings files |
1252 | - def get_locale(self) |
1253 | +Known Sources |
1254 | +============= |
1255 | |
1256 | - @property |
1257 | - def availability_zone(self) |
1258 | +The following is a list of documents for each supported datasource: |
1259 | |
1260 | - # gets the instance id that was assigned to this instance by the |
1261 | - # cloud provider or when said instance id does not exist in the backing |
1262 | - # metadata this will return 'iid-datasource' |
1263 | - def get_instance_id(self) |
1264 | +.. toctree:: |
1265 | + :titlesonly: |
1266 | |
1267 | - # gets the fully qualified domain name that this host should be using |
1268 | - # when configuring network or hostname releated settings, typically |
1269 | - # assigned either by the cloud provider or the user creating the vm |
1270 | - def get_hostname(self, fqdn=False) |
1271 | + datasources/aliyun.rst |
1272 | + datasources/altcloud.rst |
1273 | + datasources/ec2.rst |
1274 | + datasources/azure.rst |
1275 | + datasources/cloudsigma.rst |
1276 | + datasources/cloudstack.rst |
1277 | + datasources/configdrive.rst |
1278 | + datasources/digitalocean.rst |
1279 | + datasources/exoscale.rst |
1280 | + datasources/fallback.rst |
1281 | + datasources/gce.rst |
1282 | + datasources/maas.rst |
1283 | + datasources/nocloud.rst |
1284 | + datasources/opennebula.rst |
1285 | + datasources/openstack.rst |
1286 | + datasources/oracle.rst |
1287 | + datasources/ovf.rst |
1288 | + datasources/smartos.rst |
1289 | |
1290 | - def get_package_mirror_info(self) |
1291 | |
1292 | +Creation |
1293 | +======== |
1294 | |
1295 | -Adding a new Datasource |
1296 | ------------------------ |
1297 | The datasource objects have a few touch points with cloud-init. If you |
1298 | -are interested in adding a new datasource for your cloud platform you'll |
1299 | +are interested in adding a new datasource for your cloud platform you will |
1300 | need to take care of the following items: |
1301 | |
1302 | * **Identify a mechanism for positive identification of the platform**: |
1303 | @@ -139,31 +107,61 @@ need to take care of the following items: |
1304 | file in ``doc/datasources/<cloudplatform>.rst`` |
1305 | |
1306 | |
1307 | -Datasource Documentation |
1308 | -======================== |
1309 | -The following is a list of the implemented datasources. |
1310 | -Follow for more information. |
1311 | +API |
1312 | +=== |
1313 | |
1314 | -.. toctree:: |
1315 | - :maxdepth: 2 |
1316 | +The current interface that a datasource object must provide is the following: |
1317 | |
1318 | - datasources/aliyun.rst |
1319 | - datasources/altcloud.rst |
1320 | - datasources/azure.rst |
1321 | - datasources/cloudsigma.rst |
1322 | - datasources/cloudstack.rst |
1323 | - datasources/configdrive.rst |
1324 | - datasources/digitalocean.rst |
1325 | - datasources/ec2.rst |
1326 | - datasources/exoscale.rst |
1327 | - datasources/maas.rst |
1328 | - datasources/nocloud.rst |
1329 | - datasources/opennebula.rst |
1330 | - datasources/openstack.rst |
1331 | - datasources/oracle.rst |
1332 | - datasources/ovf.rst |
1333 | - datasources/smartos.rst |
1334 | - datasources/fallback.rst |
1335 | - datasources/gce.rst |
1336 | +.. sourcecode:: python |
1337 | + |
1338 | + # returns a mime multipart message that contains |
1339 | + # all the various fully-expanded components that |
1340 | + # were found from processing the raw user data string |
1341 | + # - when filtering only the mime messages targeting |
1342 | + # this instance id will be returned (or messages with |
1343 | + # no instance id) |
1344 | + def get_userdata(self, apply_filter=False) |
1345 | + |
1346 | + # returns the raw userdata string (or none) |
1347 | + def get_userdata_raw(self) |
1348 | + |
1349 | + # returns a integer (or none) which can be used to identify |
1350 | + # this instance in a group of instances which are typically |
1351 | + # created from a single command, thus allowing programmatic |
1352 | + # filtering on this launch index (or other selective actions) |
1353 | + @property |
1354 | + def launch_index(self) |
1355 | + |
1356 | + # the data sources' config_obj is a cloud-config formatted |
1357 | + # object that came to it from ways other than cloud-config |
1358 | + # because cloud-config content would be handled elsewhere |
1359 | + def get_config_obj(self) |
1360 | + |
1361 | + #returns a list of public ssh keys |
1362 | + def get_public_ssh_keys(self) |
1363 | + |
1364 | + # translates a device 'short' name into the actual physical device |
1365 | + # fully qualified name (or none if said physical device is not attached |
1366 | + # or does not exist) |
1367 | + def device_name_to_device(self, name) |
1368 | + |
1369 | + # gets the locale string this instance should be applying |
1370 | + # which typically used to adjust the instances locale settings files |
1371 | + def get_locale(self) |
1372 | + |
1373 | + @property |
1374 | + def availability_zone(self) |
1375 | + |
1376 | + # gets the instance id that was assigned to this instance by the |
1377 | + # cloud provider or when said instance id does not exist in the backing |
1378 | + # metadata this will return 'iid-datasource' |
1379 | + def get_instance_id(self) |
1380 | + |
1381 | + # gets the fully qualified domain name that this host should be using |
1382 | + # when configuring network or hostname related settings, typically |
1383 | + # assigned either by the cloud provider or the user creating the vm |
1384 | + def get_hostname(self, fqdn=False) |
1385 | + |
1386 | + def get_package_mirror_info(self) |
1387 | |
1388 | -.. vi: textwidth=78 |
1389 | +.. vi: textwidth=79 |
1390 | diff --git a/doc/rtd/topics/datasources/altcloud.rst b/doc/rtd/topics/datasources/altcloud.rst |
1391 | index eeb197f..9d7e3de 100644 |
1392 | --- a/doc/rtd/topics/datasources/altcloud.rst |
1393 | +++ b/doc/rtd/topics/datasources/altcloud.rst |
1394 | @@ -3,24 +3,25 @@ |
1395 | Alt Cloud |
1396 | ========= |
1397 | |
1398 | -The datasource altcloud will be used to pick up user data on `RHEVm`_ and `vSphere`_. |
1399 | +The datasource altcloud will be used to pick up user data on `RHEVm`_ and |
1400 | +`vSphere`_. |
1401 | |
1402 | RHEVm |
1403 | ----- |
1404 | |
1405 | For `RHEVm`_ v3.0 the userdata is injected into the VM using floppy |
1406 | -injection via the `RHEVm`_ dashboard "Custom Properties". |
1407 | +injection via the `RHEVm`_ dashboard "Custom Properties". |
1408 | |
1409 | The format of the Custom Properties entry must be: |
1410 | |
1411 | :: |
1412 | - |
1413 | + |
1414 | floppyinject=user-data.txt:<base64 encoded data> |
1415 | |
1416 | For example to pass a simple bash script: |
1417 | |
1418 | .. sourcecode:: sh |
1419 | - |
1420 | + |
1421 | % cat simple_script.bash |
1422 | #!/bin/bash |
1423 | echo "Hello Joe!" >> /tmp/JJV_Joe_out.txt |
1424 | @@ -38,7 +39,7 @@ set the "Custom Properties" when creating the RHEMv v3.0 VM to: |
1425 | **NOTE:** The prefix with file name must be: ``floppyinject=user-data.txt:`` |
1426 | |
1427 | It is also possible to launch a `RHEVm`_ v3.0 VM and pass optional user |
1428 | -data to it using the Delta Cloud. |
1429 | +data to it using the Delta Cloud. |
1430 | |
1431 | For more information on Delta Cloud see: http://deltacloud.apache.org |
1432 | |
1433 | @@ -46,12 +47,12 @@ vSphere |
1434 | ------- |
1435 | |
1436 | For VMWare's `vSphere`_ the userdata is injected into the VM as an ISO |
1437 | -via the cdrom. This can be done using the `vSphere`_ dashboard |
1438 | +via the cdrom. This can be done using the `vSphere`_ dashboard |
1439 | by connecting an ISO image to the CD/DVD drive. |
1440 | |
1441 | To pass this example script to cloud-init running in a `vSphere`_ VM |
1442 | set the CD/DVD drive when creating the vSphere VM to point to an |
1443 | -ISO on the data store. |
1444 | +ISO on the data store. |
1445 | |
1446 | **Note:** The ISO must contain the user data. |
1447 | |
1448 | @@ -61,13 +62,13 @@ Create the ISO |
1449 | ^^^^^^^^^^^^^^ |
1450 | |
1451 | .. sourcecode:: sh |
1452 | - |
1453 | + |
1454 | % mkdir my-iso |
1455 | |
1456 | NOTE: The file name on the ISO must be: ``user-data.txt`` |
1457 | |
1458 | .. sourcecode:: sh |
1459 | - |
1460 | + |
1461 | % cp simple_script.bash my-iso/user-data.txt |
1462 | % genisoimage -o user-data.iso -r my-iso |
1463 | |
1464 | @@ -75,7 +76,7 @@ Verify the ISO |
1465 | ^^^^^^^^^^^^^^ |
1466 | |
1467 | .. sourcecode:: sh |
1468 | - |
1469 | + |
1470 | % sudo mkdir /media/vsphere_iso |
1471 | % sudo mount -o loop user-data.iso /media/vsphere_iso |
1472 | % cat /media/vsphere_iso/user-data.txt |
1473 | @@ -84,7 +85,7 @@ Verify the ISO |
1474 | Then, launch the `vSphere`_ VM the ISO user-data.iso attached as a CDROM. |
1475 | |
1476 | It is also possible to launch a `vSphere`_ VM and pass optional user |
1477 | -data to it using the Delta Cloud. |
1478 | +data to it using the Delta Cloud. |
1479 | |
1480 | For more information on Delta Cloud see: http://deltacloud.apache.org |
1481 | |
1482 | diff --git a/doc/rtd/topics/datasources/azure.rst b/doc/rtd/topics/datasources/azure.rst |
1483 | index b41cddd..8328dfa 100644 |
1484 | --- a/doc/rtd/topics/datasources/azure.rst |
1485 | +++ b/doc/rtd/topics/datasources/azure.rst |
1486 | @@ -82,7 +82,8 @@ The settings that may be configured are: |
1487 | provided command to obtain metadata. |
1488 | * **apply_network_config**: Boolean set to True to use network configuration |
1489 | described by Azure's IMDS endpoint instead of fallback network config of |
1490 | - dhcp on eth0. Default is True. For Ubuntu 16.04 or earlier, default is False. |
1491 | + dhcp on eth0. Default is True. For Ubuntu 16.04 or earlier, default is |
1492 | + False. |
1493 | * **data_dir**: Path used to read metadata files and write crawled data. |
1494 | * **dhclient_lease_file**: The fallback lease file to source when looking for |
1495 | custom DHCP option 245 from Azure fabric. |
1496 | diff --git a/doc/rtd/topics/datasources/cloudstack.rst b/doc/rtd/topics/datasources/cloudstack.rst |
1497 | index a3101ed..95b9587 100644 |
1498 | --- a/doc/rtd/topics/datasources/cloudstack.rst |
1499 | +++ b/doc/rtd/topics/datasources/cloudstack.rst |
1500 | @@ -7,7 +7,7 @@ CloudStack |
1501 | sshkey thru the Virtual-Router. The datasource obtains the VR address via |
1502 | dhcp lease information given to the instance. |
1503 | For more details on meta-data and user-data, |
1504 | -refer the `CloudStack Administrator Guide`_. |
1505 | +refer the `CloudStack Administrator Guide`_. |
1506 | |
1507 | URLs to access user-data and meta-data from the Virtual Machine. Here 10.1.1.1 |
1508 | is the Virtual Router IP: |
1509 | diff --git a/doc/rtd/topics/datasources/configdrive.rst b/doc/rtd/topics/datasources/configdrive.rst |
1510 | index f1a488a..f4c5a34 100644 |
1511 | --- a/doc/rtd/topics/datasources/configdrive.rst |
1512 | +++ b/doc/rtd/topics/datasources/configdrive.rst |
1513 | @@ -64,7 +64,7 @@ The following criteria are required to as a config drive: |
1514 | :: |
1515 | |
1516 | openstack/ |
1517 | - - 2012-08-10/ or latest/ |
1518 | + - 2012-08-10/ or latest/ |
1519 | - meta_data.json |
1520 | - user_data (not mandatory) |
1521 | - content/ |
1522 | @@ -83,7 +83,7 @@ only) file in the following ways. |
1523 | |
1524 | :: |
1525 | |
1526 | - dsmode: |
1527 | + dsmode: |
1528 | values: local, net, pass |
1529 | default: pass |
1530 | |
1531 | @@ -97,10 +97,10 @@ The difference between 'local' and 'net' is that local will not require |
1532 | networking to be up before user-data actions (or boothooks) are run. |
1533 | |
1534 | :: |
1535 | - |
1536 | + |
1537 | instance-id: |
1538 | default: iid-dsconfigdrive |
1539 | - |
1540 | + |
1541 | This is utilized as the metadata's instance-id. It should generally |
1542 | be unique, as it is what is used to determine "is this a new instance". |
1543 | |
1544 | @@ -108,18 +108,18 @@ be unique, as it is what is used to determine "is this a new instance". |
1545 | |
1546 | public-keys: |
1547 | default: None |
1548 | - |
1549 | + |
1550 | If present, these keys will be used as the public keys for the |
1551 | instance. This value overrides the content in authorized_keys. |
1552 | |
1553 | Note: it is likely preferable to provide keys via user-data |
1554 | |
1555 | :: |
1556 | - |
1557 | + |
1558 | user-data: |
1559 | default: None |
1560 | - |
1561 | -This provides cloud-init user-data. See :ref:`examples <yaml_examples>` for |
1562 | + |
1563 | +This provides cloud-init user-data. See :ref:`examples <yaml_examples>` for |
1564 | what all can be present here. |
1565 | |
1566 | .. _OpenStack: http://www.openstack.org/ |
1567 | diff --git a/doc/rtd/topics/datasources/digitalocean.rst b/doc/rtd/topics/datasources/digitalocean.rst |
1568 | index 938ede8..88f1e5f 100644 |
1569 | --- a/doc/rtd/topics/datasources/digitalocean.rst |
1570 | +++ b/doc/rtd/topics/datasources/digitalocean.rst |
1571 | @@ -20,8 +20,10 @@ DigitalOcean's datasource can be configured as follows: |
1572 | retries: 3 |
1573 | timeout: 2 |
1574 | |
1575 | -- *retries*: Determines the number of times to attempt to connect to the metadata service |
1576 | -- *timeout*: Determines the timeout in seconds to wait for a response from the metadata service |
1577 | +- *retries*: Determines the number of times to attempt to connect to the |
1578 | + metadata service |
1579 | +- *timeout*: Determines the timeout in seconds to wait for a response from the |
1580 | + metadata service |
1581 | |
1582 | .. _DigitalOcean: http://digitalocean.com/ |
1583 | .. _metadata service: https://developers.digitalocean.com/metadata/ |
1584 | diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst |
1585 | index 76beca9..a90f377 100644 |
1586 | --- a/doc/rtd/topics/datasources/ec2.rst |
1587 | +++ b/doc/rtd/topics/datasources/ec2.rst |
1588 | @@ -13,7 +13,7 @@ instance metadata. |
1589 | Metadata is accessible via the following URL: |
1590 | |
1591 | :: |
1592 | - |
1593 | + |
1594 | GET http://169.254.169.254/2009-04-04/meta-data/ |
1595 | ami-id |
1596 | ami-launch-index |
1597 | @@ -34,19 +34,20 @@ Metadata is accessible via the following URL: |
1598 | Userdata is accessible via the following URL: |
1599 | |
1600 | :: |
1601 | - |
1602 | + |
1603 | GET http://169.254.169.254/2009-04-04/user-data |
1604 | 1234,fred,reboot,true | 4512,jimbo, | 173,,, |
1605 | |
1606 | Note that there are multiple versions of this data provided, cloud-init |
1607 | by default uses **2009-04-04** but newer versions can be supported with |
1608 | relative ease (newer versions have more data exposed, while maintaining |
1609 | -backward compatibility with the previous versions). |
1610 | +backward compatibility with the previous versions). |
1611 | |
1612 | -To see which versions are supported from your cloud provider use the following URL: |
1613 | +To see which versions are supported from your cloud provider use the following |
1614 | +URL: |
1615 | |
1616 | :: |
1617 | - |
1618 | + |
1619 | GET http://169.254.169.254/ |
1620 | 1.0 |
1621 | 2007-01-19 |
1622 | diff --git a/doc/rtd/topics/datasources/exoscale.rst b/doc/rtd/topics/datasources/exoscale.rst |
1623 | index 27aec9c..9074edc 100644 |
1624 | --- a/doc/rtd/topics/datasources/exoscale.rst |
1625 | +++ b/doc/rtd/topics/datasources/exoscale.rst |
1626 | @@ -26,8 +26,8 @@ In the password server case, the following rules apply in order to enable the |
1627 | "restore instance password" functionality: |
1628 | |
1629 | * If a password is returned by the password server, it is then marked "saved" |
1630 | - by the cloud-init datasource. Subsequent boots will skip setting the password |
1631 | - (the password server will return "saved_password"). |
1632 | + by the cloud-init datasource. Subsequent boots will skip setting the |
1633 | + password (the password server will return "saved_password"). |
1634 | * When the instance password is reset (via the Exoscale UI), the password |
1635 | server will return the non-empty password at next boot, therefore causing |
1636 | cloud-init to reset the instance's password. |
1637 | @@ -38,15 +38,15 @@ Configuration |
1638 | Users of this datasource are discouraged from changing the default settings |
1639 | unless instructed to by Exoscale support. |
1640 | |
1641 | -The following settings are available and can be set for the datasource in system |
1642 | -configuration (in `/etc/cloud/cloud.cfg.d/`). |
1643 | +The following settings are available and can be set for the datasource in |
1644 | +system configuration (in `/etc/cloud/cloud.cfg.d/`). |
1645 | |
1646 | The settings available are: |
1647 | |
1648 | * **metadata_url**: The URL for the metadata service (defaults to |
1649 | ``http://169.254.169.254``) |
1650 | - * **api_version**: The API version path on which to query the instance metadata |
1651 | - (defaults to ``1.0``) |
1652 | + * **api_version**: The API version path on which to query the instance |
1653 | + metadata (defaults to ``1.0``) |
1654 | * **password_server_port**: The port (on the metadata server) on which the |
1655 | password server listens (defaults to ``8080``). |
1656 | * **timeout**: the timeout value provided to urlopen for each individual http |
1657 | diff --git a/doc/rtd/topics/datasources/nocloud.rst b/doc/rtd/topics/datasources/nocloud.rst |
1658 | index 1c5cf96..bc96f7f 100644 |
1659 | --- a/doc/rtd/topics/datasources/nocloud.rst |
1660 | +++ b/doc/rtd/topics/datasources/nocloud.rst |
1661 | @@ -57,24 +57,24 @@ Given a disk ubuntu 12.04 cloud image in 'disk.img', you can create a |
1662 | sufficient disk by following the example below. |
1663 | |
1664 | :: |
1665 | - |
1666 | + |
1667 | ## create user-data and meta-data files that will be used |
1668 | ## to modify image on first boot |
1669 | $ { echo instance-id: iid-local01; echo local-hostname: cloudimg; } > meta-data |
1670 | - |
1671 | + |
1672 | $ printf "#cloud-config\npassword: passw0rd\nchpasswd: { expire: False }\nssh_pwauth: True\n" > user-data |
1673 | - |
1674 | + |
1675 | ## create a disk to attach with some user-data and meta-data |
1676 | $ genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data |
1677 | - |
1678 | + |
1679 | ## alternatively, create a vfat filesystem with same files |
1680 | ## $ truncate --size 2M seed.img |
1681 | ## $ mkfs.vfat -n cidata seed.img |
1682 | ## $ mcopy -oi seed.img user-data meta-data :: |
1683 | - |
1684 | + |
1685 | ## create a new qcow image to boot, backed by your original image |
1686 | $ qemu-img create -f qcow2 -b disk.img boot-disk.img |
1687 | - |
1688 | + |
1689 | ## boot the image and login as 'ubuntu' with password 'passw0rd' |
1690 | ## note, passw0rd was set as password through the user-data above, |
1691 | ## there is no password set on these images. |
1692 | @@ -88,12 +88,12 @@ to determine if this is "first boot". So if you are making updates to |
1693 | user-data you will also have to change that, or start the disk fresh. |
1694 | |
1695 | Also, you can inject an ``/etc/network/interfaces`` file by providing the |
1696 | -content for that file in the ``network-interfaces`` field of metadata. |
1697 | +content for that file in the ``network-interfaces`` field of metadata. |
1698 | |
1699 | Example metadata: |
1700 | |
1701 | :: |
1702 | - |
1703 | + |
1704 | instance-id: iid-abcdefg |
1705 | network-interfaces: | |
1706 | iface eth0 inet static |
1707 | diff --git a/doc/rtd/topics/datasources/opennebula.rst b/doc/rtd/topics/datasources/opennebula.rst |
1708 | index 7c0367c..8e7c255 100644 |
1709 | --- a/doc/rtd/topics/datasources/opennebula.rst |
1710 | +++ b/doc/rtd/topics/datasources/opennebula.rst |
1711 | @@ -21,7 +21,7 @@ Datasource configuration |
1712 | Datasource accepts following configuration options. |
1713 | |
1714 | :: |
1715 | - |
1716 | + |
1717 | dsmode: |
1718 | values: local, net, disabled |
1719 | default: net |
1720 | @@ -30,7 +30,7 @@ Tells if this datasource will be processed in 'local' (pre-networking) or |
1721 | 'net' (post-networking) stage or even completely 'disabled'. |
1722 | |
1723 | :: |
1724 | - |
1725 | + |
1726 | parseuser: |
1727 | default: nobody |
1728 | |
1729 | @@ -46,7 +46,7 @@ The following criteria are required: |
1730 | or have a *filesystem* label of **CONTEXT** or **CDROM** |
1731 | 2. Must contain file *context.sh* with contextualization variables. |
1732 | File is generated by OpenNebula, it has a KEY='VALUE' format and |
1733 | - can be easily read by bash |
1734 | + can be easily read by bash |
1735 | |
1736 | Contextualization variables |
1737 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1738 | @@ -57,7 +57,7 @@ the OpenNebula documentation. Where multiple similar variables are |
1739 | specified, only first found is taken. |
1740 | |
1741 | :: |
1742 | - |
1743 | + |
1744 | DSMODE |
1745 | |
1746 | Datasource mode configuration override. Values: local, net, disabled. |
1747 | @@ -75,30 +75,30 @@ Datasource mode configuration override. Values: local, net, disabled. |
1748 | Static `network configuration`_. |
1749 | |
1750 | :: |
1751 | - |
1752 | + |
1753 | HOSTNAME |
1754 | |
1755 | Instance hostname. |
1756 | |
1757 | :: |
1758 | - |
1759 | + |
1760 | PUBLIC_IP |
1761 | IP_PUBLIC |
1762 | ETH0_IP |
1763 | |
1764 | If no hostname has been specified, cloud-init will try to create hostname |
1765 | -from instance's IP address in 'local' dsmode. In 'net' dsmode, cloud-init |
1766 | +from instance's IP address in 'local' dsmode. In 'net' dsmode, cloud-init |
1767 | tries to resolve one of its IP addresses to get hostname. |
1768 | |
1769 | :: |
1770 | - |
1771 | + |
1772 | SSH_KEY |
1773 | SSH_PUBLIC_KEY |
1774 | |
1775 | One or multiple SSH keys (separated by newlines) can be specified. |
1776 | |
1777 | :: |
1778 | - |
1779 | + |
1780 | USER_DATA |
1781 | USERDATA |
1782 | |
1783 | @@ -111,7 +111,7 @@ This example cloud-init configuration (*cloud.cfg*) enables |
1784 | OpenNebula datasource only in 'net' mode. |
1785 | |
1786 | :: |
1787 | - |
1788 | + |
1789 | disable_ec2_metadata: True |
1790 | datasource_list: ['OpenNebula'] |
1791 | datasource: |
1792 | @@ -123,17 +123,17 @@ Example VM's context section |
1793 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1794 | |
1795 | :: |
1796 | - |
1797 | + |
1798 | CONTEXT=[ |
1799 | PUBLIC_IP="$NIC[IP]", |
1800 | - SSH_KEY="$USER[SSH_KEY] |
1801 | - $USER[SSH_KEY1] |
1802 | + SSH_KEY="$USER[SSH_KEY] |
1803 | + $USER[SSH_KEY1] |
1804 | $USER[SSH_KEY2] ", |
1805 | USER_DATA="#cloud-config |
1806 | # see https://help.ubuntu.com/community/CloudInit |
1807 | - |
1808 | + |
1809 | packages: [] |
1810 | - |
1811 | + |
1812 | mounts: |
1813 | - [vdc,none,swap,sw,0,0] |
1814 | runcmd: |
1815 | diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst |
1816 | index 421da08..8ce2a53 100644 |
1817 | --- a/doc/rtd/topics/datasources/openstack.rst |
1818 | +++ b/doc/rtd/topics/datasources/openstack.rst |
1819 | @@ -78,6 +78,7 @@ upgrade packages and install ``htop`` on all instances: |
1820 | {"cloud-init": "#cloud-config\npackage_upgrade: True\npackages:\n - htop"} |
1821 | |
1822 | For more general information about how cloud-init handles vendor data, |
1823 | -including how it can be disabled by users on instances, see :doc:`/topics/vendordata`. |
1824 | +including how it can be disabled by users on instances, see |
1825 | +:doc:`/topics/vendordata`. |
1826 | |
1827 | .. vi: textwidth=78 |
1828 | diff --git a/doc/rtd/topics/datasources/smartos.rst b/doc/rtd/topics/datasources/smartos.rst |
1829 | index cb9a128..be11dfb 100644 |
1830 | --- a/doc/rtd/topics/datasources/smartos.rst |
1831 | +++ b/doc/rtd/topics/datasources/smartos.rst |
1832 | @@ -15,7 +15,8 @@ second serial console. On Linux, this is /dev/ttyS1. The data is a provided |
1833 | via a simple protocol: something queries for the data, the console responds |
1834 | responds with the status and if "SUCCESS" returns until a single ".\n". |
1835 | |
1836 | -New versions of the SmartOS tooling will include support for base64 encoded data. |
1837 | +New versions of the SmartOS tooling will include support for base64 encoded |
1838 | +data. |
1839 | |
1840 | Meta-data channels |
1841 | ------------------ |
1842 | @@ -27,7 +28,7 @@ channels of SmartOS. |
1843 | |
1844 | - per the spec, user-data is for consumption by the end-user, not |
1845 | provisioning tools |
1846 | - - cloud-init entirely ignores this channel other than writting it to disk |
1847 | + - cloud-init entirely ignores this channel other than writing it to disk |
1848 | - removal of the meta-data key means that /var/db/user-data gets removed |
1849 | - a backup of previous meta-data is maintained as |
1850 | /var/db/user-data.<timestamp>. <timestamp> is the epoch time when |
1851 | @@ -42,8 +43,9 @@ channels of SmartOS. |
1852 | - <timestamp> is the epoch time when cloud-init ran. |
1853 | - when the 'user-script' meta-data key goes missing, the user-script is |
1854 | removed from the file system, although a backup is maintained. |
1855 | - - if the script is not shebanged (i.e. starts with #!<executable>), then |
1856 | - or is not an executable, cloud-init will add a shebang of "#!/bin/bash" |
1857 | + - if the script does not start with a shebang (i.e. starts with |
1858 | + #!<executable>), then or is not an executable, cloud-init will add a |
1859 | + shebang of "#!/bin/bash" |
1860 | |
1861 | * cloud-init:user-data is treated like on other Clouds. |
1862 | |
1863 | @@ -133,7 +135,7 @@ or not to base64 decode something: |
1864 | * base64_all: Except for excluded keys, attempt to base64 decode |
1865 | the values. If the value fails to decode properly, it will be |
1866 | returned in its text |
1867 | - * base64_keys: A comma deliminated list of which keys are base64 encoded. |
1868 | + * base64_keys: A comma delimited list of which keys are base64 encoded. |
1869 | * b64-<key>: |
1870 | for any key, if there exists an entry in the metadata for 'b64-<key>' |
1871 | Then 'b64-<key>' is expected to be a plaintext boolean indicating whether |
1872 | diff --git a/doc/rtd/topics/debugging.rst b/doc/rtd/topics/debugging.rst |
1873 | index e13d915..afcf267 100644 |
1874 | --- a/doc/rtd/topics/debugging.rst |
1875 | +++ b/doc/rtd/topics/debugging.rst |
1876 | @@ -68,18 +68,18 @@ subcommands default to reading /var/log/cloud-init.log. |
1877 | 00.00100s (modules-final/config-rightscale_userdata) |
1878 | ... |
1879 | |
1880 | -* ``analyze boot`` Make subprocess calls to the kernel in order to get relevant |
1881 | +* ``analyze boot`` Make subprocess calls to the kernel in order to get relevant |
1882 | pre-cloud-init timestamps, such as the kernel start, kernel finish boot, and cloud-init start. |
1883 | |
1884 | .. code-block:: shell-session |
1885 | |
1886 | - $ cloud-init analyze boot |
1887 | + $ cloud-init analyze boot |
1888 | -- Most Recent Boot Record -- |
1889 | - Kernel Started at: 2019-06-13 15:59:55.809385 |
1890 | - Kernel ended boot at: 2019-06-13 16:00:00.944740 |
1891 | - Kernel time to boot (seconds): 5.135355 |
1892 | - Cloud-init start: 2019-06-13 16:00:05.738396 |
1893 | - Time between Kernel boot and Cloud-init start (seconds): 4.793656 |
1894 | + Kernel Started at: 2019-06-13 15:59:55.809385 |
1895 | + Kernel ended boot at: 2019-06-13 16:00:00.944740 |
1896 | + Kernel time to boot (seconds): 5.135355 |
1897 | + Cloud-init start: 2019-06-13 16:00:05.738396 |
1898 | + Time between Kernel boot and Cloud-init start (seconds): 4.793656 |
1899 | |
1900 | |
1901 | Analyze quickstart - LXC |
1902 | diff --git a/doc/rtd/topics/dir_layout.rst b/doc/rtd/topics/dir_layout.rst |
1903 | index 7a6265e..ebd63ae 100644 |
1904 | --- a/doc/rtd/topics/dir_layout.rst |
1905 | +++ b/doc/rtd/topics/dir_layout.rst |
1906 | @@ -2,11 +2,12 @@ |
1907 | Directory layout |
1908 | **************** |
1909 | |
1910 | -Cloudinits's directory structure is somewhat different from a regular application:: |
1911 | +Cloud-init's directory structure is somewhat different from a regular |
1912 | +application:: |
1913 | |
1914 | /var/lib/cloud/ |
1915 | - data/ |
1916 | - - instance-id |
1917 | + - instance-id |
1918 | - previous-instance-id |
1919 | - datasource |
1920 | - previous-datasource |
1921 | @@ -35,38 +36,41 @@ Cloudinits's directory structure is somewhat different from a regular applicatio |
1922 | |
1923 | The main directory containing the cloud-init specific subdirectories. |
1924 | It is typically located at ``/var/lib`` but there are certain configuration |
1925 | - scenarios where this can be altered. |
1926 | + scenarios where this can be altered. |
1927 | |
1928 | TBD, describe this overriding more. |
1929 | |
1930 | ``data/`` |
1931 | |
1932 | - Contains information related to instance ids, datasources and hostnames of the previous |
1933 | - and current instance if they are different. These can be examined as needed to |
1934 | - determine any information related to a previous boot (if applicable). |
1935 | + Contains information related to instance ids, datasources and hostnames of |
1936 | + the previous and current instance if they are different. These can be |
1937 | + examined as needed to determine any information related to a previous boot |
1938 | + (if applicable). |
1939 | |
1940 | ``handlers/`` |
1941 | |
1942 | - Custom ``part-handlers`` code is written out here. Files that end up here are written |
1943 | - out with in the scheme of ``part-handler-XYZ`` where ``XYZ`` is the handler number (the |
1944 | - first handler found starts at 0). |
1945 | + Custom ``part-handlers`` code is written out here. Files that end up here are |
1946 | + written out with in the scheme of ``part-handler-XYZ`` where ``XYZ`` is the |
1947 | + handler number (the first handler found starts at 0). |
1948 | |
1949 | |
1950 | ``instance`` |
1951 | |
1952 | - A symlink to the current ``instances/`` subdirectory that points to the currently |
1953 | - active instance (which is active is dependent on the datasource loaded). |
1954 | + A symlink to the current ``instances/`` subdirectory that points to the |
1955 | + currently active instance (which is active is dependent on the datasource |
1956 | + loaded). |
1957 | |
1958 | ``instances/`` |
1959 | |
1960 | - All instances that were created using this image end up with instance identifier |
1961 | - subdirectories (and corresponding data for each instance). The currently active |
1962 | - instance will be symlinked the ``instance`` symlink file defined previously. |
1963 | + All instances that were created using this image end up with instance |
1964 | + identifier subdirectories (and corresponding data for each instance). The |
1965 | + currently active instance will be symlinked the ``instance`` symlink file |
1966 | + defined previously. |
1967 | |
1968 | ``scripts/`` |
1969 | |
1970 | - Scripts that are downloaded/created by the corresponding ``part-handler`` will end up |
1971 | - in one of these subdirectories. |
1972 | + Scripts that are downloaded/created by the corresponding ``part-handler`` |
1973 | + will end up in one of these subdirectories. |
1974 | |
1975 | ``seed/`` |
1976 | |
1977 | @@ -77,6 +81,7 @@ Cloudinits's directory structure is somewhat different from a regular applicatio |
1978 | Cloud-init has a concept of a module semaphore, which basically consists |
1979 | of the module name and its frequency. These files are used to ensure a module |
1980 | is only ran `per-once`, `per-instance`, `per-always`. This folder contains |
1981 | - semaphore `files` which are only supposed to run `per-once` (not tied to the instance id). |
1982 | + semaphore `files` which are only supposed to run `per-once` (not tied to the |
1983 | + instance id). |
1984 | |
1985 | .. vi: textwidth=78 |
1986 | diff --git a/doc/rtd/topics/docs.rst b/doc/rtd/topics/docs.rst |
1987 | new file mode 100644 |
1988 | index 0000000..1b15377 |
1989 | --- /dev/null |
1990 | +++ b/doc/rtd/topics/docs.rst |
1991 | @@ -0,0 +1,84 @@ |
1992 | +.. _docs: |
1993 | + |
1994 | +Docs |
1995 | +**** |
1996 | + |
1997 | +These docs are hosted on Read the Docs. The following will explain how to |
1998 | +contribute to and build these docs locally. |
1999 | + |
2000 | +The documentation is primarily written in reStructuredText. |
2001 | + |
2002 | + |
2003 | +Building |
2004 | +======== |
2005 | + |
2006 | +There is a makefile target to build the documentation for you: |
2007 | + |
2008 | +.. code-block:: shell-session |
2009 | + |
2010 | + $ tox -e doc |
2011 | + |
2012 | +This will do two things: |
2013 | + |
2014 | +- Build the documentation using sphinx |
2015 | +- Run doc8 against the documentation source code |
2016 | + |
2017 | +Once build the HTML files will be viewable in ``doc/rtd_html``. Use your |
2018 | +web browser to open ``index.html`` to view and navigate the site. |
2019 | + |
2020 | +Style Guide |
2021 | +=========== |
2022 | + |
2023 | +Headings |
2024 | +-------- |
2025 | +The headings used across the documentation use the following hierarchy: |
2026 | + |
2027 | +- ``*****``: used once atop of a new page |
2028 | +- ``=====``: each sections on the page |
2029 | +- ``-----``: subsections |
2030 | +- ``^^^^^``: sub-subsections |
2031 | +- ``"""""``: paragraphs |
2032 | + |
2033 | +The top level header ``######`` is reserved for the first page. |
2034 | + |
2035 | +If under and overline are used, their length must be identical. The length of |
2036 | +the underline must be at least as long as the title itself |
2037 | + |
2038 | +Line Length |
2039 | +----------- |
2040 | +Please keep the line lengths to a maximum of **79** characters. This ensures |
2041 | +that the pages and tables do not get too wide that side scrolling is required. |
2042 | + |
2043 | +Header |
2044 | +------ |
2045 | +Adding a link at the top of the page allows for the page to be referenced by |
2046 | +other pages. For example for the FAQ page this would be: |
2047 | + |
2048 | +.. code-block:: rst |
2049 | + |
2050 | + .. _faq: |
2051 | + |
2052 | +Footer |
2053 | +------ |
2054 | +The footer should include the textwidth |
2055 | + |
2056 | +.. code-block:: rst |
2057 | + |
2058 | + .. vi: textwidth=79 |
2059 | + |
2060 | +Vertical Whitespace |
2061 | +------------------- |
2062 | +One newline between each section helps ensure readability of the documentation |
2063 | +source code. |
2064 | + |
2065 | +Common Words |
2066 | +------------ |
2067 | +There are some common words that should follow specific usage: |
2068 | + |
2069 | +- ``cloud-init``: always lower case with a hyphen, unless starting a sentence |
2070 | + in which case only the 'C' is capitalized (e.g. ``Cloud-init``). |
2071 | +- ``metadata``: one word |
2072 | +- ``user data``: two words, not to be combined |
2073 | +- ``vendor data``: like user data, it is two words |
2074 | + |
2075 | +.. vi: textwidth=79 |
2076 | diff --git a/doc/rtd/topics/examples.rst b/doc/rtd/topics/examples.rst |
2077 | index c30d226..62b8ee4 100644 |
2078 | --- a/doc/rtd/topics/examples.rst |
2079 | +++ b/doc/rtd/topics/examples.rst |
2080 | @@ -134,7 +134,7 @@ Configure instances ssh-keys |
2081 | .. literalinclude:: ../../examples/cloud-config-ssh-keys.txt |
2082 | :language: yaml |
2083 | :linenos: |
2084 | - |
2085 | + |
2086 | Additional apt configuration |
2087 | ============================ |
2088 | |
2089 | diff --git a/doc/rtd/topics/faq.rst b/doc/rtd/topics/faq.rst |
2090 | new file mode 100644 |
2091 | index 0000000..16e19c2 |
2092 | --- /dev/null |
2093 | +++ b/doc/rtd/topics/faq.rst |
2094 | @@ -0,0 +1,43 @@ |
2095 | +.. _faq: |
2096 | + |
2097 | +FAQ |
2098 | +*** |
2099 | + |
2100 | +Getting help |
2101 | +============ |
2102 | + |
2103 | +Having trouble? We would like to help! |
2104 | + |
2105 | +- Use the search bar at the upper left to search these docs |
2106 | +- Ask a question in the ``#cloud-init`` IRC channel on Freenode |
2107 | +- Join and ask questions on the `cloud-init mailing list <https://launchpad.net/~cloud-init>`_ |
2108 | +- Find a bug? `Report bugs on Launchpad <https://bugs.launchpad.net/cloud-init/+filebug>`_ |
2109 | + |
2110 | + |
2111 | +Media |
2112 | +===== |
2113 | + |
2114 | +Below are some videos, blog posts, and white papers about cloud-init from a |
2115 | +variety of sources. |
2116 | + |
2117 | +- `Cloud Instance Initialization with cloud-init (Whitepaper)`_ |
2118 | +- `cloud-init Summit 2018`_ |
2119 | +- `cloud-init - The cross-cloud Magic Sauce (PDF)`_ |
2120 | +- `cloud-init Summit 2017`_ |
2121 | +- `cloud-init - Building clouds one Linux box at a time (Video)`_ |
2122 | +- `cloud-init - Building clouds one Linux box at a time (PDF)`_ |
2123 | +- `Metadata and cloud-init`_ |
2124 | +- `The beauty of cloud-init`_ |
2125 | +- `Introduction to cloud-init`_ |
2126 | + |
2127 | +.. _Cloud Instance Initialization with cloud-init (Whitepaper): https://ubuntu.com/blog/cloud-instance-initialisation-with-cloud-init |
2128 | +.. _cloud-init Summit 2018: https://powersj.io/post/cloud-init-summit18/ |
2129 | +.. _cloud-init - The cross-cloud Magic Sauce (PDF): https://events.linuxfoundation.org/wp-content/uploads/2017/12/cloud-init-The-cross-cloud-Magic-Sauce-Scott-Moser-Chad-Smith-Canonical.pdf |
2130 | +.. _cloud-init Summit 2017: https://powersj.io/post/cloud-init-summit17/ |
2131 | +.. _cloud-init - Building clouds one Linux box at a time (Video): https://www.youtube.com/watch?v=1joQfUZQcPg |
2132 | +.. _cloud-init - Building clouds one Linux box at a time (PDF): https://annex.debconf.org/debconf-share/debconf17/slides/164-cloud-init_Building_clouds_one_Linux_box_at_a_time.pdf |
2133 | +.. _Metadata and cloud-init: https://www.youtube.com/watch?v=RHVhIWifVqU |
2134 | +.. _The beauty of cloud-init: http://brandon.fuller.name/archives/2011/05/02/06.40.57/ |
2135 | +.. _Introduction to cloud-init: http://www.youtube.com/watch?v=-zL3BdbKyGY |
2136 | + |
2137 | +.. vi: textwidth=79 |
2138 | diff --git a/doc/rtd/topics/format.rst b/doc/rtd/topics/format.rst |
2139 | index 74d1fee..7605040 100644 |
2140 | --- a/doc/rtd/topics/format.rst |
2141 | +++ b/doc/rtd/topics/format.rst |
2142 | @@ -4,22 +4,24 @@ |
2143 | User-Data Formats |
2144 | ***************** |
2145 | |
2146 | -User data that will be acted upon by cloud-init must be in one of the following types. |
2147 | +User data that will be acted upon by cloud-init must be in one of the following |
2148 | +types. |
2149 | |
2150 | Gzip Compressed Content |
2151 | ======================= |
2152 | |
2153 | Content found to be gzip compressed will be uncompressed. |
2154 | -The uncompressed data will then be used as if it were not compressed. |
2155 | +The uncompressed data will then be used as if it were not compressed. |
2156 | This is typically useful because user-data is limited to ~16384 [#]_ bytes. |
2157 | |
2158 | Mime Multi Part Archive |
2159 | ======================= |
2160 | |
2161 | -This list of rules is applied to each part of this multi-part file. |
2162 | +This list of rules is applied to each part of this multi-part file. |
2163 | Using a mime-multi part file, the user can specify more than one type of data. |
2164 | |
2165 | -For example, both a user data script and a cloud-config type could be specified. |
2166 | +For example, both a user data script and a cloud-config type could be |
2167 | +specified. |
2168 | |
2169 | Supported content-types: |
2170 | |
2171 | @@ -66,7 +68,8 @@ User-Data Script |
2172 | |
2173 | Typically used by those who just want to execute a shell script. |
2174 | |
2175 | -Begins with: ``#!`` or ``Content-Type: text/x-shellscript`` when using a MIME archive. |
2176 | +Begins with: ``#!`` or ``Content-Type: text/x-shellscript`` when using a MIME |
2177 | +archive. |
2178 | |
2179 | .. note:: |
2180 | New in cloud-init v. 18.4: User-data scripts can also render cloud instance |
2181 | @@ -83,25 +86,27 @@ Example |
2182 | #!/bin/sh |
2183 | echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt |
2184 | |
2185 | - $ euca-run-instances --key mykey --user-data-file myscript.sh ami-a07d95c9 |
2186 | + $ euca-run-instances --key mykey --user-data-file myscript.sh ami-a07d95c9 |
2187 | |
2188 | Include File |
2189 | ============ |
2190 | |
2191 | This content is a ``include`` file. |
2192 | |
2193 | -The file contains a list of urls, one per line. |
2194 | -Each of the URLs will be read, and their content will be passed through this same set of rules. |
2195 | -Ie, the content read from the URL can be gzipped, mime-multi-part, or plain text. |
2196 | -If an error occurs reading a file the remaining files will not be read. |
2197 | +The file contains a list of urls, one per line. Each of the URLs will be read, |
2198 | +and their content will be passed through this same set of rules. Ie, the |
2199 | +content read from the URL can be gzipped, mime-multi-part, or plain text. If |
2200 | +an error occurs reading a file the remaining files will not be read. |
2201 | |
2202 | -Begins with: ``#include`` or ``Content-Type: text/x-include-url`` when using a MIME archive. |
2203 | +Begins with: ``#include`` or ``Content-Type: text/x-include-url`` when using |
2204 | +a MIME archive. |
2205 | |
2206 | Cloud Config Data |
2207 | ================= |
2208 | |
2209 | -Cloud-config is the simplest way to accomplish some things |
2210 | -via user-data. Using cloud-config syntax, the user can specify certain things in a human friendly format. |
2211 | +Cloud-config is the simplest way to accomplish some things via user-data. Using |
2212 | +cloud-config syntax, the user can specify certain things in a human friendly |
2213 | +format. |
2214 | |
2215 | These things include: |
2216 | |
2217 | @@ -114,9 +119,11 @@ These things include: |
2218 | .. note:: |
2219 | This file must be valid yaml syntax. |
2220 | |
2221 | -See the :ref:`yaml_examples` section for a commented set of examples of supported cloud config formats. |
2222 | +See the :ref:`yaml_examples` section for a commented set of examples of |
2223 | +supported cloud config formats. |
2224 | |
2225 | -Begins with: ``#cloud-config`` or ``Content-Type: text/cloud-config`` when using a MIME archive. |
2226 | +Begins with: ``#cloud-config`` or ``Content-Type: text/cloud-config`` when |
2227 | +using a MIME archive. |
2228 | |
2229 | .. note:: |
2230 | New in cloud-init v. 18.4: Cloud config dta can also render cloud instance |
2231 | @@ -126,25 +133,41 @@ Begins with: ``#cloud-config`` or ``Content-Type: text/cloud-config`` when using |
2232 | Upstart Job |
2233 | =========== |
2234 | |
2235 | -Content is placed into a file in ``/etc/init``, and will be consumed by upstart as any other upstart job. |
2236 | +Content is placed into a file in ``/etc/init``, and will be consumed by upstart |
2237 | +as any other upstart job. |
2238 | |
2239 | -Begins with: ``#upstart-job`` or ``Content-Type: text/upstart-job`` when using a MIME archive. |
2240 | +Begins with: ``#upstart-job`` or ``Content-Type: text/upstart-job`` when using |
2241 | +a MIME archive. |
2242 | |
2243 | Cloud Boothook |
2244 | ============== |
2245 | |
2246 | -This content is ``boothook`` data. It is stored in a file under ``/var/lib/cloud`` and then executed immediately. |
2247 | -This is the earliest ``hook`` available. Note, that there is no mechanism provided for running only once. The boothook must take care of this itself. |
2248 | -It is provided with the instance id in the environment variable ``INSTANCE_ID``. This could be made use of to provide a 'once-per-instance' type of functionality. |
2249 | +This content is ``boothook`` data. It is stored in a file under |
2250 | +``/var/lib/cloud`` and then executed immediately. This is the earliest ``hook`` |
2251 | +available. Note, that there is no mechanism provided for running only once. The |
2252 | +boothook must take care of this itself. |
2253 | |
2254 | -Begins with: ``#cloud-boothook`` or ``Content-Type: text/cloud-boothook`` when using a MIME archive. |
2255 | +It is provided with the instance id in the environment variable |
2256 | +``INSTANCE_ID``. This could be made use of to provide a 'once-per-instance' |
2257 | +type of functionality. |
2258 | + |
2259 | +Begins with: ``#cloud-boothook`` or ``Content-Type: text/cloud-boothook`` when |
2260 | +using a MIME archive. |
2261 | |
2262 | Part Handler |
2263 | ============ |
2264 | |
2265 | -This is a ``part-handler``: It contains custom code for either supporting new mime-types in multi-part user data, or overriding the existing handlers for supported mime-types. It will be written to a file in ``/var/lib/cloud/data`` based on its filename (which is generated). |
2266 | -This must be python code that contains a ``list_types`` function and a ``handle_part`` function. |
2267 | -Once the section is read the ``list_types`` method will be called. It must return a list of mime-types that this part-handler handles. Because mime parts are processed in order, a ``part-handler`` part must precede any parts with mime-types it is expected to handle in the same user data. |
2268 | +This is a ``part-handler``: It contains custom code for either supporting new |
2269 | +mime-types in multi-part user data, or overriding the existing handlers for |
2270 | +supported mime-types. It will be written to a file in ``/var/lib/cloud/data`` |
2271 | +based on its filename (which is generated). |
2272 | + |
2273 | +This must be python code that contains a ``list_types`` function and a |
2274 | +``handle_part`` function. Once the section is read the ``list_types`` method |
2275 | +will be called. It must return a list of mime-types that this part-handler |
2276 | +handles. Because mime parts are processed in order, a ``part-handler`` part |
2277 | +must precede any parts with mime-types it is expected to handle in the same |
2278 | +user data. |
2279 | |
2280 | The ``handle_part`` function must be defined like: |
2281 | |
2282 | @@ -156,11 +179,13 @@ The ``handle_part`` function must be defined like: |
2283 | # filename = the filename of the part (or a generated filename if none is present in mime data) |
2284 | # payload = the parts' content |
2285 | |
2286 | -Cloud-init will then call the ``handle_part`` function once before it handles any parts, once per part received, and once after all parts have been handled. |
2287 | -The ``'__begin__'`` and ``'__end__'`` sentinels allow the part handler to do initialization or teardown before or after |
2288 | -receiving any parts. |
2289 | +Cloud-init will then call the ``handle_part`` function once before it handles |
2290 | +any parts, once per part received, and once after all parts have been handled. |
2291 | +The ``'__begin__'`` and ``'__end__'`` sentinels allow the part handler to do |
2292 | +initialization or teardown before or after receiving any parts. |
2293 | |
2294 | -Begins with: ``#part-handler`` or ``Content-Type: text/part-handler`` when using a MIME archive. |
2295 | +Begins with: ``#part-handler`` or ``Content-Type: text/part-handler`` when |
2296 | +using a MIME archive. |
2297 | |
2298 | Example |
2299 | ------- |
2300 | diff --git a/doc/rtd/topics/merging.rst b/doc/rtd/topics/merging.rst |
2301 | index 5f7ca18..2b5e5da 100644 |
2302 | --- a/doc/rtd/topics/merging.rst |
2303 | +++ b/doc/rtd/topics/merging.rst |
2304 | @@ -68,8 +68,10 @@ Cloud-init provides merging for the following built-in types: |
2305 | The ``Dict`` merger has the following options which control what is done with |
2306 | values contained within the config. |
2307 | |
2308 | -- ``allow_delete``: Existing values not present in the new value can be deleted, defaults to False |
2309 | -- ``no_replace``: Do not replace an existing value if one is already present, enabled by default. |
2310 | +- ``allow_delete``: Existing values not present in the new value can be |
2311 | + deleted, defaults to False |
2312 | +- ``no_replace``: Do not replace an existing value if one is already present, |
2313 | + enabled by default. |
2314 | - ``replace``: Overwrite existing values with new ones. |
2315 | |
2316 | The ``List`` merger has the following options which control what is done with |
2317 | @@ -77,7 +79,8 @@ the values contained within the config. |
2318 | |
2319 | - ``append``: Add new value to the end of the list, defaults to False. |
2320 | - ``prepend``: Add new values to the start of the list, defaults to False. |
2321 | -- ``no_replace``: Do not replace an existing value if one is already present, enabled by default. |
2322 | +- ``no_replace``: Do not replace an existing value if one is already present, |
2323 | + enabled by default. |
2324 | - ``replace``: Overwrite existing values with new ones. |
2325 | |
2326 | The ``Str`` merger has the following options which control what is done with |
2327 | @@ -88,10 +91,13 @@ the values contained within the config. |
2328 | Common options for all merge types which control how recursive merging is |
2329 | done on other types. |
2330 | |
2331 | -- ``recurse_dict``: If True merge the new values of the dictionary, defaults to True. |
2332 | -- ``recurse_list``: If True merge the new values of the list, defaults to False. |
2333 | +- ``recurse_dict``: If True merge the new values of the dictionary, defaults to |
2334 | + True. |
2335 | +- ``recurse_list``: If True merge the new values of the list, defaults to |
2336 | + False. |
2337 | - ``recurse_array``: Alias for ``recurse_list``. |
2338 | -- ``recurse_str``: If True merge the new values of the string, defaults to False. |
2339 | +- ``recurse_str``: If True merge the new values of the string, defaults to |
2340 | + False. |
2341 | |
2342 | |
2343 | Customizability |
2344 | diff --git a/doc/rtd/topics/moreinfo.rst b/doc/rtd/topics/moreinfo.rst |
2345 | deleted file mode 100644 |
2346 | index 9c3b7fb..0000000 |
2347 | --- a/doc/rtd/topics/moreinfo.rst |
2348 | +++ /dev/null |
2349 | @@ -1,13 +0,0 @@ |
2350 | -**************** |
2351 | -More information |
2352 | -**************** |
2353 | - |
2354 | -Useful external references |
2355 | -========================== |
2356 | - |
2357 | -- `The beauty of cloudinit`_ |
2358 | -- `Introduction to cloud-init`_ (video) |
2359 | - |
2360 | -.. _Introduction to cloud-init: http://www.youtube.com/watch?v=-zL3BdbKyGY |
2361 | -.. _The beauty of cloudinit: http://brandon.fuller.name/archives/2011/05/02/06.40.57/ |
2362 | -.. vi: textwidth=78 |
2363 | diff --git a/doc/rtd/topics/network-config-format-v2.rst b/doc/rtd/topics/network-config-format-v2.rst |
2364 | index 50f5fa6..7f85755 100644 |
2365 | --- a/doc/rtd/topics/network-config-format-v2.rst |
2366 | +++ b/doc/rtd/topics/network-config-format-v2.rst |
2367 | @@ -54,11 +54,11 @@ Physical devices |
2368 | |
2369 | : (Examples: ethernet, wifi) These can dynamically come and go between |
2370 | reboots and even during runtime (hotplugging). In the generic case, they |
2371 | - can be selected by ``match:`` rules on desired properties, such as name/name |
2372 | - pattern, MAC address, driver, or device paths. In general these will match |
2373 | - any number of devices (unless they refer to properties which are unique |
2374 | - such as the full path or MAC address), so without further knowledge about |
2375 | - the hardware these will always be considered as a group. |
2376 | + can be selected by ``match:`` rules on desired properties, such as |
2377 | + name/name pattern, MAC address, driver, or device paths. In general these |
2378 | + will match any number of devices (unless they refer to properties which are |
2379 | + unique such as the full path or MAC address), so without further knowledge |
2380 | + about the hardware these will always be considered as a group. |
2381 | |
2382 | It is valid to specify no match rules at all, in which case the ID field is |
2383 | simply the interface name to be matched. This is mostly useful if you want |
2384 | @@ -228,8 +228,8 @@ Example: :: |
2385 | |
2386 | **parameters**: *<(mapping)>* |
2387 | |
2388 | -Customization parameters for special bonding options. Time values are specified |
2389 | -in seconds unless otherwise specified. |
2390 | +Customization parameters for special bonding options. Time values are |
2391 | +specified in seconds unless otherwise specified. |
2392 | |
2393 | **mode**: *<(scalar)>* |
2394 | |
2395 | @@ -367,8 +367,8 @@ Example: :: |
2396 | |
2397 | **parameters**: <*(mapping)>* |
2398 | |
2399 | -Customization parameters for special bridging options. Time values are specified |
2400 | -in seconds unless otherwise specified. |
2401 | +Customization parameters for special bridging options. Time values are |
2402 | +specified in seconds unless otherwise specified. |
2403 | |
2404 | **ageing-time**: <*(scalar)>* |
2405 | |
2406 | diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py |
2407 | index 349d54c..a615470 100644 |
2408 | --- a/tests/unittests/test_datasource/test_ovf.py |
2409 | +++ b/tests/unittests/test_datasource/test_ovf.py |
2410 | @@ -169,19 +169,56 @@ class TestDatasourceOVF(CiTestCase): |
2411 | MARKER-ID = 12345345 |
2412 | """) |
2413 | util.write_file(conf_file, conf_content) |
2414 | - with self.assertRaises(CustomScriptNotFound) as context: |
2415 | - wrap_and_call( |
2416 | - 'cloudinit.sources.DataSourceOVF', |
2417 | - {'util.read_dmi_data': 'vmware', |
2418 | - 'util.del_dir': True, |
2419 | - 'search_file': self.tdir, |
2420 | - 'wait_for_imc_cfg_file': conf_file, |
2421 | - 'get_nics_to_enable': ''}, |
2422 | - ds.get_data) |
2423 | + with mock.patch(MPATH + 'get_tools_config', return_value='true'): |
2424 | + with self.assertRaises(CustomScriptNotFound) as context: |
2425 | + wrap_and_call( |
2426 | + 'cloudinit.sources.DataSourceOVF', |
2427 | + {'util.read_dmi_data': 'vmware', |
2428 | + 'util.del_dir': True, |
2429 | + 'search_file': self.tdir, |
2430 | + 'wait_for_imc_cfg_file': conf_file, |
2431 | + 'get_nics_to_enable': ''}, |
2432 | + ds.get_data) |
2433 | customscript = self.tmp_path('test-script', self.tdir) |
2434 | self.assertIn('Script %s not found!!' % customscript, |
2435 | str(context.exception)) |
2436 | |
2437 | + def test_get_data_cust_script_disabled(self): |
2438 | + """If custom script is disabled by VMware tools configuration, |
2439 | + raise a RuntimeError. |
2440 | + """ |
2441 | + paths = Paths({'cloud_dir': self.tdir}) |
2442 | + ds = self.datasource( |
2443 | + sys_cfg={'disable_vmware_customization': False}, distro={}, |
2444 | + paths=paths) |
2445 | + # Prepare the conf file |
2446 | + conf_file = self.tmp_path('test-cust', self.tdir) |
2447 | + conf_content = dedent("""\ |
2448 | + [CUSTOM-SCRIPT] |
2449 | + SCRIPT-NAME = test-script |
2450 | + [MISC] |
2451 | + MARKER-ID = 12345346 |
2452 | + """) |
2453 | + util.write_file(conf_file, conf_content) |
2454 | + # Prepare the custom sript |
2455 | + customscript = self.tmp_path('test-script', self.tdir) |
2456 | + util.write_file(customscript, "This is the post cust script") |
2457 | + |
2458 | + with mock.patch(MPATH + 'get_tools_config', return_value='false'): |
2459 | + with mock.patch(MPATH + 'set_customization_status', |
2460 | + return_value=('msg', b'')): |
2461 | + with self.assertRaises(RuntimeError) as context: |
2462 | + wrap_and_call( |
2463 | + 'cloudinit.sources.DataSourceOVF', |
2464 | + {'util.read_dmi_data': 'vmware', |
2465 | + 'util.del_dir': True, |
2466 | + 'search_file': self.tdir, |
2467 | + 'wait_for_imc_cfg_file': conf_file, |
2468 | + 'get_nics_to_enable': ''}, |
2469 | + ds.get_data) |
2470 | + self.assertIn('Custom script is disabled by VM Administrator', |
2471 | + str(context.exception)) |
2472 | + |
2473 | def test_get_data_non_vmware_seed_platform_info(self): |
2474 | """Platform info properly reports when on non-vmware platforms.""" |
2475 | paths = Paths({'cloud_dir': self.tdir, 'run_dir': self.tdir}) |
2476 | diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py |
2477 | index 587e699..de87be2 100644 |
2478 | --- a/tests/unittests/test_ds_identify.py |
2479 | +++ b/tests/unittests/test_ds_identify.py |
2480 | @@ -195,6 +195,10 @@ class DsIdentifyBase(CiTestCase): |
2481 | return self._check_via_dict( |
2482 | data, RC_FOUND, dslist=[data.get('ds'), DS_NONE]) |
2483 | |
2484 | + def _test_ds_not_found(self, name): |
2485 | + data = copy.deepcopy(VALID_CFG[name]) |
2486 | + return self._check_via_dict(data, RC_NOT_FOUND) |
2487 | + |
2488 | def _check_via_dict(self, data, rc, dslist=None, **kwargs): |
2489 | ret = self._call_via_dict(data, **kwargs) |
2490 | good = False |
2491 | @@ -244,9 +248,13 @@ class TestDsIdentify(DsIdentifyBase): |
2492 | self._test_ds_found('Ec2-xen') |
2493 | |
2494 | def test_brightbox_is_ec2(self): |
2495 | - """EC2: product_serial ends with 'brightbox.com'""" |
2496 | + """EC2: product_serial ends with '.brightbox.com'""" |
2497 | self._test_ds_found('Ec2-brightbox') |
2498 | |
2499 | + def test_bobrightbox_is_not_brightbox(self): |
2500 | + """EC2: bobrightbox.com in product_serial is not brightbox'""" |
2501 | + self._test_ds_not_found('Ec2-brightbox-negative') |
2502 | + |
2503 | def test_gce_by_product_name(self): |
2504 | """GCE identifies itself with product_name.""" |
2505 | self._test_ds_found('GCE') |
2506 | @@ -724,7 +732,11 @@ VALID_CFG = { |
2507 | }, |
2508 | 'Ec2-brightbox': { |
2509 | 'ds': 'Ec2', |
2510 | - 'files': {P_PRODUCT_SERIAL: 'facc6e2f.brightbox.com\n'}, |
2511 | + 'files': {P_PRODUCT_SERIAL: 'srv-otuxg.gb1.brightbox.com\n'}, |
2512 | + }, |
2513 | + 'Ec2-brightbox-negative': { |
2514 | + 'ds': 'Ec2', |
2515 | + 'files': {P_PRODUCT_SERIAL: 'tricky-host.bobrightbox.com\n'}, |
2516 | }, |
2517 | 'GCE': { |
2518 | 'ds': 'GCE', |
2519 | diff --git a/tests/unittests/test_vmware/test_guestcust_util.py b/tests/unittests/test_vmware/test_guestcust_util.py |
2520 | new file mode 100644 |
2521 | index 0000000..b8fa994 |
2522 | --- /dev/null |
2523 | +++ b/tests/unittests/test_vmware/test_guestcust_util.py |
2524 | @@ -0,0 +1,65 @@ |
2525 | +# Copyright (C) 2019 Canonical Ltd. |
2526 | +# Copyright (C) 2019 VMware INC. |
2527 | +# |
2528 | +# Author: Xiaofeng Wang <xiaofengw@vmware.com> |
2529 | +# |
2530 | +# This file is part of cloud-init. See LICENSE file for license information. |
2531 | + |
2532 | +from cloudinit import util |
2533 | +from cloudinit.sources.helpers.vmware.imc.guestcust_util import ( |
2534 | + get_tools_config, |
2535 | +) |
2536 | +from cloudinit.tests.helpers import CiTestCase, mock |
2537 | + |
2538 | + |
2539 | +class TestGuestCustUtil(CiTestCase): |
2540 | + def test_get_tools_config_not_installed(self): |
2541 | + """ |
2542 | + This test is designed to verify the behavior if vmware-toolbox-cmd |
2543 | + is not installed. |
2544 | + """ |
2545 | + with mock.patch.object(util, 'which', return_value=None): |
2546 | + self.assertEqual( |
2547 | + get_tools_config('section', 'key', 'defaultVal'), 'defaultVal') |
2548 | + |
2549 | + def test_get_tools_config_internal_exception(self): |
2550 | + """ |
2551 | + This test is designed to verify the behavior if internal exception |
2552 | + is raised. |
2553 | + """ |
2554 | + with mock.patch.object(util, 'which', return_value='/dummy/path'): |
2555 | + with mock.patch.object(util, 'subp', |
2556 | + return_value=('key=value', b''), |
2557 | + side_effect=util.ProcessExecutionError( |
2558 | + "subp failed", exit_code=99)): |
2559 | + # verify return value is 'defaultVal', not 'value'. |
2560 | + self.assertEqual( |
2561 | + get_tools_config('section', 'key', 'defaultVal'), |
2562 | + 'defaultVal') |
2563 | + |
2564 | + def test_get_tools_config_normal(self): |
2565 | + """ |
2566 | + This test is designed to verify the value could be parsed from |
2567 | + key = value of the given [section] |
2568 | + """ |
2569 | + with mock.patch.object(util, 'which', return_value='/dummy/path'): |
2570 | + # value is not blank |
2571 | + with mock.patch.object(util, 'subp', |
2572 | + return_value=('key = value ', b'')): |
2573 | + self.assertEqual( |
2574 | + get_tools_config('section', 'key', 'defaultVal'), |
2575 | + 'value') |
2576 | + # value is blank |
2577 | + with mock.patch.object(util, 'subp', |
2578 | + return_value=('key = ', b'')): |
2579 | + self.assertEqual( |
2580 | + get_tools_config('section', 'key', 'defaultVal'), |
2581 | + '') |
2582 | + # value contains = |
2583 | + with mock.patch.object(util, 'subp', |
2584 | + return_value=('key=Bar=Wark', b'')): |
2585 | + self.assertEqual( |
2586 | + get_tools_config('section', 'key', 'defaultVal'), |
2587 | + 'Bar=Wark') |
2588 | + |
2589 | +# vi: ts=4 expandtab |
2590 | diff --git a/tools/ds-identify b/tools/ds-identify |
2591 | index e0d4865..2447d14 100755 |
2592 | --- a/tools/ds-identify |
2593 | +++ b/tools/ds-identify |
2594 | @@ -891,9 +891,8 @@ ec2_identify_platform() { |
2595 | local default="$1" |
2596 | local serial="${DI_DMI_PRODUCT_SERIAL}" |
2597 | |
2598 | - # brightbox https://bugs.launchpad.net/cloud-init/+bug/1661693 |
2599 | case "$serial" in |
2600 | - *brightbox.com) _RET="Brightbox"; return 0;; |
2601 | + *.brightbox.com) _RET="Brightbox"; return 0;; |
2602 | esac |
2603 | |
2604 | # AWS http://docs.aws.amazon.com/AWSEC2/ |
2605 | diff --git a/tox.ini b/tox.ini |
2606 | index 1f01eb7..f5baf32 100644 |
2607 | --- a/tox.ini |
2608 | +++ b/tox.ini |
2609 | @@ -53,8 +53,13 @@ exclude = .venv,.tox,dist,doc,*egg,.git,build,tools |
2610 | |
2611 | [testenv:doc] |
2612 | basepython = python3 |
2613 | -deps = sphinx |
2614 | -commands = {envpython} -m sphinx {posargs:doc/rtd doc/rtd_html} |
2615 | +deps = |
2616 | + doc8 |
2617 | + sphinx |
2618 | + sphinx_rtd_theme |
2619 | +commands = |
2620 | + {envpython} -m sphinx {posargs:doc/rtd doc/rtd_html} |
2621 | + doc8 doc/rtd |
2622 | |
2623 | [testenv:xenial] |
2624 | commands = |
PASSED: Continuous integration, rev:83da1d94fbf 51086db0b27d34f 986832140297f6 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1159/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1159//rebuild
https:/