Merge ~chad.smith/cloud-init:ubuntu/devel into cloud-init: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)
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)

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
1diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py
2index 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
14diff --git a/cloudinit/handlers/upstart_job.py b/cloudinit/handlers/upstart_job.py
15index 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:
27diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
28index 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:
63diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
64index 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)
85diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py
86index 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))
98diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
99index 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]
222diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
223index 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
327diff --git a/cloudinit/util.py b/cloudinit/util.py
328index 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
405diff --git a/debian/changelog b/debian/changelog
406index 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
430diff --git a/doc/rtd/topics/datasources.rst b/doc/rtd/topics/datasources.rst
431index 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
538diff --git a/doc/rtd/topics/datasources/cloudstack.rst b/doc/rtd/topics/datasources/cloudstack.rst
539index 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
585diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst
586index 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
624diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst
625index 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
653diff --git a/packages/suse/cloud-init.spec.in b/packages/suse/cloud-init.spec.in
654index 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
785diff --git a/setup.py b/setup.py
786index 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
835diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py
836index 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")

Subscribers

People subscribed via source and target branches