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