blob: ab392be753a90415aff6f948a6737e2f85e4ff4b [file] [log] [blame]
akutzdd794a42018-09-18 10:04:21 -05001# Cloud-Init Datasource for VMware Guestinfo
2#
3# Copyright (c) 2018 VMware, Inc. All Rights Reserved.
4#
5# This product is licensed to you under the Apache 2.0 license (the "License").
6# You may not use this product except in compliance with the Apache 2.0 License.
7#
8# This product may include a number of subcomponents with separate copyright
9# notices and license terms. Your use of these subcomponents is subject to the
10# terms and conditions of the subcomponent's license, as noted in the LICENSE
11# file.
akutz77457a62018-08-22 16:07:21 -050012#
akutz6501f902018-08-24 12:19:05 -050013# Authors: Anish Swaminathan <anishs@vmware.com>
14# Andrew Kutz <akutz@vmware.com>
akutz77457a62018-08-22 16:07:21 -050015#
akutz0d1fce52019-06-01 18:54:29 -050016
17'''
18A cloud init datasource for VMware GuestInfo.
19'''
20
akutz77457a62018-08-22 16:07:21 -050021import base64
akutzffc4dd52019-06-02 11:34:55 -050022import collections
akutz84389c82019-06-02 19:24:38 -050023import copy
akutz0d1fce52019-06-01 18:54:29 -050024from distutils.spawn import find_executable
akutzffc4dd52019-06-02 11:34:55 -050025import json
akutz10cd1402019-10-23 18:06:39 -050026import os
akutzffc4dd52019-06-02 11:34:55 -050027import socket
akutz10cd1402019-10-23 18:06:39 -050028import string
akutzffc4dd52019-06-02 11:34:55 -050029import zlib
akutz77457a62018-08-22 16:07:21 -050030
31from cloudinit import log as logging
32from cloudinit import sources
33from cloudinit import util
akutz6501f902018-08-24 12:19:05 -050034from cloudinit import safeyaml
akutz77457a62018-08-22 16:07:21 -050035
akutzffc4dd52019-06-02 11:34:55 -050036from deepmerge import always_merger
37import netifaces
38
akutz77457a62018-08-22 16:07:21 -050039LOG = logging.getLogger(__name__)
akutz0d1fce52019-06-01 18:54:29 -050040NOVAL = "No value found"
yvespp8d58dfd2019-10-29 20:42:09 +010041VMWARE_RPCTOOL = find_executable("vmware-rpctool")
akutz10cd1402019-10-23 18:06:39 -050042VMX_GUESTINFO = "VMX_GUESTINFO"
akutzdaf81f12019-12-08 11:20:33 -060043GUESTINFO_EMPTY_YAML_VAL = "---"
akutz77457a62018-08-22 16:07:21 -050044
akutz0d1fce52019-06-01 18:54:29 -050045
46class NetworkConfigError(Exception):
47 '''
48 NetworkConfigError is raised when there is an issue getting or
49 applying network configuration.
50 '''
51 pass
52
53
Andrew Kutz4f66b8b2018-09-16 18:28:59 -050054class DataSourceVMwareGuestInfo(sources.DataSource):
akutz0d1fce52019-06-01 18:54:29 -050055 '''
56 This cloud-init datasource was designed for use with CentOS 7,
57 which uses cloud-init 0.7.9. However, this datasource should
58 work with any Linux distribution for which cloud-init is
59 avaialble.
60
61 The documentation for cloud-init 0.7.9's datasource is
62 available at http://bit.ly/cloudinit-datasource-0-7-9. The
63 current documentation for cloud-init is found at
64 https://cloudinit.readthedocs.io/en/latest/.
65
66 Setting the hostname:
67 The hostname is set by way of the metadata key "local-hostname".
68
69 Setting the instance ID:
70 The instance ID may be set by way of the metadata key "instance-id".
71 However, if this value is absent then then the instance ID is
72 read from the file /sys/class/dmi/id/product_uuid.
73
74 Configuring the network:
75 The network is configured by setting the metadata key "network"
76 with a value consistent with Network Config Versions 1 or 2,
77 depending on the Linux distro's version of cloud-init:
78
79 Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1
80 Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2
81
82 For example, CentOS 7's official cloud-init package is version
83 0.7.9 and does not support Network Config Version 2. However,
84 this datasource still supports supplying Network Config Version 2
85 data as long as the Linux distro's cloud-init package is new
86 enough to parse the data.
87
88 The metadata key "network.encoding" may be used to indicate the
89 format of the metadata key "network". Valid encodings are base64
90 and gzip+base64.
91 '''
92
93 dsname = 'VMwareGuestInfo'
94
akutz77457a62018-08-22 16:07:21 -050095 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
96 sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)
akutz10cd1402019-10-23 18:06:39 -050097 if not get_data_access_method():
yvespp8d58dfd2019-10-29 20:42:09 +010098 LOG.error("Failed to find vmware-rpctool")
akutz77457a62018-08-22 16:07:21 -050099
100 def get_data(self):
akutz0d1fce52019-06-01 18:54:29 -0500101 """
102 This method should really be _get_data in accordance with the most
103 recent versions of cloud-init. However, because the datasource
104 supports as far back as cloud-init 0.7.9, get_data is still used.
105
106 Because of this the method attempts to do some of the same things
107 that the get_data functions in newer versions of cloud-init do,
108 such as calling persist_instance_data.
109 """
akutzdbce3d92019-12-08 00:09:11 -0600110 data_access_method = get_data_access_method()
111 if not data_access_method:
yvespp8d58dfd2019-10-29 20:42:09 +0100112 LOG.error("vmware-rpctool is required to fetch guestinfo value")
akutz77457a62018-08-22 16:07:21 -0500113 return False
akutz6501f902018-08-24 12:19:05 -0500114
akutz0d1fce52019-06-01 18:54:29 -0500115 # Get the metadata.
116 self.metadata = load_metadata()
akutz6501f902018-08-24 12:19:05 -0500117
akutz0d1fce52019-06-01 18:54:29 -0500118 # Get the user data.
119 self.userdata_raw = guestinfo('userdata')
akutz6501f902018-08-24 12:19:05 -0500120
akutz0d1fce52019-06-01 18:54:29 -0500121 # Get the vendor data.
122 self.vendordata_raw = guestinfo('vendordata')
akutz6501f902018-08-24 12:19:05 -0500123
akutzdbce3d92019-12-08 00:09:11 -0600124 # Check to see if any of the guestinfo data should be removed.
125 if data_access_method == VMWARE_RPCTOOL:
126 clear_guestinfo_keys(self.metadata['cleanup-guestinfo'])
127
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530128 if self.metadata or self.userdata_raw or self.vendordata_raw:
129 return True
130 else:
131 return False
akutz77457a62018-08-22 16:07:21 -0500132
akutz0d1fce52019-06-01 18:54:29 -0500133 def setup(self, is_new_instance):
134 """setup(is_new_instance)
135
136 This is called before user-data and vendor-data have been processed.
137
138 Unless the datasource has set mode to 'local', then networking
139 per 'fallback' or per 'network_config' will have been written and
140 brought up the OS at this point.
141 """
142
akutzffc4dd52019-06-02 11:34:55 -0500143 # Get information about the host.
akutz0d1fce52019-06-01 18:54:29 -0500144 host_info = get_host_info()
145 LOG.info("got host-info: %s", host_info)
akutzffc4dd52019-06-02 11:34:55 -0500146
147 # Ensure the metadata gets updated with information about the
148 # host, including the network interfaces, default IP addresses,
149 # etc.
150 self.metadata = always_merger.merge(self.metadata, host_info)
akutz0d1fce52019-06-01 18:54:29 -0500151
152 # Persist the instance data for versions of cloud-init that support
153 # doing so. This occurs here rather than in the get_data call in
154 # order to ensure that the network interfaces are up and can be
155 # persisted with the metadata.
156 try:
157 self.persist_instance_data()
158 except AttributeError:
159 pass
160
akutz6501f902018-08-24 12:19:05 -0500161 @property
162 def network_config(self):
akutz0d1fce52019-06-01 18:54:29 -0500163 if 'network' in self.metadata:
164 LOG.debug("using metadata network config")
165 else:
166 LOG.debug("using fallback network config")
167 self.metadata['network'] = {
168 'config': self.distro.generate_fallback_config(),
169 }
170 return self.metadata['network']['config']
akutz77457a62018-08-22 16:07:21 -0500171
172 def get_instance_id(self):
akutz6501f902018-08-24 12:19:05 -0500173 # Pull the instance ID out of the metadata if present. Otherwise
174 # read the file /sys/class/dmi/id/product_uuid for the instance ID.
175 if self.metadata and 'instance-id' in self.metadata:
176 return self.metadata['instance-id']
akutz77457a62018-08-22 16:07:21 -0500177 with open('/sys/class/dmi/id/product_uuid', 'r') as id_file:
Andrey Klimentyeve1c5ed42019-10-09 12:32:44 +0300178 self.metadata['instance-id'] = str(id_file.read()).rstrip().lower()
akutz0d1fce52019-06-01 18:54:29 -0500179 return self.metadata['instance-id']
akutz77457a62018-08-22 16:07:21 -0500180
Andrey Klimentyeva23229d2019-10-17 15:30:00 +0300181 def get_public_ssh_keys(self):
182 public_keys_data = ""
183 if 'public-keys-data' in self.metadata:
184 public_keys_data = self.metadata['public-keys-data'].splitlines()
185
186 public_keys = []
187 if not public_keys_data:
188 return public_keys
189
190 for public_key in public_keys_data:
191 public_keys.append(public_key)
192
193 return public_keys
194
akutz6501f902018-08-24 12:19:05 -0500195
akutz0d1fce52019-06-01 18:54:29 -0500196def decode(key, enc_type, data):
197 '''
198 decode returns the decoded string value of data
199 key is a string used to identify the data being decoded in log messages
200 ----
201 In py 2.7:
202 json.loads method takes string as input
203 zlib.decompress takes and returns a string
204 base64.b64decode takes and returns a string
205 -----
206 In py 3.6 and newer:
207 json.loads method takes bytes or string as input
208 zlib.decompress takes and returns a bytes
209 base64.b64decode takes bytes or string and returns bytes
210 -----
211 In py > 3, < 3.6:
212 json.loads method takes string as input
213 zlib.decompress takes and returns a bytes
214 base64.b64decode takes bytes or string and returns bytes
215 -----
216 Given the above conditions the output from zlib.decompress and
217 base64.b64decode would be bytes with newer python and str in older
218 version. Thus we would covert the output to str before returning
219 '''
220 LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type)
akutz6501f902018-08-24 12:19:05 -0500221
akutz0d1fce52019-06-01 18:54:29 -0500222 raw_data = None
223 if enc_type == "gzip+base64" or enc_type == "gz+b64":
224 LOG.debug("Decoding %s format %s", enc_type, key)
225 raw_data = zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16)
226 elif enc_type == "base64" or enc_type == "b64":
227 LOG.debug("Decoding %s format %s", enc_type, key)
228 raw_data = base64.b64decode(data)
229 else:
230 LOG.debug("Plain-text data %s", key)
231 raw_data = data
Sidharth Surana3a421682018-10-10 15:42:08 -0700232
akutz0d1fce52019-06-01 18:54:29 -0500233 if isinstance(raw_data, bytes):
234 return raw_data.decode('utf-8')
235 return raw_data
236
237
akutzdaf81f12019-12-08 11:20:33 -0600238def get_none_if_empty_val(val):
239 '''
240 get_none_if_empty_val returns None if the provided value, once stripped
241 of its trailing whitespace, is empty or equal to GUESTINFO_EMPTY_YAML_VAL.
242
243 The return value is always a string, regardless of whether the input is
244 a bytes class or a string.
245 '''
246
247 # If the provided value is a bytes class, convert it to a string to
248 # simplify the rest of this function's logic.
249 if isinstance(val, bytes):
250 val = val.decode()
251
252 val = val.rstrip()
253 if len(val) == 0 or val == GUESTINFO_EMPTY_YAML_VAL:
254 return None
255 return val
256
257
258def handle_returned_guestinfo_val(key, val):
259 '''
260 handle_returned_guestinfo_val returns the provided value if it is
261 not empty or set to GUESTINFO_EMPTY_YAML_VAL, otherwise None is
262 returned
263 '''
264 val = get_none_if_empty_val(val)
265 if val:
266 return val
267 LOG.debug("No value found for key %s", key)
268 return None
269
270
akutz0d1fce52019-06-01 18:54:29 -0500271def get_guestinfo_value(key):
272 '''
273 Returns a guestinfo value for the specified key.
274 '''
275 LOG.debug("Getting guestinfo value for key %s", key)
akutz10cd1402019-10-23 18:06:39 -0500276
277 data_access_method = get_data_access_method()
278
279 if data_access_method == VMX_GUESTINFO:
280 env_key = ("vmx.guestinfo." + key).upper().replace(".", "_", -1)
akutzdaf81f12019-12-08 11:20:33 -0600281 return handle_returned_guestinfo_val(key, os.environ.get(env_key, ""))
akutz10cd1402019-10-23 18:06:39 -0500282
yvespp8d58dfd2019-10-29 20:42:09 +0100283 if data_access_method == VMWARE_RPCTOOL:
akutz10cd1402019-10-23 18:06:39 -0500284 try:
285 (stdout, stderr) = util.subp(
yvespp8d58dfd2019-10-29 20:42:09 +0100286 [VMWARE_RPCTOOL, "info-get guestinfo." + key])
akutz10cd1402019-10-23 18:06:39 -0500287 if stderr == NOVAL:
288 LOG.debug("No value found for key %s", key)
289 elif not stdout:
290 LOG.error("Failed to get guestinfo value for key %s", key)
291 else:
akutzdaf81f12019-12-08 11:20:33 -0600292 return handle_returned_guestinfo_val(key, stdout)
akutz10cd1402019-10-23 18:06:39 -0500293 except util.ProcessExecutionError as error:
294 if error.stderr == NOVAL:
295 LOG.debug("No value found for key %s", key)
296 else:
297 util.logexc(
298 LOG, "Failed to get guestinfo value for key %s: %s", key, error)
299 except Exception:
akutz0d1fce52019-06-01 18:54:29 -0500300 util.logexc(
akutz10cd1402019-10-23 18:06:39 -0500301 LOG, "Unexpected error while trying to get guestinfo value for key %s", key)
302
akutz0d1fce52019-06-01 18:54:29 -0500303 return None
akutz6501f902018-08-24 12:19:05 -0500304
akutz0d1fce52019-06-01 18:54:29 -0500305
akutzdbce3d92019-12-08 00:09:11 -0600306def set_guestinfo_value(key, value):
307 '''
308 Sets a guestinfo value for the specified key. Set value to an empty string
309 to clear an existing guestinfo key.
310 '''
311
312 # If value is an empty string then set it to a single space as it is not
313 # possible to set a guestinfo key to an empty string. Setting a guestinfo
314 # key to a single space is as close as it gets to clearing an existing
315 # guestinfo key.
316 if value == "":
317 value = " "
318
319 LOG.debug("Setting guestinfo key=%s to value=%s", key, value)
320
321 data_access_method = get_data_access_method()
322
323 if data_access_method == VMX_GUESTINFO:
324 return True
325
326 if data_access_method == VMWARE_RPCTOOL:
327 try:
328 util.subp(
329 [VMWARE_RPCTOOL, ("info-set guestinfo.%s %s" % (key, value))])
330 return True
331 except util.ProcessExecutionError as error:
332 util.logexc(
333 LOG, "Failed to set guestinfo key=%s to value=%s: %s", key, value, error)
334 except Exception:
335 util.logexc(
336 LOG, "Unexpected error while trying to set guestinfo key=%s to value=%s", key, value)
337
338 return None
339
340
341def clear_guestinfo_keys(keys):
342 '''
akutzdaf81f12019-12-08 11:20:33 -0600343 clear_guestinfo_keys clears guestinfo of all of the keys in the given list.
344 each key will have its value set to "---". Since the value is valid YAML,
345 cloud-init can still read it if it tries.
akutzdbce3d92019-12-08 00:09:11 -0600346 '''
347 if not keys:
348 return
akutzdaf81f12019-12-08 11:20:33 -0600349 if not type(keys) in (list, tuple):
350 keys = [keys]
akutzdbce3d92019-12-08 00:09:11 -0600351 for key in keys:
akutzdaf81f12019-12-08 11:20:33 -0600352 LOG.info("clearing guestinfo.%s", key)
353 if not set_guestinfo_value(key, GUESTINFO_EMPTY_YAML_VAL):
354 LOG.error("failed to clear guestinfo.%s", key)
355 LOG.info("clearing guestinfo.%s.encoding", key)
356 if not set_guestinfo_value(key + ".encoding", ""):
357 LOG.error("failed to clear guestinfo.%s.encoding", key)
akutzdbce3d92019-12-08 00:09:11 -0600358
359
akutz0d1fce52019-06-01 18:54:29 -0500360def guestinfo(key):
361 '''
362 guestinfo returns the guestinfo value for the provided key, decoding
363 the value when required
364 '''
365 data = get_guestinfo_value(key)
366 if not data:
akutz6501f902018-08-24 12:19:05 -0500367 return None
akutz0d1fce52019-06-01 18:54:29 -0500368 enc_type = get_guestinfo_value(key + '.encoding')
369 return decode('guestinfo.' + key, enc_type, data)
370
371
372def load(data):
373 '''
374 load first attempts to unmarshal the provided data as JSON, and if
375 that fails then attempts to unmarshal the data as YAML. If data is
376 None then a new dictionary is returned.
377 '''
378 if not data:
379 return {}
380 try:
381 return json.loads(data)
382 except:
383 return safeyaml.load(data)
384
385
386def load_metadata():
387 '''
388 load_metadata loads the metadata from the guestinfo data, optionally
389 decoding the network config when required
390 '''
391 data = load(guestinfo('metadata'))
akutz84389c82019-06-02 19:24:38 -0500392 LOG.debug('loaded metadata %s', data)
akutz0d1fce52019-06-01 18:54:29 -0500393
394 network = None
395 if 'network' in data:
396 network = data['network']
397 del data['network']
398
399 network_enc = None
400 if 'network.encoding' in data:
401 network_enc = data['network.encoding']
402 del data['network.encoding']
403
404 if network:
akutz84389c82019-06-02 19:24:38 -0500405 LOG.debug('network data found')
406 if isinstance(network, collections.Mapping):
407 LOG.debug("network data copied to 'config' key")
408 network = {
409 'config': copy.deepcopy(network)
410 }
411 else:
412 LOG.debug("network data to be decoded %s", network)
akutz0d1fce52019-06-01 18:54:29 -0500413 dec_net = decode('metadata.network', network_enc, network)
akutz84389c82019-06-02 19:24:38 -0500414 network = {
415 'config': load(dec_net),
416 }
417
418 LOG.debug('network data %s', network)
akutz0d1fce52019-06-01 18:54:29 -0500419 data['network'] = network
420
421 return data
422
akutz6501f902018-08-24 12:19:05 -0500423
akutz77457a62018-08-22 16:07:21 -0500424def get_datasource_list(depends):
akutz0d1fce52019-06-01 18:54:29 -0500425 '''
akutz77457a62018-08-22 16:07:21 -0500426 Return a list of data sources that match this set of dependencies
akutz0d1fce52019-06-01 18:54:29 -0500427 '''
Andrew Kutz4f66b8b2018-09-16 18:28:59 -0500428 return [DataSourceVMwareGuestInfo]
akutz0d1fce52019-06-01 18:54:29 -0500429
430
akutzffc4dd52019-06-02 11:34:55 -0500431def get_default_ip_addrs():
432 '''
433 Returns the default IPv4 and IPv6 addresses based on the device(s) used for
434 the default route. Please note that None may be returned for either address
435 family if that family has no default route or if there are multiple
436 addresses associated with the device used by the default route for a given
437 address.
438 '''
439 gateways = netifaces.gateways()
440 if 'default' not in gateways:
441 return None, None
442
443 default_gw = gateways['default']
444 if netifaces.AF_INET not in default_gw and netifaces.AF_INET6 not in default_gw:
445 return None, None
446
447 ipv4 = None
448 ipv6 = None
449
450 gw4 = default_gw.get(netifaces.AF_INET)
451 if gw4:
452 _, dev4 = gw4
akutz0b519f72019-06-02 14:58:57 -0500453 addr4_fams = netifaces.ifaddresses(dev4)
454 if addr4_fams:
455 af_inet4 = addr4_fams.get(netifaces.AF_INET)
456 if af_inet4:
457 if len(af_inet4) > 1:
akutzffc4dd52019-06-02 11:34:55 -0500458 LOG.warn(
akutz0b519f72019-06-02 14:58:57 -0500459 "device %s has more than one ipv4 address: %s", dev4, af_inet4)
460 elif 'addr' in af_inet4[0]:
461 ipv4 = af_inet4[0]['addr']
akutzffc4dd52019-06-02 11:34:55 -0500462
463 # Try to get the default IPv6 address by first seeing if there is a default
akutz0b519f72019-06-02 14:58:57 -0500464 # IPv6 route.
akutzffc4dd52019-06-02 11:34:55 -0500465 gw6 = default_gw.get(netifaces.AF_INET6)
466 if gw6:
467 _, dev6 = gw6
468 addr6_fams = netifaces.ifaddresses(dev6)
469 if addr6_fams:
470 af_inet6 = addr6_fams.get(netifaces.AF_INET6)
471 if af_inet6:
472 if len(af_inet6) > 1:
473 LOG.warn(
474 "device %s has more than one ipv6 address: %s", dev6, af_inet6)
475 elif 'addr' in af_inet6[0]:
476 ipv6 = af_inet6[0]['addr']
akutz0b519f72019-06-02 14:58:57 -0500477
478 # If there is a default IPv4 address but not IPv6, then see if there is a
479 # single IPv6 address associated with the same device associated with the
480 # default IPv4 address.
481 if ipv4 and not ipv6:
482 af_inet6 = addr4_fams.get(netifaces.AF_INET6)
akutzffc4dd52019-06-02 11:34:55 -0500483 if af_inet6:
484 if len(af_inet6) > 1:
485 LOG.warn(
486 "device %s has more than one ipv6 address: %s", dev4, af_inet6)
487 elif 'addr' in af_inet6[0]:
488 ipv6 = af_inet6[0]['addr']
489
akutz0b519f72019-06-02 14:58:57 -0500490 # If there is a default IPv6 address but not IPv4, then see if there is a
491 # single IPv4 address associated with the same device associated with the
492 # default IPv6 address.
493 if not ipv4 and ipv6:
494 af_inet4 = addr6_fams.get(netifaces.AF_INET4)
495 if af_inet4:
496 if len(af_inet4) > 1:
497 LOG.warn(
498 "device %s has more than one ipv4 address: %s", dev6, af_inet4)
499 elif 'addr' in af_inet4[0]:
500 ipv4 = af_inet4[0]['addr']
501
akutzffc4dd52019-06-02 11:34:55 -0500502 return ipv4, ipv6
503
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530504# patched socket.getfqdn() - see https://bugs.python.org/issue5004
akutz10cd1402019-10-23 18:06:39 -0500505
506
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530507def getfqdn(name=''):
508 """Get fully qualified domain name from name.
509 An empty argument is interpreted as meaning the local host.
510 """
511 name = name.strip()
512 if not name or name == '0.0.0.0':
513 name = socket.gethostname()
514 try:
akutz10cd1402019-10-23 18:06:39 -0500515 addrs = socket.getaddrinfo(
516 name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME)
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530517 except socket.error:
518 pass
519 else:
520 for addr in addrs:
521 if addr[3]:
522 name = addr[3]
523 break
524 return name
akutzffc4dd52019-06-02 11:34:55 -0500525
akutz10cd1402019-10-23 18:06:39 -0500526
akutz0d1fce52019-06-01 18:54:29 -0500527def get_host_info():
528 '''
529 Returns host information such as the host name and network interfaces.
530 '''
akutz0d1fce52019-06-01 18:54:29 -0500531
532 host_info = {
533 'network': {
534 'interfaces': {
535 'by-mac': collections.OrderedDict(),
akutzffc4dd52019-06-02 11:34:55 -0500536 'by-ipv4': collections.OrderedDict(),
537 'by-ipv6': collections.OrderedDict(),
akutz0d1fce52019-06-01 18:54:29 -0500538 },
539 },
540 }
541
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530542 hostname = getfqdn(socket.gethostname())
akutz0d1fce52019-06-01 18:54:29 -0500543 if hostname:
akutzffc4dd52019-06-02 11:34:55 -0500544 host_info['hostname'] = hostname
akutz0d1fce52019-06-01 18:54:29 -0500545 host_info['local-hostname'] = hostname
546
akutzffc4dd52019-06-02 11:34:55 -0500547 default_ipv4, default_ipv6 = get_default_ip_addrs()
548 if default_ipv4:
549 host_info['local-ipv4'] = default_ipv4
550 if default_ipv6:
551 host_info['local-ipv6'] = default_ipv6
552
akutz0d1fce52019-06-01 18:54:29 -0500553 by_mac = host_info['network']['interfaces']['by-mac']
akutzffc4dd52019-06-02 11:34:55 -0500554 by_ipv4 = host_info['network']['interfaces']['by-ipv4']
555 by_ipv6 = host_info['network']['interfaces']['by-ipv6']
akutz0d1fce52019-06-01 18:54:29 -0500556
557 ifaces = netifaces.interfaces()
558 for dev_name in ifaces:
559 addr_fams = netifaces.ifaddresses(dev_name)
560 af_link = addr_fams.get(netifaces.AF_LINK)
akutz0b519f72019-06-02 14:58:57 -0500561 af_inet4 = addr_fams.get(netifaces.AF_INET)
akutz0d1fce52019-06-01 18:54:29 -0500562 af_inet6 = addr_fams.get(netifaces.AF_INET6)
563
564 mac = None
565 if af_link and 'addr' in af_link[0]:
566 mac = af_link[0]['addr']
567
568 # Do not bother recording localhost
569 if mac == "00:00:00:00:00:00":
570 continue
571
akutz0b519f72019-06-02 14:58:57 -0500572 if mac and (af_inet4 or af_inet6):
akutz0d1fce52019-06-01 18:54:29 -0500573 key = mac
574 val = {}
akutz0b519f72019-06-02 14:58:57 -0500575 if af_inet4:
576 val["ipv4"] = af_inet4
akutz0d1fce52019-06-01 18:54:29 -0500577 if af_inet6:
akutzffc4dd52019-06-02 11:34:55 -0500578 val["ipv6"] = af_inet6
akutz0d1fce52019-06-01 18:54:29 -0500579 by_mac[key] = val
580
akutz0b519f72019-06-02 14:58:57 -0500581 if af_inet4:
582 for ip_info in af_inet4:
akutz0d1fce52019-06-01 18:54:29 -0500583 key = ip_info['addr']
akutzffc4dd52019-06-02 11:34:55 -0500584 if key == '127.0.0.1':
585 continue
akutz84389c82019-06-02 19:24:38 -0500586 val = copy.deepcopy(ip_info)
akutz0d1fce52019-06-01 18:54:29 -0500587 del val['addr']
588 if mac:
589 val['mac'] = mac
akutzffc4dd52019-06-02 11:34:55 -0500590 by_ipv4[key] = val
akutz0d1fce52019-06-01 18:54:29 -0500591
592 if af_inet6:
593 for ip_info in af_inet6:
594 key = ip_info['addr']
akutzffc4dd52019-06-02 11:34:55 -0500595 if key == '::1':
596 continue
akutz84389c82019-06-02 19:24:38 -0500597 val = copy.deepcopy(ip_info)
akutz0d1fce52019-06-01 18:54:29 -0500598 del val['addr']
599 if mac:
600 val['mac'] = mac
akutzffc4dd52019-06-02 11:34:55 -0500601 by_ipv6[key] = val
akutz0d1fce52019-06-01 18:54:29 -0500602
603 return host_info
604
605
akutz10cd1402019-10-23 18:06:39 -0500606def get_data_access_method():
607 if os.environ.get(VMX_GUESTINFO, ""):
608 return VMX_GUESTINFO
yvespp8d58dfd2019-10-29 20:42:09 +0100609 if VMWARE_RPCTOOL:
610 return VMWARE_RPCTOOL
akutz10cd1402019-10-23 18:06:39 -0500611 return None
612
613
akutzffc4dd52019-06-02 11:34:55 -0500614def main():
615 '''
616 Executed when this file is used as a program.
617 '''
618 metadata = {'network': {'config': {'dhcp': True}}}
619 host_info = get_host_info()
620 metadata = always_merger.merge(metadata, host_info)
621 print(util.json_dumps(metadata))
622
623
akutz0d1fce52019-06-01 18:54:29 -0500624if __name__ == "__main__":
akutzffc4dd52019-06-02 11:34:55 -0500625 main()
akutz0d1fce52019-06-01 18:54:29 -0500626
627# vi: ts=4 expandtab