Changes include:
1. Adding new code changes / features for guestinfo datasource support.
2. Datasource name is changed from vmxguestinfo to VMwareGuestInfo in the
datasource_list of cloud config file in the latest release.
Change-Id: I4107797fe50ae2e179d0f2d9ac37520817034f62
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/7679
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: Anish Swaminathan <anishs@vmware.com>
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,470 @@ |
0 |
+diff --git a/cloudinit/sources/DataSourceVMwareGuestInfo.py b/cloudinit/sources/DataSourceVMwareGuestInfo.py |
|
1 |
+index e69de29..59dd685 100644 |
|
2 |
+--- a/cloudinit/sources/DataSourceVMwareGuestInfo.py |
|
3 |
+@@ -0,0 +1,465 @@ |
|
4 |
++# Cloud-Init Datasource for VMware Guestinfo |
|
5 |
++# |
|
6 |
++# Copyright (c) 2018 VMware, Inc. All Rights Reserved. |
|
7 |
++# |
|
8 |
++# This product is licensed to you under the Apache 2.0 license (the "License"). |
|
9 |
++# You may not use this product except in compliance with the Apache 2.0 License. |
|
10 |
++# |
|
11 |
++# This product may include a number of subcomponents with separate copyright |
|
12 |
++# notices and license terms. Your use of these subcomponents is subject to the |
|
13 |
++# terms and conditions of the subcomponent's license, as noted in the LICENSE |
|
14 |
++# file. |
|
15 |
++# |
|
16 |
++# Authors: Anish Swaminathan <anishs@vmware.com> |
|
17 |
++# Andrew Kutz <akutz@vmware.com> |
|
18 |
++# |
|
19 |
++ |
|
20 |
++''' |
|
21 |
++A cloud init datasource for VMware GuestInfo. |
|
22 |
++''' |
|
23 |
++ |
|
24 |
++import base64 |
|
25 |
++import collections |
|
26 |
++import copy |
|
27 |
++from distutils.spawn import find_executable |
|
28 |
++import json |
|
29 |
++import socket |
|
30 |
++import zlib |
|
31 |
++ |
|
32 |
++from cloudinit import log as logging |
|
33 |
++from cloudinit import sources |
|
34 |
++from cloudinit import util |
|
35 |
++from cloudinit import safeyaml |
|
36 |
++ |
|
37 |
++from deepmerge import always_merger |
|
38 |
++import netifaces |
|
39 |
++ |
|
40 |
++LOG = logging.getLogger(__name__) |
|
41 |
++NOVAL = "No value found" |
|
42 |
++VMTOOLSD = find_executable("vmtoolsd") |
|
43 |
++ |
|
44 |
++ |
|
45 |
++class NetworkConfigError(Exception): |
|
46 |
++ ''' |
|
47 |
++ NetworkConfigError is raised when there is an issue getting or |
|
48 |
++ applying network configuration. |
|
49 |
++ ''' |
|
50 |
++ pass |
|
51 |
++ |
|
52 |
++ |
|
53 |
++class DataSourceVMwareGuestInfo(sources.DataSource): |
|
54 |
++ ''' |
|
55 |
++ This cloud-init datasource was designed for use with CentOS 7, |
|
56 |
++ which uses cloud-init 0.7.9. However, this datasource should |
|
57 |
++ work with any Linux distribution for which cloud-init is |
|
58 |
++ avaialble. |
|
59 |
++ The documentation for cloud-init 0.7.9's datasource is |
|
60 |
++ available at http://bit.ly/cloudinit-datasource-0-7-9. The |
|
61 |
++ current documentation for cloud-init is found at |
|
62 |
++ https://cloudinit.readthedocs.io/en/latest/. |
|
63 |
++ Setting the hostname: |
|
64 |
++ The hostname is set by way of the metadata key "local-hostname". |
|
65 |
++ Setting the instance ID: |
|
66 |
++ The instance ID may be set by way of the metadata key "instance-id". |
|
67 |
++ However, if this value is absent then then the instance ID is |
|
68 |
++ read from the file /sys/class/dmi/id/product_uuid. |
|
69 |
++ Configuring the network: |
|
70 |
++ The network is configured by setting the metadata key "network" |
|
71 |
++ with a value consistent with Network Config Versions 1 or 2, |
|
72 |
++ depending on the Linux distro's version of cloud-init: |
|
73 |
++ Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1 |
|
74 |
++ Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2 |
|
75 |
++ For example, CentOS 7's official cloud-init package is version |
|
76 |
++ 0.7.9 and does not support Network Config Version 2. However, |
|
77 |
++ this datasource still supports supplying Network Config Version 2 |
|
78 |
++ data as long as the Linux distro's cloud-init package is new |
|
79 |
++ enough to parse the data. |
|
80 |
++ The metadata key "network.encoding" may be used to indicate the |
|
81 |
++ format of the metadata key "network". Valid encodings are base64 |
|
82 |
++ and gzip+base64. |
|
83 |
++ ''' |
|
84 |
++ |
|
85 |
++ dsname = 'VMwareGuestInfo' |
|
86 |
++ |
|
87 |
++ def __init__(self, sys_cfg, distro, paths, ud_proc=None): |
|
88 |
++ sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc) |
|
89 |
++ if not VMTOOLSD: |
|
90 |
++ LOG.error("Failed to find vmtoolsd") |
|
91 |
++ |
|
92 |
++ def get_data(self): |
|
93 |
++ """ |
|
94 |
++ This method should really be _get_data in accordance with the most |
|
95 |
++ recent versions of cloud-init. However, because the datasource |
|
96 |
++ supports as far back as cloud-init 0.7.9, get_data is still used. |
|
97 |
++ Because of this the method attempts to do some of the same things |
|
98 |
++ that the get_data functions in newer versions of cloud-init do, |
|
99 |
++ such as calling persist_instance_data. |
|
100 |
++ """ |
|
101 |
++ if not VMTOOLSD: |
|
102 |
++ LOG.error("vmtoolsd is required to fetch guestinfo value") |
|
103 |
++ return False |
|
104 |
++ |
|
105 |
++ # Get the metadata. |
|
106 |
++ self.metadata = load_metadata() |
|
107 |
++ |
|
108 |
++ # Get the user data. |
|
109 |
++ self.userdata_raw = guestinfo('userdata') |
|
110 |
++ |
|
111 |
++ # Get the vendor data. |
|
112 |
++ self.vendordata_raw = guestinfo('vendordata') |
|
113 |
++ |
|
114 |
++ return True |
|
115 |
++ |
|
116 |
++ def setup(self, is_new_instance): |
|
117 |
++ """setup(is_new_instance) |
|
118 |
++ This is called before user-data and vendor-data have been processed. |
|
119 |
++ Unless the datasource has set mode to 'local', then networking |
|
120 |
++ per 'fallback' or per 'network_config' will have been written and |
|
121 |
++ brought up the OS at this point. |
|
122 |
++ """ |
|
123 |
++ |
|
124 |
++ # Get information about the host. |
|
125 |
++ host_info = get_host_info() |
|
126 |
++ LOG.info("got host-info: %s", host_info) |
|
127 |
++ |
|
128 |
++ # Ensure the metadata gets updated with information about the |
|
129 |
++ # host, including the network interfaces, default IP addresses, |
|
130 |
++ # etc. |
|
131 |
++ self.metadata = always_merger.merge(self.metadata, host_info) |
|
132 |
++ |
|
133 |
++ # Persist the instance data for versions of cloud-init that support |
|
134 |
++ # doing so. This occurs here rather than in the get_data call in |
|
135 |
++ # order to ensure that the network interfaces are up and can be |
|
136 |
++ # persisted with the metadata. |
|
137 |
++ try: |
|
138 |
++ self.persist_instance_data() |
|
139 |
++ except AttributeError: |
|
140 |
++ pass |
|
141 |
++ |
|
142 |
++ @property |
|
143 |
++ def network_config(self): |
|
144 |
++ if 'network' in self.metadata: |
|
145 |
++ LOG.debug("using metadata network config") |
|
146 |
++ else: |
|
147 |
++ LOG.debug("using fallback network config") |
|
148 |
++ self.metadata['network'] = { |
|
149 |
++ 'config': self.distro.generate_fallback_config(), |
|
150 |
++ } |
|
151 |
++ return self.metadata['network']['config'] |
|
152 |
++ |
|
153 |
++ def get_instance_id(self): |
|
154 |
++ # Pull the instance ID out of the metadata if present. Otherwise |
|
155 |
++ # read the file /sys/class/dmi/id/product_uuid for the instance ID. |
|
156 |
++ if self.metadata and 'instance-id' in self.metadata: |
|
157 |
++ return self.metadata['instance-id'] |
|
158 |
++ with open('/sys/class/dmi/id/product_uuid', 'r') as id_file: |
|
159 |
++ self.metadata['instance-id'] = str(id_file.read()).rstrip() |
|
160 |
++ return self.metadata['instance-id'] |
|
161 |
++ |
|
162 |
++ |
|
163 |
++def decode(key, enc_type, data): |
|
164 |
++ ''' |
|
165 |
++ decode returns the decoded string value of data |
|
166 |
++ key is a string used to identify the data being decoded in log messages |
|
167 |
++ ---- |
|
168 |
++ In py 2.7: |
|
169 |
++ json.loads method takes string as input |
|
170 |
++ zlib.decompress takes and returns a string |
|
171 |
++ base64.b64decode takes and returns a string |
|
172 |
++ ----- |
|
173 |
++ In py 3.6 and newer: |
|
174 |
++ json.loads method takes bytes or string as input |
|
175 |
++ zlib.decompress takes and returns a bytes |
|
176 |
++ base64.b64decode takes bytes or string and returns bytes |
|
177 |
++ ----- |
|
178 |
++ In py > 3, < 3.6: |
|
179 |
++ json.loads method takes string as input |
|
180 |
++ zlib.decompress takes and returns a bytes |
|
181 |
++ base64.b64decode takes bytes or string and returns bytes |
|
182 |
++ ----- |
|
183 |
++ Given the above conditions the output from zlib.decompress and |
|
184 |
++ base64.b64decode would be bytes with newer python and str in older |
|
185 |
++ version. Thus we would covert the output to str before returning |
|
186 |
++ ''' |
|
187 |
++ LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type) |
|
188 |
++ |
|
189 |
++ raw_data = None |
|
190 |
++ if enc_type == "gzip+base64" or enc_type == "gz+b64": |
|
191 |
++ LOG.debug("Decoding %s format %s", enc_type, key) |
|
192 |
++ raw_data = zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16) |
|
193 |
++ elif enc_type == "base64" or enc_type == "b64": |
|
194 |
++ LOG.debug("Decoding %s format %s", enc_type, key) |
|
195 |
++ raw_data = base64.b64decode(data) |
|
196 |
++ else: |
|
197 |
++ LOG.debug("Plain-text data %s", key) |
|
198 |
++ raw_data = data |
|
199 |
++ |
|
200 |
++ if isinstance(raw_data, bytes): |
|
201 |
++ return raw_data.decode('utf-8') |
|
202 |
++ return raw_data |
|
203 |
++ |
|
204 |
++ |
|
205 |
++def get_guestinfo_value(key): |
|
206 |
++ ''' |
|
207 |
++ Returns a guestinfo value for the specified key. |
|
208 |
++ ''' |
|
209 |
++ LOG.debug("Getting guestinfo value for key %s", key) |
|
210 |
++ try: |
|
211 |
++ (stdout, stderr) = util.subp( |
|
212 |
++ [VMTOOLSD, "--cmd", "info-get guestinfo." + key]) |
|
213 |
++ if stderr == NOVAL: |
|
214 |
++ LOG.debug("No value found for key %s", key) |
|
215 |
++ elif not stdout: |
|
216 |
++ LOG.error("Failed to get guestinfo value for key %s", key) |
|
217 |
++ else: |
|
218 |
++ return stdout.rstrip() |
|
219 |
++ except util.ProcessExecutionError as error: |
|
220 |
++ if error.stderr == NOVAL: |
|
221 |
++ LOG.debug("No value found for key %s", key) |
|
222 |
++ else: |
|
223 |
++ util.logexc( |
|
224 |
++ LOG, "Failed to get guestinfo value for key %s: %s", key, error) |
|
225 |
++ except Exception: |
|
226 |
++ util.logexc( |
|
227 |
++ LOG, "Unexpected error while trying to get guestinfo value for key %s", key) |
|
228 |
++ return None |
|
229 |
++ |
|
230 |
++ |
|
231 |
++def guestinfo(key): |
|
232 |
++ ''' |
|
233 |
++ guestinfo returns the guestinfo value for the provided key, decoding |
|
234 |
++ the value when required |
|
235 |
++ ''' |
|
236 |
++ data = get_guestinfo_value(key) |
|
237 |
++ if not data: |
|
238 |
++ return None |
|
239 |
++ enc_type = get_guestinfo_value(key + '.encoding') |
|
240 |
++ return decode('guestinfo.' + key, enc_type, data) |
|
241 |
++ |
|
242 |
++ |
|
243 |
++def load(data): |
|
244 |
++ ''' |
|
245 |
++ load first attempts to unmarshal the provided data as JSON, and if |
|
246 |
++ that fails then attempts to unmarshal the data as YAML. If data is |
|
247 |
++ None then a new dictionary is returned. |
|
248 |
++ ''' |
|
249 |
++ if not data: |
|
250 |
++ return {} |
|
251 |
++ try: |
|
252 |
++ return json.loads(data) |
|
253 |
++ except: |
|
254 |
++ return safeyaml.load(data) |
|
255 |
++ |
|
256 |
++ |
|
257 |
++def load_metadata(): |
|
258 |
++ ''' |
|
259 |
++ load_metadata loads the metadata from the guestinfo data, optionally |
|
260 |
++ decoding the network config when required |
|
261 |
++ ''' |
|
262 |
++ data = load(guestinfo('metadata')) |
|
263 |
++ LOG.debug('loaded metadata %s', data) |
|
264 |
++ |
|
265 |
++ network = None |
|
266 |
++ if 'network' in data: |
|
267 |
++ network = data['network'] |
|
268 |
++ del data['network'] |
|
269 |
++ |
|
270 |
++ network_enc = None |
|
271 |
++ if 'network.encoding' in data: |
|
272 |
++ network_enc = data['network.encoding'] |
|
273 |
++ del data['network.encoding'] |
|
274 |
++ |
|
275 |
++ if network: |
|
276 |
++ LOG.debug('network data found') |
|
277 |
++ if isinstance(network, collections.Mapping): |
|
278 |
++ LOG.debug("network data copied to 'config' key") |
|
279 |
++ network = { |
|
280 |
++ 'config': copy.deepcopy(network) |
|
281 |
++ } |
|
282 |
++ else: |
|
283 |
++ LOG.debug("network data to be decoded %s", network) |
|
284 |
++ dec_net = decode('metadata.network', network_enc, network) |
|
285 |
++ network = { |
|
286 |
++ 'config': load(dec_net), |
|
287 |
++ } |
|
288 |
++ |
|
289 |
++ LOG.debug('network data %s', network) |
|
290 |
++ data['network'] = network |
|
291 |
++ |
|
292 |
++ return data |
|
293 |
++ |
|
294 |
++ |
|
295 |
++def get_datasource_list(depends): |
|
296 |
++ ''' |
|
297 |
++ Return a list of data sources that match this set of dependencies |
|
298 |
++ ''' |
|
299 |
++ return [DataSourceVMwareGuestInfo] |
|
300 |
++ |
|
301 |
++ |
|
302 |
++def get_default_ip_addrs(): |
|
303 |
++ ''' |
|
304 |
++ Returns the default IPv4 and IPv6 addresses based on the device(s) used for |
|
305 |
++ the default route. Please note that None may be returned for either address |
|
306 |
++ family if that family has no default route or if there are multiple |
|
307 |
++ addresses associated with the device used by the default route for a given |
|
308 |
++ address. |
|
309 |
++ ''' |
|
310 |
++ gateways = netifaces.gateways() |
|
311 |
++ if 'default' not in gateways: |
|
312 |
++ return None, None |
|
313 |
++ |
|
314 |
++ default_gw = gateways['default'] |
|
315 |
++ if netifaces.AF_INET not in default_gw and netifaces.AF_INET6 not in default_gw: |
|
316 |
++ return None, None |
|
317 |
++ |
|
318 |
++ ipv4 = None |
|
319 |
++ ipv6 = None |
|
320 |
++ |
|
321 |
++ gw4 = default_gw.get(netifaces.AF_INET) |
|
322 |
++ if gw4: |
|
323 |
++ _, dev4 = gw4 |
|
324 |
++ addr4_fams = netifaces.ifaddresses(dev4) |
|
325 |
++ if addr4_fams: |
|
326 |
++ af_inet4 = addr4_fams.get(netifaces.AF_INET) |
|
327 |
++ if af_inet4: |
|
328 |
++ if len(af_inet4) > 1: |
|
329 |
++ LOG.warn( |
|
330 |
++ "device %s has more than one ipv4 address: %s", dev4, af_inet4) |
|
331 |
++ elif 'addr' in af_inet4[0]: |
|
332 |
++ ipv4 = af_inet4[0]['addr'] |
|
333 |
++ |
|
334 |
++ # Try to get the default IPv6 address by first seeing if there is a default |
|
335 |
++ # IPv6 route. |
|
336 |
++ gw6 = default_gw.get(netifaces.AF_INET6) |
|
337 |
++ if gw6: |
|
338 |
++ _, dev6 = gw6 |
|
339 |
++ addr6_fams = netifaces.ifaddresses(dev6) |
|
340 |
++ if addr6_fams: |
|
341 |
++ af_inet6 = addr6_fams.get(netifaces.AF_INET6) |
|
342 |
++ if af_inet6: |
|
343 |
++ if len(af_inet6) > 1: |
|
344 |
++ LOG.warn( |
|
345 |
++ "device %s has more than one ipv6 address: %s", dev6, af_inet6) |
|
346 |
++ elif 'addr' in af_inet6[0]: |
|
347 |
++ ipv6 = af_inet6[0]['addr'] |
|
348 |
++ |
|
349 |
++ # If there is a default IPv4 address but not IPv6, then see if there is a |
|
350 |
++ # single IPv6 address associated with the same device associated with the |
|
351 |
++ # default IPv4 address. |
|
352 |
++ if ipv4 and not ipv6: |
|
353 |
++ af_inet6 = addr4_fams.get(netifaces.AF_INET6) |
|
354 |
++ if af_inet6: |
|
355 |
++ if len(af_inet6) > 1: |
|
356 |
++ LOG.warn( |
|
357 |
++ "device %s has more than one ipv6 address: %s", dev4, af_inet6) |
|
358 |
++ elif 'addr' in af_inet6[0]: |
|
359 |
++ ipv6 = af_inet6[0]['addr'] |
|
360 |
++ |
|
361 |
++ # If there is a default IPv6 address but not IPv4, then see if there is a |
|
362 |
++ # single IPv4 address associated with the same device associated with the |
|
363 |
++ # default IPv6 address. |
|
364 |
++ if not ipv4 and ipv6: |
|
365 |
++ af_inet4 = addr6_fams.get(netifaces.AF_INET4) |
|
366 |
++ if af_inet4: |
|
367 |
++ if len(af_inet4) > 1: |
|
368 |
++ LOG.warn( |
|
369 |
++ "device %s has more than one ipv4 address: %s", dev6, af_inet4) |
|
370 |
++ elif 'addr' in af_inet4[0]: |
|
371 |
++ ipv4 = af_inet4[0]['addr'] |
|
372 |
++ |
|
373 |
++ return ipv4, ipv6 |
|
374 |
++ |
|
375 |
++ |
|
376 |
++def get_host_info(): |
|
377 |
++ ''' |
|
378 |
++ Returns host information such as the host name and network interfaces. |
|
379 |
++ ''' |
|
380 |
++ |
|
381 |
++ host_info = { |
|
382 |
++ 'network': { |
|
383 |
++ 'interfaces': { |
|
384 |
++ 'by-mac': collections.OrderedDict(), |
|
385 |
++ 'by-ipv4': collections.OrderedDict(), |
|
386 |
++ 'by-ipv6': collections.OrderedDict(), |
|
387 |
++ }, |
|
388 |
++ }, |
|
389 |
++ } |
|
390 |
++ |
|
391 |
++ hostname = socket.getfqdn() |
|
392 |
++ if hostname: |
|
393 |
++ host_info['hostname'] = hostname |
|
394 |
++ host_info['local-hostname'] = hostname |
|
395 |
++ |
|
396 |
++ default_ipv4, default_ipv6 = get_default_ip_addrs() |
|
397 |
++ if default_ipv4: |
|
398 |
++ host_info['local-ipv4'] = default_ipv4 |
|
399 |
++ if default_ipv6: |
|
400 |
++ host_info['local-ipv6'] = default_ipv6 |
|
401 |
++ |
|
402 |
++ by_mac = host_info['network']['interfaces']['by-mac'] |
|
403 |
++ by_ipv4 = host_info['network']['interfaces']['by-ipv4'] |
|
404 |
++ by_ipv6 = host_info['network']['interfaces']['by-ipv6'] |
|
405 |
++ |
|
406 |
++ ifaces = netifaces.interfaces() |
|
407 |
++ for dev_name in ifaces: |
|
408 |
++ addr_fams = netifaces.ifaddresses(dev_name) |
|
409 |
++ af_link = addr_fams.get(netifaces.AF_LINK) |
|
410 |
++ af_inet4 = addr_fams.get(netifaces.AF_INET) |
|
411 |
++ af_inet6 = addr_fams.get(netifaces.AF_INET6) |
|
412 |
++ |
|
413 |
++ mac = None |
|
414 |
++ if af_link and 'addr' in af_link[0]: |
|
415 |
++ mac = af_link[0]['addr'] |
|
416 |
++ |
|
417 |
++ # Do not bother recording localhost |
|
418 |
++ if mac == "00:00:00:00:00:00": |
|
419 |
++ continue |
|
420 |
++ |
|
421 |
++ if mac and (af_inet4 or af_inet6): |
|
422 |
++ key = mac |
|
423 |
++ val = {} |
|
424 |
++ if af_inet4: |
|
425 |
++ val["ipv4"] = af_inet4 |
|
426 |
++ if af_inet6: |
|
427 |
++ val["ipv6"] = af_inet6 |
|
428 |
++ by_mac[key] = val |
|
429 |
++ |
|
430 |
++ if af_inet4: |
|
431 |
++ for ip_info in af_inet4: |
|
432 |
++ key = ip_info['addr'] |
|
433 |
++ if key == '127.0.0.1': |
|
434 |
++ continue |
|
435 |
++ val = copy.deepcopy(ip_info) |
|
436 |
++ del val['addr'] |
|
437 |
++ if mac: |
|
438 |
++ val['mac'] = mac |
|
439 |
++ by_ipv4[key] = val |
|
440 |
++ |
|
441 |
++ if af_inet6: |
|
442 |
++ for ip_info in af_inet6: |
|
443 |
++ key = ip_info['addr'] |
|
444 |
++ if key == '::1': |
|
445 |
++ continue |
|
446 |
++ val = copy.deepcopy(ip_info) |
|
447 |
++ del val['addr'] |
|
448 |
++ if mac: |
|
449 |
++ val['mac'] = mac |
|
450 |
++ by_ipv6[key] = val |
|
451 |
++ |
|
452 |
++ return host_info |
|
453 |
++ |
|
454 |
++ |
|
455 |
++def main(): |
|
456 |
++ ''' |
|
457 |
++ Executed when this file is used as a program. |
|
458 |
++ ''' |
|
459 |
++ metadata = {'network': {'config': {'dhcp': True}}} |
|
460 |
++ host_info = get_host_info() |
|
461 |
++ metadata = always_merger.merge(metadata, host_info) |
|
462 |
++ print(util.json_dumps(metadata)) |
|
463 |
++ |
|
464 |
++ |
|
465 |
++if __name__ == "__main__": |
|
466 |
++ main() |
|
467 |
++ |
|
468 |
++# vi: ts=4 expandtab |
... | ... |
@@ -2,7 +2,7 @@ |
2 | 2 |
|
3 | 3 |
Name: cloud-init |
4 | 4 |
Version: 19.1 |
5 |
-Release: 1%{?dist} |
|
5 |
+Release: 2%{?dist} |
|
6 | 6 |
Summary: Cloud instance init scripts |
7 | 7 |
Group: System Environment/Base |
8 | 8 |
License: GPLv3 |
... | ... |
@@ -13,16 +13,18 @@ Source0: https://launchpad.net/cloud-init/trunk/%{version}/+download/%{na |
13 | 13 |
%define sha1 cloud-init=6de398dd755959dde47c8d6f6e255a0857017c44 |
14 | 14 |
Source1: cloud-photon.cfg |
15 | 15 |
Source2: 99-disable-networking-config.cfg |
16 |
+Source3: dscheck_VMwareGuestInfo |
|
16 | 17 |
|
17 | 18 |
Patch0: photon-distro.patch |
18 | 19 |
Patch2: vca-admin-pwd.patch |
19 | 20 |
Patch3: photon-hosts-template.patch |
20 |
-Patch5: datasource-guestinfo.patch |
|
21 |
+Patch5: DataSourceVMwareGuestInfo.patch |
|
21 | 22 |
Patch6: systemd-service-changes.patch |
22 | 23 |
Patch7: makecheck.patch |
23 | 24 |
Patch8: systemd-resolved-config.patch |
24 | 25 |
Patch9: cloud-init-azureds.patch |
25 | 26 |
Patch10: ds-identity.patch |
27 |
+Patch11: ds-guestinfo-photon.patch |
|
26 | 28 |
|
27 | 29 |
BuildRequires: python3 |
28 | 30 |
BuildRequires: python3-libs |
... | ... |
@@ -60,6 +62,8 @@ Requires: python3-six |
60 | 60 |
Requires: python3-setuptools |
61 | 61 |
Requires: python3-xml |
62 | 62 |
Requires: python3-jsonschema |
63 |
+Requires: python3-deepmerge |
|
64 |
+Requires: python3-netifaces |
|
63 | 65 |
BuildArch: noarch |
64 | 66 |
|
65 | 67 |
%description |
... | ... |
@@ -79,6 +83,7 @@ ssh keys and to let the user run various scripts. |
79 | 79 |
%patch8 -p1 |
80 | 80 |
%patch9 -p1 |
81 | 81 |
%patch10 -p1 |
82 |
+%patch11 -p1 |
|
82 | 83 |
|
83 | 84 |
find systemd -name "cloud*.service*" | xargs sed -i s/StandardOutput=journal+console/StandardOutput=journal/g |
84 | 85 |
|
... | ... |
@@ -97,6 +102,7 @@ cp -p %{SOURCE1} %{buildroot}/%{_sysconfdir}/cloud/cloud.cfg |
97 | 97 |
|
98 | 98 |
# Disable networking config by cloud-init |
99 | 99 |
cp -p %{SOURCE2} $RPM_BUILD_ROOT/%{_sysconfdir}/cloud/cloud.cfg.d/ |
100 |
+install -m 755 %{SOURCE3} $RPM_BUILD_ROOT/%{_bindir}/ |
|
100 | 101 |
|
101 | 102 |
%check |
102 | 103 |
easy_install_3=$(ls /usr/bin |grep easy_install |grep 3) |
... | ... |
@@ -143,10 +149,13 @@ rm -rf $RPM_BUILD_ROOT |
143 | 143 |
%{python3_sitelib}/* |
144 | 144 |
%{_bindir}/cloud-init* |
145 | 145 |
%{_bindir}/cloud-id |
146 |
+%{_bindir}/dscheck_VMwareGuestInfo |
|
146 | 147 |
%{_datadir}/bash-completion/completions/cloud-init |
147 | 148 |
%dir /var/lib/cloud |
148 | 149 |
|
149 | 150 |
%changelog |
151 |
+* Tue Jul 23 2019 Keerthana K <keerthanak@vmware.com> 19.1-2 |
|
152 |
+- support for additional features in VMGuestInfo Datasource. |
|
150 | 153 |
* Tue Jun 25 2019 Keerthana K <keerthanak@vmware.com> 19.1-1 |
151 | 154 |
- Upgrade to version 19.1 and fix cloud-init GOS logic. |
152 | 155 |
* Thu Jun 13 2019 Keerthana K <keerthanak@vmware.com> 18.3-4 |
39 | 39 |
deleted file mode 100644 |
... | ... |
@@ -1,149 +0,0 @@ |
1 |
-diff -rupN cloud-init-0.7.9/cloudinit/sources/DataSourceVmxGuestinfo.py cloud-init-0.7.9-new/cloudinit/sources/DataSourceVmxGuestinfo.py |
|
2 |
-+++ cloud-init-0.7.9-new/cloudinit/sources/DataSourceVmxGuestinfo.py 2017-05-08 07:47:27.388662680 -0700 |
|
3 |
-@@ -0,0 +1,145 @@ |
|
4 |
-+# vi: ts=4 expandtab |
|
5 |
-+# |
|
6 |
-+# Copyright (C) 2017 VMware Inc. |
|
7 |
-+# |
|
8 |
-+# Author: Anish Swaminathan <anishs@vmware.com> |
|
9 |
-+# |
|
10 |
-+import os |
|
11 |
-+import base64 |
|
12 |
-+ |
|
13 |
-+from cloudinit import log as logging |
|
14 |
-+from cloudinit import sources |
|
15 |
-+from cloudinit import util |
|
16 |
-+ |
|
17 |
-+from distutils.spawn import find_executable |
|
18 |
-+ |
|
19 |
-+LOG = logging.getLogger(__name__) |
|
20 |
-+ |
|
21 |
-+class DataSourceVmxGuestinfo(sources.DataSource): |
|
22 |
-+ def __init__(self, sys_cfg, distro, paths, ud_proc=None): |
|
23 |
-+ sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc) |
|
24 |
-+ self.metadata = {} |
|
25 |
-+ self.userdata_raw = '' |
|
26 |
-+ self.vmtoolsd = find_executable("vmtoolsd") |
|
27 |
-+ if not self.vmtoolsd: |
|
28 |
-+ LOG.error("Failed to find vmtoolsd") |
|
29 |
-+ |
|
30 |
-+ def get_data(self): |
|
31 |
-+ if not self.vmtoolsd: |
|
32 |
-+ LOG.error("vmtoolsd is required to fetch guestinfo value") |
|
33 |
-+ return False |
|
34 |
-+ hostname = self._get_guestinfo_value('hostname') |
|
35 |
-+ if hostname: |
|
36 |
-+ self.distro.set_hostname(hostname) |
|
37 |
-+ ud = self._get_guestinfo_value('userdata') |
|
38 |
-+ if ud: |
|
39 |
-+ LOG.debug("Decoding base64 format guestinfo.userdata") |
|
40 |
-+ self.userdata_raw = base64.b64decode(ud) |
|
41 |
-+ found = True |
|
42 |
-+ dev_index = 0 |
|
43 |
-+ network_settings = '' |
|
44 |
-+ while found: |
|
45 |
-+ key_begin = 'interface.' + str(dev_index) |
|
46 |
-+ key_iname = key_begin + '.name' |
|
47 |
-+ interface_name = self._get_guestinfo_value(key_iname) |
|
48 |
-+ if interface_name: |
|
49 |
-+ network_settings += 'auto ' + interface_name + '\n' |
|
50 |
-+ network_settings += 'iface ' + interface_name |
|
51 |
-+ key_proto = key_begin + '.dhcp' |
|
52 |
-+ dhcp_enabled = self._get_guestinfo_value(key_proto) |
|
53 |
-+ key_address = key_begin + '.address' |
|
54 |
-+ address = self._get_guestinfo_value(key_address) |
|
55 |
-+ bootproto = 'dhcp' |
|
56 |
-+ if dhcp_enabled: |
|
57 |
-+ if dhcp_enabled == 'yes': |
|
58 |
-+ network_settings += ' dhcp\n' |
|
59 |
-+ elif dhcp_enabled == 'no': |
|
60 |
-+ network_settings += ' static\n' |
|
61 |
-+ bootproto = 'static' |
|
62 |
-+ else: |
|
63 |
-+ LOG.warning("Invalid value for yes/no parameter for %s, setting to dhcp", key_proto) |
|
64 |
-+ elif address: |
|
65 |
-+ bootproto = 'static' |
|
66 |
-+ dhcp_enabled == 'no' |
|
67 |
-+ network_settings += ' static\n' |
|
68 |
-+ else: |
|
69 |
-+ dhcp_enabled == 'yes' |
|
70 |
-+ network_settings += ' dhcp\n' |
|
71 |
-+ LOG.debug("Setting network bootproto to dhcp by default") |
|
72 |
-+ key_mac = key_begin + '.mac' |
|
73 |
-+ mac = self._get_guestinfo_value(key_mac) |
|
74 |
-+ if address: |
|
75 |
-+ network_settings += 'address ' + address + '\n' |
|
76 |
-+ if mac: |
|
77 |
-+ network_settings += 'hwaddress ' + mac + '\n' |
|
78 |
-+ key_netmask = key_begin + '.netmask' |
|
79 |
-+ netmask = self._get_guestinfo_value(key_netmask) |
|
80 |
-+ if netmask: |
|
81 |
-+ network_settings += 'netmask ' + netmask + '\n' |
|
82 |
-+ key_dnsserver = 'dns.servers' |
|
83 |
-+ dnsserver = self._get_guestinfo_value(key_dnsserver) |
|
84 |
-+ if dnsserver: |
|
85 |
-+ network_settings += 'dns-nameservers ' |
|
86 |
-+ dnsserver = dnsserver.split(',') |
|
87 |
-+ for d in dnsserver: |
|
88 |
-+ network_settings += d + ' ' |
|
89 |
-+ network_settings += '\n' |
|
90 |
-+ key_dnsdomain = 'dns.domains' |
|
91 |
-+ dnsdomain = self._get_guestinfo_value(key_dnsdomain) |
|
92 |
-+ if dnsdomain: |
|
93 |
-+ network_settings += 'dns-search ' |
|
94 |
-+ dnsdomain = dnsdomain.split(',') |
|
95 |
-+ for d in dnsdomain: |
|
96 |
-+ network_settings += d + ' ' |
|
97 |
-+ network_settings += '\n' |
|
98 |
-+ route_index = 0 |
|
99 |
-+ default_destination_set = False |
|
100 |
-+ while True: |
|
101 |
-+ key_route = key_begin + '.route.' + str(route_index) |
|
102 |
-+ route = self._get_guestinfo_value(key_route) |
|
103 |
-+ if route: |
|
104 |
-+ network_settings += "routes.%s " % (route_index) |
|
105 |
-+ route = route.split(',') |
|
106 |
-+ if len(route) > 2: |
|
107 |
-+ LOG.debug("Route information for %s route in %s device incorrect - ", |
|
108 |
-+ "expected 2 values", route_index, dev_index) |
|
109 |
-+ continue |
|
110 |
-+ elif len(route) == 2: |
|
111 |
-+ network_settings += route[0] + ' ' + route[1] + '\n'# Gateway Destination |
|
112 |
-+ else: #length = 1 |
|
113 |
-+ if not default_destination_set: |
|
114 |
-+ network_settings += route[0] + ' 0.0.0.0/0' + '\n' |
|
115 |
-+ default_destination_set = True |
|
116 |
-+ else: |
|
117 |
-+ LOG.debug("Default destination set previously, not setting route %s", route_index) |
|
118 |
-+ else: |
|
119 |
-+ break |
|
120 |
-+ route_index += 1 |
|
121 |
-+ else: |
|
122 |
-+ found = False |
|
123 |
-+ dev_index += 1 |
|
124 |
-+ self.distro.apply_network(network_settings, False) |
|
125 |
-+ return True |
|
126 |
-+ |
|
127 |
-+ def _get_guestinfo_value(self, key): |
|
128 |
-+ LOG.debug("Getting guestinfo value for key %s", key) |
|
129 |
-+ value = '' |
|
130 |
-+ try: |
|
131 |
-+ (value, _err) = util.subp([self.vmtoolsd, "--cmd", "info-get guestinfo." + key]) |
|
132 |
-+ if _err: |
|
133 |
-+ LOG.error("Failed to get guestinfo value for key %s", key) |
|
134 |
-+ except util.ProcessExecutionError as error: |
|
135 |
-+ util.logexc(LOG,"Failed to get guestinfo value for key %s: %s", key, error) |
|
136 |
-+ except Exception: |
|
137 |
-+ util.logexc(LOG,"Unexpected error while trying to get guestinfo value for key %s", key) |
|
138 |
-+ return value.rstrip() |
|
139 |
-+ |
|
140 |
-+ def get_instance_id(self): |
|
141 |
-+ with open('/sys/class/dmi/id/product_uuid', 'r') as id_file: |
|
142 |
-+ return str(id_file.read()).rstrip() |
|
143 |
-+ |
|
144 |
-+def get_datasource_list(depends): |
|
145 |
-+ """ |
|
146 |
-+ Return a list of data sources that match this set of dependencies |
|
147 |
-+ """ |
|
148 |
-+ return [DataSourceVmxGuestinfo] |
149 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,99 @@ |
0 |
+diff --git a/cloudinit/sources/DataSourceVMwareGuestInfo.py b/cloudinit/sources/DataSourceVMwareGuestInfo.py |
|
1 |
+index e69de29..59dd685 100644 |
|
2 |
+--- a/cloudinit/sources/DataSourceVMwareGuestInfo.py |
|
3 |
+@@ -108,6 +108,94 @@ |
|
4 |
+ # Get the vendor data. |
|
5 |
+ self.vendordata_raw = guestinfo('vendordata') |
|
6 |
+ |
|
7 |
++ hostname = get_guestinfo_value('hostname') |
|
8 |
++ if hostname: |
|
9 |
++ self.distro.set_hostname(hostname) |
|
10 |
++ found = True |
|
11 |
++ dev_index = 0 |
|
12 |
++ network_settings = '' |
|
13 |
++ while found: |
|
14 |
++ key_begin = 'interface.' + str(dev_index) |
|
15 |
++ key_iname = key_begin + '.name' |
|
16 |
++ interface_name = get_guestinfo_value(key_iname) |
|
17 |
++ if interface_name: |
|
18 |
++ network_settings += 'auto ' + interface_name + '\n' |
|
19 |
++ network_settings += 'iface ' + interface_name |
|
20 |
++ key_proto = key_begin + '.dhcp' |
|
21 |
++ dhcp_enabled = get_guestinfo_value(key_proto) |
|
22 |
++ key_address = key_begin + '.address' |
|
23 |
++ address = get_guestinfo_value(key_address) |
|
24 |
++ bootproto = 'dhcp' |
|
25 |
++ if dhcp_enabled: |
|
26 |
++ if dhcp_enabled == 'yes': |
|
27 |
++ network_settings += ' dhcp\n' |
|
28 |
++ elif dhcp_enabled == 'no': |
|
29 |
++ network_settings += ' static\n' |
|
30 |
++ bootproto = 'static' |
|
31 |
++ else: |
|
32 |
++ LOG.warning("Invalid value for yes/no parameter for %s, setting to dhcp", key_proto) |
|
33 |
++ elif address: |
|
34 |
++ bootproto = 'static' |
|
35 |
++ dhcp_enabled == 'no' |
|
36 |
++ network_settings += ' static\n' |
|
37 |
++ else: |
|
38 |
++ dhcp_enabled == 'yes' |
|
39 |
++ network_settings += ' dhcp\n' |
|
40 |
++ LOG.debug("Setting network bootproto to dhcp by default") |
|
41 |
++ key_mac = key_begin + '.mac' |
|
42 |
++ mac = get_guestinfo_value(key_mac) |
|
43 |
++ if address: |
|
44 |
++ network_settings += 'address ' + address + '\n' |
|
45 |
++ if mac: |
|
46 |
++ network_settings += 'hwaddress ' + mac + '\n' |
|
47 |
++ key_netmask = key_begin + '.netmask' |
|
48 |
++ netmask = get_guestinfo_value(key_netmask) |
|
49 |
++ if netmask: |
|
50 |
++ network_settings += 'netmask ' + netmask + '\n' |
|
51 |
++ key_dnsserver = 'dns.servers' |
|
52 |
++ dnsserver = get_guestinfo_value(key_dnsserver) |
|
53 |
++ if dnsserver: |
|
54 |
++ network_settings += 'dns-nameservers ' |
|
55 |
++ dnsserver = dnsserver.split(',') |
|
56 |
++ for d in dnsserver: |
|
57 |
++ network_settings += d + ' ' |
|
58 |
++ network_settings += '\n' |
|
59 |
++ key_dnsdomain = 'dns.domains' |
|
60 |
++ dnsdomain = get_guestinfo_value(key_dnsdomain) |
|
61 |
++ if dnsdomain: |
|
62 |
++ network_settings += 'dns-search ' |
|
63 |
++ dnsdomain = dnsdomain.split(',') |
|
64 |
++ for d in dnsdomain: |
|
65 |
++ network_settings += d + ' ' |
|
66 |
++ network_settings += '\n' |
|
67 |
++ route_index = 0 |
|
68 |
++ default_destination_set = False |
|
69 |
++ while True: |
|
70 |
++ key_route = key_begin + '.route.' + str(route_index) |
|
71 |
++ route = get_guestinfo_value(key_route) |
|
72 |
++ if route: |
|
73 |
++ network_settings += "routes.%s " % (route_index) |
|
74 |
++ route = route.split(',') |
|
75 |
++ if len(route) > 2: |
|
76 |
++ LOG.debug("Route information for %s route in %s device incorrect - ", |
|
77 |
++ "expected 2 values", route_index, dev_index) |
|
78 |
++ continue |
|
79 |
++ elif len(route) == 2: |
|
80 |
++ network_settings += route[0] + ' ' + route[1] + '\n'# Gateway Destination |
|
81 |
++ else: #length = 1 |
|
82 |
++ if not default_destination_set: |
|
83 |
++ network_settings += route[0] + ' 0.0.0.0/0' + '\n' |
|
84 |
++ default_destination_set = True |
|
85 |
++ else: |
|
86 |
++ LOG.debug("Default destination set previously, not setting route %s", route_index) |
|
87 |
++ else: |
|
88 |
++ break |
|
89 |
++ route_index += 1 |
|
90 |
++ else: |
|
91 |
++ found = False |
|
92 |
++ dev_index += 1 |
|
93 |
++ self.distro.apply_network(network_settings, False) |
|
94 |
++ |
|
95 |
+ return True |
|
96 |
+ |
|
97 |
+ def setup(self, is_new_instance): |
0 | 98 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,46 @@ |
0 |
+#!/bin/sh |
|
1 |
+ |
|
2 |
+# Cloud-Init Datasource for VMware Guestinfo |
|
3 |
+# |
|
4 |
+# Copyright (c) 2019 VMware, Inc. All Rights Reserved. |
|
5 |
+# |
|
6 |
+# This product is licensed to you under the Apache 2.0 license (the "License"). |
|
7 |
+# You may not use this product except in compliance with the Apache 2.0 License. |
|
8 |
+# |
|
9 |
+# This product may include a number of subcomponents with separate copyright |
|
10 |
+# notices and license terms. Your use of these subcomponents is subject to the |
|
11 |
+# terms and conditions of the subcomponent's license, as noted in the LICENSE |
|
12 |
+# file. |
|
13 |
+ |
|
14 |
+# |
|
15 |
+# This file should be installed to /usr/bin/dscheck_VMwareGuestInfo |
|
16 |
+# without the ".sh" extension. The extension only exists to make it easier |
|
17 |
+# to identify the file during development. |
|
18 |
+# |
|
19 |
+# This file provides cloud-init's ds-identify program a shell type that |
|
20 |
+# can be resolved with "type dscheck_VMwareGuestInfo" and used to validate |
|
21 |
+# where a datasource is installed and useable. |
|
22 |
+# |
|
23 |
+# Cloud-init's ds-identify program in /usr/lib/cloud-init includes functions |
|
24 |
+# to determine whether or not datasources can be used. Because the program |
|
25 |
+# is a shell script and uses "type dscheck_DATASOURCE_NAME" to determine |
|
26 |
+# if there is a matching bash type that can answer for the datasource, |
|
27 |
+# it's possible to respond with an external script. While other datasources |
|
28 |
+# have functions in ds-identify, the "type" command looks up types both |
|
29 |
+# in Bash's function table as well as script in the PATH. Therefore the |
|
30 |
+# ds-identify program, when looking up whether or not the datasource |
|
31 |
+# VMwareGuestInfo can be used, will defer to this file when it is in the |
|
32 |
+# PATH and named dscheck_VMwareGuestInfo. |
|
33 |
+# |
|
34 |
+ |
|
35 |
+if ! command -v vmtoolsd >/dev/null 2>&1; then |
|
36 |
+ exit 1 |
|
37 |
+fi |
|
38 |
+ |
|
39 |
+if { vmtoolsd --cmd "info-get guestinfo.metadata" || \ |
|
40 |
+ vmtoolsd --cmd "info-get guestinfo.userdata" || \ |
|
41 |
+ vmtoolsd --cmd "info-get guestinfo.vendordata"; } >/dev/null 2>&1; then |
|
42 |
+ exit 0 |
|
43 |
+fi |
|
44 |
+ |
|
45 |
+exit 1 |