Merge ~chad.smith/cloud-init:ubuntu/devel into cloud-init:ubuntu/devel
- Git
- lp:~chad.smith/cloud-init
- ubuntu/devel
- Merge into ubuntu/devel
Proposed by
Chad Smith
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Scott Moser | ||||||||
Approved revision: | 6838d91891b31b79f06ada7253cce675d56983dc | ||||||||
Merged at revision: | 6838d91891b31b79f06ada7253cce675d56983dc | ||||||||
Proposed branch: | ~chad.smith/cloud-init:ubuntu/devel | ||||||||
Merge into: | cloud-init:ubuntu/devel | ||||||||
Diff against target: |
871 lines (+394/-79) 16 files modified
cloudinit/config/cc_mounts.py (+1/-1) cloudinit/handlers/upstart_job.py (+1/-1) cloudinit/sources/DataSourceAltCloud.py (+8/-8) cloudinit/sources/DataSourceNoCloud.py (+2/-2) cloudinit/sources/DataSourceOpenNebula.py (+1/-1) cloudinit/sources/DataSourceSmartOS.py (+41/-5) cloudinit/tests/test_util.py (+77/-1) cloudinit/util.py (+38/-5) debian/changelog (+11/-2) doc/rtd/topics/datasources.rst (+97/-0) doc/rtd/topics/datasources/cloudstack.rst (+20/-6) doc/rtd/topics/datasources/ec2.rst (+30/-0) doc/rtd/topics/datasources/openstack.rst (+6/-2) packages/suse/cloud-init.spec.in (+21/-42) setup.py (+14/-3) tests/unittests/test_datasource/test_smartos.py (+26/-0) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+347396@code.launchpad.net |
Commit message
Upstream snapshot of cloud-init tip for release into Cosmic.
Contains:
- full package version in logs
- util: add get_linux_distro function to replace platform.dist
[Robert Schweikert] (LP: #1745235)
- pyflakes: fix unused variable references identified by pyflakes 2.0.0.
- Do not use the systemd_prefix macro, not available in this environment
[Robert Schweikert]
- doc: Add config info to ec2, openstack and cloudstack datasource docs
- Enable SmartOS network metadata to work with netplan via per-subnet
routes [Dan McDonald] (LP: #1763512)
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py |
2 | index eca6ea3..339baba 100644 |
3 | --- a/cloudinit/config/cc_mounts.py |
4 | +++ b/cloudinit/config/cc_mounts.py |
5 | @@ -233,7 +233,7 @@ def setup_swapfile(fname, size=None, maxsize=None): |
6 | if str(size).lower() == "auto": |
7 | try: |
8 | memsize = util.read_meminfo()['total'] |
9 | - except IOError as e: |
10 | + except IOError: |
11 | LOG.debug("Not creating swap: failed to read meminfo") |
12 | return |
13 | |
14 | diff --git a/cloudinit/handlers/upstart_job.py b/cloudinit/handlers/upstart_job.py |
15 | index 1ca92d4..dc33876 100644 |
16 | --- a/cloudinit/handlers/upstart_job.py |
17 | +++ b/cloudinit/handlers/upstart_job.py |
18 | @@ -97,7 +97,7 @@ def _has_suitable_upstart(): |
19 | else: |
20 | util.logexc(LOG, "dpkg --compare-versions failed [%s]", |
21 | e.exit_code) |
22 | - except Exception as e: |
23 | + except Exception: |
24 | util.logexc(LOG, "dpkg --compare-versions failed") |
25 | return False |
26 | else: |
27 | diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py |
28 | index f6e86f3..24fd65f 100644 |
29 | --- a/cloudinit/sources/DataSourceAltCloud.py |
30 | +++ b/cloudinit/sources/DataSourceAltCloud.py |
31 | @@ -184,11 +184,11 @@ class DataSourceAltCloud(sources.DataSource): |
32 | cmd = CMD_PROBE_FLOPPY |
33 | (cmd_out, _err) = util.subp(cmd) |
34 | LOG.debug('Command: %s\nOutput%s', ' '.join(cmd), cmd_out) |
35 | - except ProcessExecutionError as _err: |
36 | - util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), _err) |
37 | + except ProcessExecutionError as e: |
38 | + util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), e) |
39 | return False |
40 | - except OSError as _err: |
41 | - util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), _err) |
42 | + except OSError as e: |
43 | + util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), e) |
44 | return False |
45 | |
46 | floppy_dev = '/dev/fd0' |
47 | @@ -197,11 +197,11 @@ class DataSourceAltCloud(sources.DataSource): |
48 | try: |
49 | (cmd_out, _err) = util.udevadm_settle(exists=floppy_dev, timeout=5) |
50 | LOG.debug('Command: %s\nOutput%s', ' '.join(cmd), cmd_out) |
51 | - except ProcessExecutionError as _err: |
52 | - util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), _err) |
53 | + except ProcessExecutionError as e: |
54 | + util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), e) |
55 | return False |
56 | - except OSError as _err: |
57 | - util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), _err) |
58 | + except OSError as e: |
59 | + util.logexc(LOG, 'Failed command: %s\n%s', ' '.join(cmd), e) |
60 | return False |
61 | |
62 | try: |
63 | diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py |
64 | index 5d3a8dd..2daea59 100644 |
65 | --- a/cloudinit/sources/DataSourceNoCloud.py |
66 | +++ b/cloudinit/sources/DataSourceNoCloud.py |
67 | @@ -78,7 +78,7 @@ class DataSourceNoCloud(sources.DataSource): |
68 | LOG.debug("Using seeded data from %s", path) |
69 | mydata = _merge_new_seed(mydata, seeded) |
70 | break |
71 | - except ValueError as e: |
72 | + except ValueError: |
73 | pass |
74 | |
75 | # If the datasource config had a 'seedfrom' entry, then that takes |
76 | @@ -117,7 +117,7 @@ class DataSourceNoCloud(sources.DataSource): |
77 | try: |
78 | seeded = util.mount_cb(dev, _pp2d_callback, |
79 | pp2d_kwargs) |
80 | - except ValueError as e: |
81 | + except ValueError: |
82 | if dev in label_list: |
83 | LOG.warning("device %s with label=%s not a" |
84 | "valid seed.", dev, label) |
85 | diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py |
86 | index d4a4111..16c1078 100644 |
87 | --- a/cloudinit/sources/DataSourceOpenNebula.py |
88 | +++ b/cloudinit/sources/DataSourceOpenNebula.py |
89 | @@ -378,7 +378,7 @@ def read_context_disk_dir(source_dir, asuser=None): |
90 | if asuser is not None: |
91 | try: |
92 | pwd.getpwnam(asuser) |
93 | - except KeyError as e: |
94 | + except KeyError: |
95 | raise BrokenContextDiskDir( |
96 | "configured user '{user}' does not exist".format( |
97 | user=asuser)) |
98 | diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py |
99 | index c91e4d5..f92e8b5 100644 |
100 | --- a/cloudinit/sources/DataSourceSmartOS.py |
101 | +++ b/cloudinit/sources/DataSourceSmartOS.py |
102 | @@ -17,7 +17,7 @@ |
103 | # of a serial console. |
104 | # |
105 | # Certain behavior is defined by the DataDictionary |
106 | -# http://us-east.manta.joyent.com/jmc/public/mdata/datadict.html |
107 | +# https://eng.joyent.com/mdata/datadict.html |
108 | # Comments with "@datadictionary" are snippets of the definition |
109 | |
110 | import base64 |
111 | @@ -298,6 +298,7 @@ class DataSourceSmartOS(sources.DataSource): |
112 | self.userdata_raw = ud |
113 | self.vendordata_raw = md['vendor-data'] |
114 | self.network_data = md['network-data'] |
115 | + self.routes_data = md['routes'] |
116 | |
117 | self._set_provisioned() |
118 | return True |
119 | @@ -321,7 +322,8 @@ class DataSourceSmartOS(sources.DataSource): |
120 | convert_smartos_network_data( |
121 | network_data=self.network_data, |
122 | dns_servers=self.metadata['dns_servers'], |
123 | - dns_domain=self.metadata['dns_domain'])) |
124 | + dns_domain=self.metadata['dns_domain'], |
125 | + routes=self.routes_data)) |
126 | return self._network_config |
127 | |
128 | |
129 | @@ -760,7 +762,8 @@ def get_smartos_environ(uname_version=None, product_name=None): |
130 | |
131 | # Convert SMARTOS 'sdc:nics' data to network_config yaml |
132 | def convert_smartos_network_data(network_data=None, |
133 | - dns_servers=None, dns_domain=None): |
134 | + dns_servers=None, dns_domain=None, |
135 | + routes=None): |
136 | """Return a dictionary of network_config by parsing provided |
137 | SMARTOS sdc:nics configuration data |
138 | |
139 | @@ -778,6 +781,10 @@ def convert_smartos_network_data(network_data=None, |
140 | keys are related to ip configuration. For each ip in the 'ips' list |
141 | we create a subnet entry under 'subnets' pairing the ip to a one in |
142 | the 'gateways' list. |
143 | + |
144 | + Each route in sdc:routes is mapped to a route on each interface. |
145 | + The sdc:routes properties 'dst' and 'gateway' map to 'network' and |
146 | + 'gateway'. The 'linklocal' sdc:routes property is ignored. |
147 | """ |
148 | |
149 | valid_keys = { |
150 | @@ -800,6 +807,10 @@ def convert_smartos_network_data(network_data=None, |
151 | 'scope', |
152 | 'type', |
153 | ], |
154 | + 'route': [ |
155 | + 'network', |
156 | + 'gateway', |
157 | + ], |
158 | } |
159 | |
160 | if dns_servers: |
161 | @@ -814,6 +825,9 @@ def convert_smartos_network_data(network_data=None, |
162 | else: |
163 | dns_domain = [] |
164 | |
165 | + if not routes: |
166 | + routes = [] |
167 | + |
168 | def is_valid_ipv4(addr): |
169 | return '.' in addr |
170 | |
171 | @@ -840,6 +854,7 @@ def convert_smartos_network_data(network_data=None, |
172 | if ip == "dhcp": |
173 | subnet = {'type': 'dhcp4'} |
174 | else: |
175 | + routeents = [] |
176 | subnet = dict((k, v) for k, v in nic.items() |
177 | if k in valid_keys['subnet']) |
178 | subnet.update({ |
179 | @@ -861,6 +876,25 @@ def convert_smartos_network_data(network_data=None, |
180 | pgws[proto]['gw'] = gateways[0] |
181 | subnet.update({'gateway': pgws[proto]['gw']}) |
182 | |
183 | + for route in routes: |
184 | + rcfg = dict((k, v) for k, v in route.items() |
185 | + if k in valid_keys['route']) |
186 | + # Linux uses the value of 'gateway' to determine |
187 | + # automatically if the route is a forward/next-hop |
188 | + # (non-local IP for gateway) or an interface/resolver |
189 | + # (local IP for gateway). So we can ignore the |
190 | + # 'interface' attribute of sdc:routes, because SDC |
191 | + # guarantees that the gateway is a local IP for |
192 | + # "interface=true". |
193 | + # |
194 | + # Eventually we should be smart and compare "gateway" |
195 | + # to see if it's in the prefix. We can then smartly |
196 | + # add or not-add this route. But for now, |
197 | + # when in doubt, use brute force! Routes for everyone! |
198 | + rcfg.update({'network': route['dst']}) |
199 | + routeents.append(rcfg) |
200 | + subnet.update({'routes': routeents}) |
201 | + |
202 | subnets.append(subnet) |
203 | cfg.update({'subnets': subnets}) |
204 | config.append(cfg) |
205 | @@ -904,12 +938,14 @@ if __name__ == "__main__": |
206 | keyname = SMARTOS_ATTRIB_JSON[key] |
207 | data[key] = client.get_json(keyname) |
208 | elif key == "network_config": |
209 | - for depkey in ('network-data', 'dns_servers', 'dns_domain'): |
210 | + for depkey in ('network-data', 'dns_servers', 'dns_domain', |
211 | + 'routes'): |
212 | load_key(client, depkey, data) |
213 | data[key] = convert_smartos_network_data( |
214 | network_data=data['network-data'], |
215 | dns_servers=data['dns_servers'], |
216 | - dns_domain=data['dns_domain']) |
217 | + dns_domain=data['dns_domain'], |
218 | + routes=data['routes']) |
219 | else: |
220 | if key in SMARTOS_ATTRIB_MAP: |
221 | keyname, strip = SMARTOS_ATTRIB_MAP[key] |
222 | diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py |
223 | index 3c05a43..17853fc 100644 |
224 | --- a/cloudinit/tests/test_util.py |
225 | +++ b/cloudinit/tests/test_util.py |
226 | @@ -3,11 +3,12 @@ |
227 | """Tests for cloudinit.util""" |
228 | |
229 | import logging |
230 | -from textwrap import dedent |
231 | +import platform |
232 | |
233 | import cloudinit.util as util |
234 | |
235 | from cloudinit.tests.helpers import CiTestCase, mock |
236 | +from textwrap import dedent |
237 | |
238 | LOG = logging.getLogger(__name__) |
239 | |
240 | @@ -16,6 +17,29 @@ MOUNT_INFO = [ |
241 | '153 68 254:0 / /home rw,relatime shared:101 - xfs /dev/sda2 rw,attr2' |
242 | ] |
243 | |
244 | +OS_RELEASE_SLES = dedent("""\ |
245 | + NAME="SLES"\n |
246 | + VERSION="12-SP3"\n |
247 | + VERSION_ID="12.3"\n |
248 | + PRETTY_NAME="SUSE Linux Enterprise Server 12 SP3"\n |
249 | + ID="sles"\nANSI_COLOR="0;32"\n |
250 | + CPE_NAME="cpe:/o:suse:sles:12:sp3"\n |
251 | +""") |
252 | + |
253 | +OS_RELEASE_UBUNTU = dedent("""\ |
254 | + NAME="Ubuntu"\n |
255 | + VERSION="16.04.3 LTS (Xenial Xerus)"\n |
256 | + ID=ubuntu\n |
257 | + ID_LIKE=debian\n |
258 | + PRETTY_NAME="Ubuntu 16.04.3 LTS"\n |
259 | + VERSION_ID="16.04"\n |
260 | + HOME_URL="http://www.ubuntu.com/"\n |
261 | + SUPPORT_URL="http://help.ubuntu.com/"\n |
262 | + BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"\n |
263 | + VERSION_CODENAME=xenial\n |
264 | + UBUNTU_CODENAME=xenial\n |
265 | +""") |
266 | + |
267 | |
268 | class FakeCloud(object): |
269 | |
270 | @@ -261,4 +285,56 @@ class TestUdevadmSettle(CiTestCase): |
271 | self.assertRaises(util.ProcessExecutionError, util.udevadm_settle) |
272 | |
273 | |
274 | +@mock.patch('os.path.exists') |
275 | +class TestGetLinuxDistro(CiTestCase): |
276 | + |
277 | + @classmethod |
278 | + def os_release_exists(self, path): |
279 | + """Side effect function""" |
280 | + if path == '/etc/os-release': |
281 | + return 1 |
282 | + |
283 | + @mock.patch('cloudinit.util.load_file') |
284 | + def test_get_linux_distro_quoted_name(self, m_os_release, m_path_exists): |
285 | + """Verify we get the correct name if the os-release file has |
286 | + the distro name in quotes""" |
287 | + m_os_release.return_value = OS_RELEASE_SLES |
288 | + m_path_exists.side_effect = TestGetLinuxDistro.os_release_exists |
289 | + dist = util.get_linux_distro() |
290 | + self.assertEqual(('sles', '12.3', platform.machine()), dist) |
291 | + |
292 | + @mock.patch('cloudinit.util.load_file') |
293 | + def test_get_linux_distro_bare_name(self, m_os_release, m_path_exists): |
294 | + """Verify we get the correct name if the os-release file does not |
295 | + have the distro name in quotes""" |
296 | + m_os_release.return_value = OS_RELEASE_UBUNTU |
297 | + m_path_exists.side_effect = TestGetLinuxDistro.os_release_exists |
298 | + dist = util.get_linux_distro() |
299 | + self.assertEqual(('ubuntu', '16.04', platform.machine()), dist) |
300 | + |
301 | + @mock.patch('platform.dist') |
302 | + def test_get_linux_distro_no_data(self, m_platform_dist, m_path_exists): |
303 | + """Verify we get no information if os-release does not exist""" |
304 | + m_platform_dist.return_value = ('', '', '') |
305 | + m_path_exists.return_value = 0 |
306 | + dist = util.get_linux_distro() |
307 | + self.assertEqual(('', '', ''), dist) |
308 | + |
309 | + @mock.patch('platform.dist') |
310 | + def test_get_linux_distro_no_impl(self, m_platform_dist, m_path_exists): |
311 | + """Verify we get an empty tuple when no information exists and |
312 | + Exceptions are not propagated""" |
313 | + m_platform_dist.side_effect = Exception() |
314 | + m_path_exists.return_value = 0 |
315 | + dist = util.get_linux_distro() |
316 | + self.assertEqual(('', '', ''), dist) |
317 | + |
318 | + @mock.patch('platform.dist') |
319 | + def test_get_linux_distro_plat_data(self, m_platform_dist, m_path_exists): |
320 | + """Verify we get the correct platform information""" |
321 | + m_platform_dist.return_value = ('foo', '1.1', 'aarch64') |
322 | + m_path_exists.return_value = 0 |
323 | + dist = util.get_linux_distro() |
324 | + self.assertEqual(('foo', '1.1', 'aarch64'), dist) |
325 | + |
326 | # vi: ts=4 expandtab |
327 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
328 | index c0473b8..d9b61cf 100644 |
329 | --- a/cloudinit/util.py |
330 | +++ b/cloudinit/util.py |
331 | @@ -576,6 +576,39 @@ def get_cfg_option_int(yobj, key, default=0): |
332 | return int(get_cfg_option_str(yobj, key, default=default)) |
333 | |
334 | |
335 | +def get_linux_distro(): |
336 | + distro_name = '' |
337 | + distro_version = '' |
338 | + if os.path.exists('/etc/os-release'): |
339 | + os_release = load_file('/etc/os-release') |
340 | + for line in os_release.splitlines(): |
341 | + if line.strip().startswith('ID='): |
342 | + distro_name = line.split('=')[-1] |
343 | + distro_name = distro_name.replace('"', '') |
344 | + if line.strip().startswith('VERSION_ID='): |
345 | + # Lets hope for the best that distros stay consistent ;) |
346 | + distro_version = line.split('=')[-1] |
347 | + distro_version = distro_version.replace('"', '') |
348 | + else: |
349 | + dist = ('', '', '') |
350 | + try: |
351 | + # Will be removed in 3.7 |
352 | + dist = platform.dist() # pylint: disable=W1505 |
353 | + except Exception: |
354 | + pass |
355 | + finally: |
356 | + found = None |
357 | + for entry in dist: |
358 | + if entry: |
359 | + found = 1 |
360 | + if not found: |
361 | + LOG.warning('Unable to determine distribution, template ' |
362 | + 'expansion may have unexpected results') |
363 | + return dist |
364 | + |
365 | + return (distro_name, distro_version, platform.machine()) |
366 | + |
367 | + |
368 | def system_info(): |
369 | info = { |
370 | 'platform': platform.platform(), |
371 | @@ -583,19 +616,19 @@ def system_info(): |
372 | 'release': platform.release(), |
373 | 'python': platform.python_version(), |
374 | 'uname': platform.uname(), |
375 | - 'dist': platform.dist(), # pylint: disable=W1505 |
376 | + 'dist': get_linux_distro() |
377 | } |
378 | system = info['system'].lower() |
379 | var = 'unknown' |
380 | if system == "linux": |
381 | linux_dist = info['dist'][0].lower() |
382 | - if linux_dist in ('centos', 'fedora', 'debian'): |
383 | + if linux_dist in ('centos', 'debian', 'fedora', 'rhel', 'suse'): |
384 | var = linux_dist |
385 | elif linux_dist in ('ubuntu', 'linuxmint', 'mint'): |
386 | var = 'ubuntu' |
387 | elif linux_dist == 'redhat': |
388 | var = 'rhel' |
389 | - elif linux_dist == 'suse': |
390 | + elif linux_dist in ('opensuse', 'sles'): |
391 | var = 'suse' |
392 | else: |
393 | var = 'linux' |
394 | @@ -2531,8 +2564,8 @@ def _call_dmidecode(key, dmidecode_path): |
395 | if result.replace(".", "") == "": |
396 | return "" |
397 | return result |
398 | - except (IOError, OSError) as _err: |
399 | - LOG.debug('failed dmidecode cmd: %s\n%s', cmd, _err) |
400 | + except (IOError, OSError) as e: |
401 | + LOG.debug('failed dmidecode cmd: %s\n%s', cmd, e) |
402 | return None |
403 | |
404 | |
405 | diff --git a/debian/changelog b/debian/changelog |
406 | index a78aae3..b529b78 100644 |
407 | --- a/debian/changelog |
408 | +++ b/debian/changelog |
409 | @@ -1,9 +1,18 @@ |
410 | -cloud-init (18.2-59-gcd1de5f4-0ubuntu2) UNRELEASED; urgency=medium |
411 | +cloud-init (18.2-64-gbbcc5e82-0ubuntu1) cosmic; urgency=medium |
412 | |
413 | * debian/rules: update version.version_string to contain packaged version. |
414 | (LP: #1770712) |
415 | + * New upstream snapshot. |
416 | + - util: add get_linux_distro function to replace platform.dist |
417 | + [Robert Schweikert] (LP: #1745235) |
418 | + - pyflakes: fix unused variable references identified by pyflakes 2.0.0. |
419 | + - - Do not use the systemd_prefix macro, not available in this environment |
420 | + [Robert Schweikert] |
421 | + - doc: Add config info to ec2, openstack and cloudstack datasource docs |
422 | + - Enable SmartOS network metadata to work with netplan via per-subnet |
423 | + routes [Dan McDonald] (LP: #1763512) |
424 | |
425 | - -- Scott Moser <smoser@ubuntu.com> Mon, 04 Jun 2018 10:00:57 -0400 |
426 | + -- Chad Smith <chad.smith@canonical.com> Mon, 04 Jun 2018 12:18:16 -0600 |
427 | |
428 | cloud-init (18.2-59-gcd1de5f4-0ubuntu1) cosmic; urgency=medium |
429 | |
430 | diff --git a/doc/rtd/topics/datasources.rst b/doc/rtd/topics/datasources.rst |
431 | index 38ba75d..30e57d8 100644 |
432 | --- a/doc/rtd/topics/datasources.rst |
433 | +++ b/doc/rtd/topics/datasources.rst |
434 | @@ -17,6 +17,103 @@ own way) internally a datasource abstract class was created to allow for a |
435 | single way to access the different cloud systems methods to provide this data |
436 | through the typical usage of subclasses. |
437 | |
438 | + |
439 | +instance-data |
440 | +------------- |
441 | +For reference, cloud-init stores all the metadata, vendordata and userdata |
442 | +provided by a cloud in a json blob at ``/run/cloud-init/instance-data.json``. |
443 | +While the json contains datasource-specific keys and names, cloud-init will |
444 | +maintain a minimal set of standardized keys that will remain stable on any |
445 | +cloud. Standardized instance-data keys will be present under a "v1" key. |
446 | +Any datasource metadata cloud-init consumes will all be present under the |
447 | +"ds" key. |
448 | + |
449 | +Below is an instance-data.json example from an OpenStack instance: |
450 | + |
451 | +.. sourcecode:: json |
452 | + |
453 | + { |
454 | + "base64-encoded-keys": [ |
455 | + "ds/meta-data/random_seed", |
456 | + "ds/user-data" |
457 | + ], |
458 | + "ds": { |
459 | + "ec2_metadata": { |
460 | + "ami-id": "ami-0000032f", |
461 | + "ami-launch-index": "0", |
462 | + "ami-manifest-path": "FIXME", |
463 | + "block-device-mapping": { |
464 | + "ami": "vda", |
465 | + "ephemeral0": "/dev/vdb", |
466 | + "root": "/dev/vda" |
467 | + }, |
468 | + "hostname": "xenial-test.novalocal", |
469 | + "instance-action": "none", |
470 | + "instance-id": "i-0006e030", |
471 | + "instance-type": "m1.small", |
472 | + "local-hostname": "xenial-test.novalocal", |
473 | + "local-ipv4": "10.5.0.6", |
474 | + "placement": { |
475 | + "availability-zone": "None" |
476 | + }, |
477 | + "public-hostname": "xenial-test.novalocal", |
478 | + "public-ipv4": "10.245.162.145", |
479 | + "reservation-id": "r-fxm623oa", |
480 | + "security-groups": "default" |
481 | + }, |
482 | + "meta-data": { |
483 | + "availability_zone": null, |
484 | + "devices": [], |
485 | + "hostname": "xenial-test.novalocal", |
486 | + "instance-id": "3e39d278-0644-4728-9479-678f9212d8f0", |
487 | + "launch_index": 0, |
488 | + "local-hostname": "xenial-test.novalocal", |
489 | + "name": "xenial-test", |
490 | + "project_id": "e0eb2d2538814...", |
491 | + "random_seed": "A6yPN...", |
492 | + "uuid": "3e39d278-0644-4728-9479-678f92..." |
493 | + }, |
494 | + "network_json": { |
495 | + "links": [ |
496 | + { |
497 | + "ethernet_mac_address": "fa:16:3e:7d:74:9b", |
498 | + "id": "tap9ca524d5-6e", |
499 | + "mtu": 8958, |
500 | + "type": "ovs", |
501 | + "vif_id": "9ca524d5-6e5a-4809-936a-6901..." |
502 | + } |
503 | + ], |
504 | + "networks": [ |
505 | + { |
506 | + "id": "network0", |
507 | + "link": "tap9ca524d5-6e", |
508 | + "network_id": "c6adfc18-9753-42eb-b3ea-18b57e6b837f", |
509 | + "type": "ipv4_dhcp" |
510 | + } |
511 | + ], |
512 | + "services": [ |
513 | + { |
514 | + "address": "10.10.160.2", |
515 | + "type": "dns" |
516 | + } |
517 | + ] |
518 | + }, |
519 | + "user-data": "I2Nsb3VkLWNvbmZpZ...", |
520 | + "vendor-data": null |
521 | + }, |
522 | + "v1": { |
523 | + "availability-zone": null, |
524 | + "cloud-name": "openstack", |
525 | + "instance-id": "3e39d278-0644-4728-9479-678f9212d8f0", |
526 | + "local-hostname": "xenial-test", |
527 | + "region": null |
528 | + } |
529 | + } |
530 | + |
531 | + |
532 | + |
533 | +Datasource API |
534 | +-------------- |
535 | The current interface that a datasource object must provide is the following: |
536 | |
537 | .. sourcecode:: python |
538 | diff --git a/doc/rtd/topics/datasources/cloudstack.rst b/doc/rtd/topics/datasources/cloudstack.rst |
539 | index 225093a..a3101ed 100644 |
540 | --- a/doc/rtd/topics/datasources/cloudstack.rst |
541 | +++ b/doc/rtd/topics/datasources/cloudstack.rst |
542 | @@ -4,7 +4,9 @@ CloudStack |
543 | ========== |
544 | |
545 | `Apache CloudStack`_ expose user-data, meta-data, user password and account |
546 | -sshkey thru the Virtual-Router. For more details on meta-data and user-data, |
547 | +sshkey thru the Virtual-Router. The datasource obtains the VR address via |
548 | +dhcp lease information given to the instance. |
549 | +For more details on meta-data and user-data, |
550 | refer the `CloudStack Administrator Guide`_. |
551 | |
552 | URLs to access user-data and meta-data from the Virtual Machine. Here 10.1.1.1 |
553 | @@ -18,14 +20,26 @@ is the Virtual Router IP: |
554 | |
555 | Configuration |
556 | ------------- |
557 | +The following configuration can be set for the datasource in system |
558 | +configuration (in `/etc/cloud/cloud.cfg` or `/etc/cloud/cloud.cfg.d/`). |
559 | |
560 | -Apache CloudStack datasource can be configured as follows: |
561 | +The settings that may be configured are: |
562 | |
563 | -.. code:: yaml |
564 | + * **max_wait**: the maximum amount of clock time in seconds that should be |
565 | + spent searching metadata_urls. A value less than zero will result in only |
566 | + one request being made, to the first in the list. (default: 120) |
567 | + * **timeout**: the timeout value provided to urlopen for each individual http |
568 | + request. This is used both when selecting a metadata_url and when crawling |
569 | + the metadata service. (default: 50) |
570 | |
571 | - datasource: |
572 | - CloudStack: {} |
573 | - None: {} |
574 | +An example configuration with the default values is provided below: |
575 | + |
576 | +.. sourcecode:: yaml |
577 | + |
578 | + datasource: |
579 | + CloudStack: |
580 | + max_wait: 120 |
581 | + timeout: 50 |
582 | datasource_list: |
583 | - CloudStack |
584 | |
585 | diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst |
586 | index 3bc66e1..64c325d 100644 |
587 | --- a/doc/rtd/topics/datasources/ec2.rst |
588 | +++ b/doc/rtd/topics/datasources/ec2.rst |
589 | @@ -60,4 +60,34 @@ To see which versions are supported from your cloud provider use the following U |
590 | ... |
591 | latest |
592 | |
593 | + |
594 | + |
595 | +Configuration |
596 | +------------- |
597 | +The following configuration can be set for the datasource in system |
598 | +configuration (in `/etc/cloud/cloud.cfg` or `/etc/cloud/cloud.cfg.d/`). |
599 | + |
600 | +The settings that may be configured are: |
601 | + |
602 | + * **metadata_urls**: This list of urls will be searched for an Ec2 |
603 | + metadata service. The first entry that successfully returns a 200 response |
604 | + for <url>/<version>/meta-data/instance-id will be selected. |
605 | + (default: ['http://169.254.169.254', 'http://instance-data:8773']). |
606 | + * **max_wait**: the maximum amount of clock time in seconds that should be |
607 | + spent searching metadata_urls. A value less than zero will result in only |
608 | + one request being made, to the first in the list. (default: 120) |
609 | + * **timeout**: the timeout value provided to urlopen for each individual http |
610 | + request. This is used both when selecting a metadata_url and when crawling |
611 | + the metadata service. (default: 50) |
612 | + |
613 | +An example configuration with the default values is provided below: |
614 | + |
615 | +.. sourcecode:: yaml |
616 | + |
617 | + datasource: |
618 | + Ec2: |
619 | + metadata_urls: ["http://169.254.169.254:80", "http://instance-data:8773"] |
620 | + max_wait: 120 |
621 | + timeout: 50 |
622 | + |
623 | .. vi: textwidth=78 |
624 | diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst |
625 | index 43592de..0ea8994 100644 |
626 | --- a/doc/rtd/topics/datasources/openstack.rst |
627 | +++ b/doc/rtd/topics/datasources/openstack.rst |
628 | @@ -25,18 +25,22 @@ The settings that may be configured are: |
629 | the metadata service. (default: 10) |
630 | * **retries**: The number of retries that should be done for an http request. |
631 | This value is used only after metadata_url is selected. (default: 5) |
632 | + * **apply_network_config**: A boolean specifying whether to configure the |
633 | + network for the instance based on network_data.json provided by the |
634 | + metadata service. When False, only configure dhcp on the primary nic for |
635 | + this instances. (default: True) |
636 | |
637 | -An example configuration with the default values is provided as example below: |
638 | +An example configuration with the default values is provided below: |
639 | |
640 | .. sourcecode:: yaml |
641 | |
642 | - #cloud-config |
643 | datasource: |
644 | OpenStack: |
645 | metadata_urls: ["http://169.254.169.254"] |
646 | max_wait: -1 |
647 | timeout: 10 |
648 | retries: 5 |
649 | + apply_network_config: True |
650 | |
651 | |
652 | Vendor Data |
653 | diff --git a/packages/suse/cloud-init.spec.in b/packages/suse/cloud-init.spec.in |
654 | index 366a78c..e781d74 100644 |
655 | --- a/packages/suse/cloud-init.spec.in |
656 | +++ b/packages/suse/cloud-init.spec.in |
657 | @@ -5,7 +5,7 @@ |
658 | # Or: http://www.rpm.org/max-rpm/ch-rpm-inside.html |
659 | |
660 | Name: cloud-init |
661 | -Version: {{version}} |
662 | +Version: {{rpm_upstream_version}} |
663 | Release: 1{{subrelease}}%{?dist} |
664 | Summary: Cloud instance init scripts |
665 | |
666 | @@ -16,22 +16,13 @@ URL: http://launchpad.net/cloud-init |
667 | Source0: {{archive_name}} |
668 | BuildRoot: %{_tmppath}/%{name}-%{version}-build |
669 | |
670 | -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 |
671 | -%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} |
672 | -%else |
673 | BuildArch: noarch |
674 | -%endif |
675 | + |
676 | |
677 | {% for r in buildrequires %} |
678 | BuildRequires: {{r}} |
679 | {% endfor %} |
680 | |
681 | -%if 0%{?suse_version} && 0%{?suse_version} <= 1210 |
682 | - %define initsys sysvinit |
683 | -%else |
684 | - %define initsys systemd |
685 | -%endif |
686 | - |
687 | # Install pypi 'dynamic' requirements |
688 | {% for r in requires %} |
689 | Requires: {{r}} |
690 | @@ -39,7 +30,7 @@ Requires: {{r}} |
691 | |
692 | # Custom patches |
693 | {% for p in patches %} |
694 | -Patch{{loop.index0}: {{p}} |
695 | +Patch{{loop.index0}}: {{p}} |
696 | {% endfor %} |
697 | |
698 | %description |
699 | @@ -63,35 +54,21 @@ end for |
700 | %{__python} setup.py install \ |
701 | --skip-build --root=%{buildroot} --prefix=%{_prefix} \ |
702 | --record-rpm=INSTALLED_FILES --install-lib=%{python_sitelib} \ |
703 | - --init-system=%{initsys} |
704 | + --init-system=systemd |
705 | + |
706 | +# Move udev rules |
707 | +mkdir -p %{buildroot}/usr/lib/udev/rules.d/ |
708 | +mv %{buildroot}/lib/udev/rules.d/* %{buildroot}/usr/lib/udev/rules.d/ |
709 | |
710 | # Remove non-SUSE templates |
711 | rm %{buildroot}/%{_sysconfdir}/cloud/templates/*.debian.* |
712 | rm %{buildroot}/%{_sysconfdir}/cloud/templates/*.redhat.* |
713 | rm %{buildroot}/%{_sysconfdir}/cloud/templates/*.ubuntu.* |
714 | |
715 | -# Remove cloud-init tests |
716 | -rm -r %{buildroot}/%{python_sitelib}/tests |
717 | - |
718 | -# Move sysvinit scripts to the correct place and create symbolic links |
719 | -%if %{initsys} == sysvinit |
720 | - mkdir -p %{buildroot}/%{_initddir} |
721 | - mv %{buildroot}%{_sysconfdir}/rc.d/init.d/* %{buildroot}%{_initddir}/ |
722 | - rmdir %{buildroot}%{_sysconfdir}/rc.d/init.d |
723 | - rmdir %{buildroot}%{_sysconfdir}/rc.d |
724 | - |
725 | - mkdir -p %{buildroot}/%{_sbindir} |
726 | - pushd %{buildroot}/%{_initddir} |
727 | - for file in * ; do |
728 | - ln -s %{_initddir}/${file} %{buildroot}/%{_sbindir}/rc${file} |
729 | - done |
730 | - popd |
731 | -%endif |
732 | - |
733 | # Move documentation |
734 | mkdir -p %{buildroot}/%{_defaultdocdir} |
735 | mv %{buildroot}/usr/share/doc/cloud-init %{buildroot}/%{_defaultdocdir} |
736 | -for doc in TODO LICENSE ChangeLog requirements.txt; do |
737 | +for doc in LICENSE ChangeLog requirements.txt; do |
738 | cp ${doc} %{buildroot}/%{_defaultdocdir}/cloud-init |
739 | done |
740 | |
741 | @@ -114,24 +91,23 @@ version_pys=$(cd "%{buildroot}" && find . -name version.py -type f) |
742 | |
743 | %files |
744 | |
745 | -# Sysvinit scripts |
746 | -%if %{initsys} == sysvinit |
747 | - %attr(0755, root, root) %{_initddir}/cloud-config |
748 | - %attr(0755, root, root) %{_initddir}/cloud-final |
749 | - %attr(0755, root, root) %{_initddir}/cloud-init-local |
750 | - %attr(0755, root, root) %{_initddir}/cloud-init |
751 | - |
752 | - %{_sbindir}/rccloud-* |
753 | -%endif |
754 | - |
755 | # Program binaries |
756 | %{_bindir}/cloud-init* |
757 | |
758 | +# systemd files |
759 | +/usr/lib/systemd/system-generators/* |
760 | +/usr/lib/systemd/system/* |
761 | + |
762 | # There doesn't seem to be an agreed upon place for these |
763 | # although it appears the standard says /usr/lib but rpmbuild |
764 | # will try /usr/lib64 ?? |
765 | /usr/lib/%{name}/uncloud-init |
766 | /usr/lib/%{name}/write-ssh-key-fingerprints |
767 | +/usr/lib/%{name}/ds-identify |
768 | + |
769 | +# udev rules |
770 | +/usr/lib/udev/rules.d/66-azure-ephemeral.rules |
771 | + |
772 | |
773 | # Docs |
774 | %doc %{_defaultdocdir}/cloud-init/* |
775 | @@ -145,6 +121,9 @@ version_pys=$(cd "%{buildroot}" && find . -name version.py -type f) |
776 | %config(noreplace) %{_sysconfdir}/cloud/templates/* |
777 | %{_sysconfdir}/bash_completion.d/cloud-init |
778 | |
779 | +%{_sysconfdir}/dhcp/dhclient-exit-hooks.d/hook-dhclient |
780 | +%{_sysconfdir}/NetworkManager/dispatcher.d/hook-network-manager |
781 | + |
782 | # Python code is here... |
783 | %{python_sitelib}/* |
784 | |
785 | diff --git a/setup.py b/setup.py |
786 | index 85b2337..5ed8eae 100755 |
787 | --- a/setup.py |
788 | +++ b/setup.py |
789 | @@ -25,7 +25,7 @@ from distutils.errors import DistutilsArgError |
790 | import subprocess |
791 | |
792 | RENDERED_TMPD_PREFIX = "RENDERED_TEMPD" |
793 | - |
794 | +VARIANT = None |
795 | |
796 | def is_f(p): |
797 | return os.path.isfile(p) |
798 | @@ -114,10 +114,20 @@ def render_tmpl(template): |
799 | atexit.register(shutil.rmtree, tmpd) |
800 | bname = os.path.basename(template).rstrip(tmpl_ext) |
801 | fpath = os.path.join(tmpd, bname) |
802 | - tiny_p([sys.executable, './tools/render-cloudcfg', template, fpath]) |
803 | + if VARIANT: |
804 | + tiny_p([sys.executable, './tools/render-cloudcfg', '--variant', |
805 | + VARIANT, template, fpath]) |
806 | + else: |
807 | + tiny_p([sys.executable, './tools/render-cloudcfg', template, fpath]) |
808 | # return path relative to setup.py |
809 | return os.path.join(os.path.basename(tmpd), bname) |
810 | |
811 | +# User can set the variant for template rendering |
812 | +if '--distro' in sys.argv: |
813 | + idx = sys.argv.index('--distro') |
814 | + VARIANT = sys.argv[idx+1] |
815 | + del sys.argv[idx+1] |
816 | + sys.argv.remove('--distro') |
817 | |
818 | INITSYS_FILES = { |
819 | 'sysvinit': [f for f in glob('sysvinit/redhat/*') if is_f(f)], |
820 | @@ -260,7 +270,7 @@ requirements = read_requires() |
821 | setuptools.setup( |
822 | name='cloud-init', |
823 | version=get_version(), |
824 | - description='EC2 initialisation magic', |
825 | + description='Cloud instance initialisation magic', |
826 | author='Scott Moser', |
827 | author_email='scott.moser@canonical.com', |
828 | url='http://launchpad.net/cloud-init/', |
829 | @@ -277,4 +287,5 @@ setuptools.setup( |
830 | } |
831 | ) |
832 | |
833 | + |
834 | # vi: ts=4 expandtab |
835 | diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py |
836 | index 706e8eb..dca0b3d 100644 |
837 | --- a/tests/unittests/test_datasource/test_smartos.py |
838 | +++ b/tests/unittests/test_datasource/test_smartos.py |
839 | @@ -1027,6 +1027,32 @@ class TestNetworkConversion(TestCase): |
840 | found = convert_net(SDC_NICS_SINGLE_GATEWAY) |
841 | self.assertEqual(expected, found) |
842 | |
843 | + def test_routes_on_all_nics(self): |
844 | + routes = [ |
845 | + {'linklocal': False, 'dst': '3.0.0.0/8', 'gateway': '8.12.42.3'}, |
846 | + {'linklocal': False, 'dst': '4.0.0.0/8', 'gateway': '10.210.1.4'}] |
847 | + expected = { |
848 | + 'version': 1, |
849 | + 'config': [ |
850 | + {'mac_address': '90:b8:d0:d8:82:b4', 'mtu': 1500, |
851 | + 'name': 'net0', 'type': 'physical', |
852 | + 'subnets': [{'address': '8.12.42.26/24', |
853 | + 'gateway': '8.12.42.1', 'type': 'static', |
854 | + 'routes': [{'network': '3.0.0.0/8', |
855 | + 'gateway': '8.12.42.3'}, |
856 | + {'network': '4.0.0.0/8', |
857 | + 'gateway': '10.210.1.4'}]}]}, |
858 | + {'mac_address': '90:b8:d0:0a:51:31', 'mtu': 1500, |
859 | + 'name': 'net1', 'type': 'physical', |
860 | + 'subnets': [{'address': '10.210.1.27/24', 'type': 'static', |
861 | + 'routes': [{'network': '3.0.0.0/8', |
862 | + 'gateway': '8.12.42.3'}, |
863 | + {'network': '4.0.0.0/8', |
864 | + 'gateway': '10.210.1.4'}]}]}]} |
865 | + found = convert_net(SDC_NICS_SINGLE_GATEWAY, routes=routes) |
866 | + self.maxDiff = None |
867 | + self.assertEqual(expected, found) |
868 | + |
869 | |
870 | @unittest2.skipUnless(get_smartos_environ() == SMARTOS_ENV_KVM, |
871 | "Only supported on KVM and bhyve guests under SmartOS") |