blob: a1467ea79196ebd1039b3832cb28ff7c5cecec93 [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"
akutz77457a62018-08-22 16:07:21 -050043
akutz0d1fce52019-06-01 18:54:29 -050044
45class NetworkConfigError(Exception):
46 '''
47 NetworkConfigError is raised when there is an issue getting or
48 applying network configuration.
49 '''
50 pass
51
52
Andrew Kutz4f66b8b2018-09-16 18:28:59 -050053class DataSourceVMwareGuestInfo(sources.DataSource):
akutz0d1fce52019-06-01 18:54:29 -050054 '''
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
60 The documentation for cloud-init 0.7.9's datasource is
61 available at http://bit.ly/cloudinit-datasource-0-7-9. The
62 current documentation for cloud-init is found at
63 https://cloudinit.readthedocs.io/en/latest/.
64
65 Setting the hostname:
66 The hostname is set by way of the metadata key "local-hostname".
67
68 Setting the instance ID:
69 The instance ID may be set by way of the metadata key "instance-id".
70 However, if this value is absent then then the instance ID is
71 read from the file /sys/class/dmi/id/product_uuid.
72
73 Configuring the network:
74 The network is configured by setting the metadata key "network"
75 with a value consistent with Network Config Versions 1 or 2,
76 depending on the Linux distro's version of cloud-init:
77
78 Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1
79 Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2
80
81 For example, CentOS 7's official cloud-init package is version
82 0.7.9 and does not support Network Config Version 2. However,
83 this datasource still supports supplying Network Config Version 2
84 data as long as the Linux distro's cloud-init package is new
85 enough to parse the data.
86
87 The metadata key "network.encoding" may be used to indicate the
88 format of the metadata key "network". Valid encodings are base64
89 and gzip+base64.
90 '''
91
92 dsname = 'VMwareGuestInfo'
93
akutz77457a62018-08-22 16:07:21 -050094 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
95 sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)
akutz10cd1402019-10-23 18:06:39 -050096 if not get_data_access_method():
yvespp8d58dfd2019-10-29 20:42:09 +010097 LOG.error("Failed to find vmware-rpctool")
akutz77457a62018-08-22 16:07:21 -050098
99 def get_data(self):
akutz0d1fce52019-06-01 18:54:29 -0500100 """
101 This method should really be _get_data in accordance with the most
102 recent versions of cloud-init. However, because the datasource
103 supports as far back as cloud-init 0.7.9, get_data is still used.
104
105 Because of this the method attempts to do some of the same things
106 that the get_data functions in newer versions of cloud-init do,
107 such as calling persist_instance_data.
108 """
akutzdbce3d92019-12-08 00:09:11 -0600109 data_access_method = get_data_access_method()
110 if not data_access_method:
yvespp8d58dfd2019-10-29 20:42:09 +0100111 LOG.error("vmware-rpctool is required to fetch guestinfo value")
akutz77457a62018-08-22 16:07:21 -0500112 return False
akutz6501f902018-08-24 12:19:05 -0500113
akutz0d1fce52019-06-01 18:54:29 -0500114 # Get the metadata.
115 self.metadata = load_metadata()
akutz6501f902018-08-24 12:19:05 -0500116
akutz0d1fce52019-06-01 18:54:29 -0500117 # Get the user data.
118 self.userdata_raw = guestinfo('userdata')
akutz6501f902018-08-24 12:19:05 -0500119
akutz0d1fce52019-06-01 18:54:29 -0500120 # Get the vendor data.
121 self.vendordata_raw = guestinfo('vendordata')
akutz6501f902018-08-24 12:19:05 -0500122
akutzdbce3d92019-12-08 00:09:11 -0600123 # Check to see if any of the guestinfo data should be removed.
124 if data_access_method == VMWARE_RPCTOOL:
125 clear_guestinfo_keys(self.metadata['cleanup-guestinfo'])
126
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530127 if self.metadata or self.userdata_raw or self.vendordata_raw:
128 return True
129 else:
130 return False
akutz77457a62018-08-22 16:07:21 -0500131
akutz0d1fce52019-06-01 18:54:29 -0500132 def setup(self, is_new_instance):
133 """setup(is_new_instance)
134
135 This is called before user-data and vendor-data have been processed.
136
137 Unless the datasource has set mode to 'local', then networking
138 per 'fallback' or per 'network_config' will have been written and
139 brought up the OS at this point.
140 """
141
akutzffc4dd52019-06-02 11:34:55 -0500142 # Get information about the host.
akutz0d1fce52019-06-01 18:54:29 -0500143 host_info = get_host_info()
144 LOG.info("got host-info: %s", host_info)
akutzffc4dd52019-06-02 11:34:55 -0500145
146 # Ensure the metadata gets updated with information about the
147 # host, including the network interfaces, default IP addresses,
148 # etc.
149 self.metadata = always_merger.merge(self.metadata, host_info)
akutz0d1fce52019-06-01 18:54:29 -0500150
151 # Persist the instance data for versions of cloud-init that support
152 # doing so. This occurs here rather than in the get_data call in
153 # order to ensure that the network interfaces are up and can be
154 # persisted with the metadata.
155 try:
156 self.persist_instance_data()
157 except AttributeError:
158 pass
159
akutz6501f902018-08-24 12:19:05 -0500160 @property
161 def network_config(self):
akutz0d1fce52019-06-01 18:54:29 -0500162 if 'network' in self.metadata:
163 LOG.debug("using metadata network config")
164 else:
165 LOG.debug("using fallback network config")
166 self.metadata['network'] = {
167 'config': self.distro.generate_fallback_config(),
168 }
169 return self.metadata['network']['config']
akutz77457a62018-08-22 16:07:21 -0500170
171 def get_instance_id(self):
akutz6501f902018-08-24 12:19:05 -0500172 # Pull the instance ID out of the metadata if present. Otherwise
173 # read the file /sys/class/dmi/id/product_uuid for the instance ID.
174 if self.metadata and 'instance-id' in self.metadata:
175 return self.metadata['instance-id']
akutz77457a62018-08-22 16:07:21 -0500176 with open('/sys/class/dmi/id/product_uuid', 'r') as id_file:
Andrey Klimentyeve1c5ed42019-10-09 12:32:44 +0300177 self.metadata['instance-id'] = str(id_file.read()).rstrip().lower()
akutz0d1fce52019-06-01 18:54:29 -0500178 return self.metadata['instance-id']
akutz77457a62018-08-22 16:07:21 -0500179
Andrey Klimentyeva23229d2019-10-17 15:30:00 +0300180 def get_public_ssh_keys(self):
181 public_keys_data = ""
182 if 'public-keys-data' in self.metadata:
183 public_keys_data = self.metadata['public-keys-data'].splitlines()
184
185 public_keys = []
186 if not public_keys_data:
187 return public_keys
188
189 for public_key in public_keys_data:
190 public_keys.append(public_key)
191
192 return public_keys
193
akutz6501f902018-08-24 12:19:05 -0500194
akutz0d1fce52019-06-01 18:54:29 -0500195def decode(key, enc_type, data):
196 '''
197 decode returns the decoded string value of data
198 key is a string used to identify the data being decoded in log messages
199 ----
200 In py 2.7:
201 json.loads method takes string as input
202 zlib.decompress takes and returns a string
203 base64.b64decode takes and returns a string
204 -----
205 In py 3.6 and newer:
206 json.loads method takes bytes or string as input
207 zlib.decompress takes and returns a bytes
208 base64.b64decode takes bytes or string and returns bytes
209 -----
210 In py > 3, < 3.6:
211 json.loads method takes string as input
212 zlib.decompress takes and returns a bytes
213 base64.b64decode takes bytes or string and returns bytes
214 -----
215 Given the above conditions the output from zlib.decompress and
216 base64.b64decode would be bytes with newer python and str in older
217 version. Thus we would covert the output to str before returning
218 '''
219 LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type)
akutz6501f902018-08-24 12:19:05 -0500220
akutz0d1fce52019-06-01 18:54:29 -0500221 raw_data = None
222 if enc_type == "gzip+base64" or enc_type == "gz+b64":
223 LOG.debug("Decoding %s format %s", enc_type, key)
224 raw_data = zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16)
225 elif enc_type == "base64" or enc_type == "b64":
226 LOG.debug("Decoding %s format %s", enc_type, key)
227 raw_data = base64.b64decode(data)
228 else:
229 LOG.debug("Plain-text data %s", key)
230 raw_data = data
Sidharth Surana3a421682018-10-10 15:42:08 -0700231
akutz0d1fce52019-06-01 18:54:29 -0500232 if isinstance(raw_data, bytes):
233 return raw_data.decode('utf-8')
234 return raw_data
235
236
237def get_guestinfo_value(key):
238 '''
239 Returns a guestinfo value for the specified key.
240 '''
241 LOG.debug("Getting guestinfo value for key %s", key)
akutz10cd1402019-10-23 18:06:39 -0500242
243 data_access_method = get_data_access_method()
244
245 if data_access_method == VMX_GUESTINFO:
246 env_key = ("vmx.guestinfo." + key).upper().replace(".", "_", -1)
247 val = os.environ.get(env_key, "")
248 if val == "":
akutz0d1fce52019-06-01 18:54:29 -0500249 LOG.debug("No value found for key %s", key)
250 else:
akutz10cd1402019-10-23 18:06:39 -0500251 return val
252
yvespp8d58dfd2019-10-29 20:42:09 +0100253 if data_access_method == VMWARE_RPCTOOL:
akutz10cd1402019-10-23 18:06:39 -0500254 try:
255 (stdout, stderr) = util.subp(
yvespp8d58dfd2019-10-29 20:42:09 +0100256 [VMWARE_RPCTOOL, "info-get guestinfo." + key])
akutz10cd1402019-10-23 18:06:39 -0500257 if stderr == NOVAL:
258 LOG.debug("No value found for key %s", key)
259 elif not stdout:
260 LOG.error("Failed to get guestinfo value for key %s", key)
261 else:
262 return stdout.rstrip()
263 except util.ProcessExecutionError as error:
264 if error.stderr == NOVAL:
265 LOG.debug("No value found for key %s", key)
266 else:
267 util.logexc(
268 LOG, "Failed to get guestinfo value for key %s: %s", key, error)
269 except Exception:
akutz0d1fce52019-06-01 18:54:29 -0500270 util.logexc(
akutz10cd1402019-10-23 18:06:39 -0500271 LOG, "Unexpected error while trying to get guestinfo value for key %s", key)
272
akutz0d1fce52019-06-01 18:54:29 -0500273 return None
akutz6501f902018-08-24 12:19:05 -0500274
akutz0d1fce52019-06-01 18:54:29 -0500275
akutzdbce3d92019-12-08 00:09:11 -0600276def set_guestinfo_value(key, value):
277 '''
278 Sets a guestinfo value for the specified key. Set value to an empty string
279 to clear an existing guestinfo key.
280 '''
281
282 # If value is an empty string then set it to a single space as it is not
283 # possible to set a guestinfo key to an empty string. Setting a guestinfo
284 # key to a single space is as close as it gets to clearing an existing
285 # guestinfo key.
286 if value == "":
287 value = " "
288
289 LOG.debug("Setting guestinfo key=%s to value=%s", key, value)
290
291 data_access_method = get_data_access_method()
292
293 if data_access_method == VMX_GUESTINFO:
294 return True
295
296 if data_access_method == VMWARE_RPCTOOL:
297 try:
298 util.subp(
299 [VMWARE_RPCTOOL, ("info-set guestinfo.%s %s" % (key, value))])
300 return True
301 except util.ProcessExecutionError as error:
302 util.logexc(
303 LOG, "Failed to set guestinfo key=%s to value=%s: %s", key, value, error)
304 except Exception:
305 util.logexc(
306 LOG, "Unexpected error while trying to set guestinfo key=%s to value=%s", key, value)
307
308 return None
309
310
311def clear_guestinfo_keys(keys):
312 '''
313 clear_guestinfo_keys clears guestinfo of all of the keys in the given list
314 '''
315 if not keys:
316 return
317 for key in keys:
318 clear_guestinfo_value(key)
319 clear_guestinfo_value(key + ".encoding")
320
321
322def clear_guestinfo_value(key):
323 '''
324 clear_guestinfo_value sets the specified guestinfo key to an empty string
325 '''
326 LOG.info("clearing guestinfo.%s", key)
327 if not set_guestinfo_value(key, ''):
328 LOG.error("failed to clear guestinfo.%s", key)
329
330
akutz0d1fce52019-06-01 18:54:29 -0500331def guestinfo(key):
332 '''
333 guestinfo returns the guestinfo value for the provided key, decoding
334 the value when required
335 '''
336 data = get_guestinfo_value(key)
337 if not data:
akutz6501f902018-08-24 12:19:05 -0500338 return None
akutz0d1fce52019-06-01 18:54:29 -0500339 enc_type = get_guestinfo_value(key + '.encoding')
340 return decode('guestinfo.' + key, enc_type, data)
341
342
343def load(data):
344 '''
345 load first attempts to unmarshal the provided data as JSON, and if
346 that fails then attempts to unmarshal the data as YAML. If data is
347 None then a new dictionary is returned.
348 '''
349 if not data:
350 return {}
351 try:
352 return json.loads(data)
353 except:
354 return safeyaml.load(data)
355
356
357def load_metadata():
358 '''
359 load_metadata loads the metadata from the guestinfo data, optionally
360 decoding the network config when required
361 '''
362 data = load(guestinfo('metadata'))
akutz84389c82019-06-02 19:24:38 -0500363 LOG.debug('loaded metadata %s', data)
akutz0d1fce52019-06-01 18:54:29 -0500364
365 network = None
366 if 'network' in data:
367 network = data['network']
368 del data['network']
369
370 network_enc = None
371 if 'network.encoding' in data:
372 network_enc = data['network.encoding']
373 del data['network.encoding']
374
375 if network:
akutz84389c82019-06-02 19:24:38 -0500376 LOG.debug('network data found')
377 if isinstance(network, collections.Mapping):
378 LOG.debug("network data copied to 'config' key")
379 network = {
380 'config': copy.deepcopy(network)
381 }
382 else:
383 LOG.debug("network data to be decoded %s", network)
akutz0d1fce52019-06-01 18:54:29 -0500384 dec_net = decode('metadata.network', network_enc, network)
akutz84389c82019-06-02 19:24:38 -0500385 network = {
386 'config': load(dec_net),
387 }
388
389 LOG.debug('network data %s', network)
akutz0d1fce52019-06-01 18:54:29 -0500390 data['network'] = network
391
392 return data
393
akutz6501f902018-08-24 12:19:05 -0500394
akutz77457a62018-08-22 16:07:21 -0500395def get_datasource_list(depends):
akutz0d1fce52019-06-01 18:54:29 -0500396 '''
akutz77457a62018-08-22 16:07:21 -0500397 Return a list of data sources that match this set of dependencies
akutz0d1fce52019-06-01 18:54:29 -0500398 '''
Andrew Kutz4f66b8b2018-09-16 18:28:59 -0500399 return [DataSourceVMwareGuestInfo]
akutz0d1fce52019-06-01 18:54:29 -0500400
401
akutzffc4dd52019-06-02 11:34:55 -0500402def get_default_ip_addrs():
403 '''
404 Returns the default IPv4 and IPv6 addresses based on the device(s) used for
405 the default route. Please note that None may be returned for either address
406 family if that family has no default route or if there are multiple
407 addresses associated with the device used by the default route for a given
408 address.
409 '''
410 gateways = netifaces.gateways()
411 if 'default' not in gateways:
412 return None, None
413
414 default_gw = gateways['default']
415 if netifaces.AF_INET not in default_gw and netifaces.AF_INET6 not in default_gw:
416 return None, None
417
418 ipv4 = None
419 ipv6 = None
420
421 gw4 = default_gw.get(netifaces.AF_INET)
422 if gw4:
423 _, dev4 = gw4
akutz0b519f72019-06-02 14:58:57 -0500424 addr4_fams = netifaces.ifaddresses(dev4)
425 if addr4_fams:
426 af_inet4 = addr4_fams.get(netifaces.AF_INET)
427 if af_inet4:
428 if len(af_inet4) > 1:
akutzffc4dd52019-06-02 11:34:55 -0500429 LOG.warn(
akutz0b519f72019-06-02 14:58:57 -0500430 "device %s has more than one ipv4 address: %s", dev4, af_inet4)
431 elif 'addr' in af_inet4[0]:
432 ipv4 = af_inet4[0]['addr']
akutzffc4dd52019-06-02 11:34:55 -0500433
434 # Try to get the default IPv6 address by first seeing if there is a default
akutz0b519f72019-06-02 14:58:57 -0500435 # IPv6 route.
akutzffc4dd52019-06-02 11:34:55 -0500436 gw6 = default_gw.get(netifaces.AF_INET6)
437 if gw6:
438 _, dev6 = gw6
439 addr6_fams = netifaces.ifaddresses(dev6)
440 if addr6_fams:
441 af_inet6 = addr6_fams.get(netifaces.AF_INET6)
442 if af_inet6:
443 if len(af_inet6) > 1:
444 LOG.warn(
445 "device %s has more than one ipv6 address: %s", dev6, af_inet6)
446 elif 'addr' in af_inet6[0]:
447 ipv6 = af_inet6[0]['addr']
akutz0b519f72019-06-02 14:58:57 -0500448
449 # If there is a default IPv4 address but not IPv6, then see if there is a
450 # single IPv6 address associated with the same device associated with the
451 # default IPv4 address.
452 if ipv4 and not ipv6:
453 af_inet6 = addr4_fams.get(netifaces.AF_INET6)
akutzffc4dd52019-06-02 11:34:55 -0500454 if af_inet6:
455 if len(af_inet6) > 1:
456 LOG.warn(
457 "device %s has more than one ipv6 address: %s", dev4, af_inet6)
458 elif 'addr' in af_inet6[0]:
459 ipv6 = af_inet6[0]['addr']
460
akutz0b519f72019-06-02 14:58:57 -0500461 # If there is a default IPv6 address but not IPv4, then see if there is a
462 # single IPv4 address associated with the same device associated with the
463 # default IPv6 address.
464 if not ipv4 and ipv6:
465 af_inet4 = addr6_fams.get(netifaces.AF_INET4)
466 if af_inet4:
467 if len(af_inet4) > 1:
468 LOG.warn(
469 "device %s has more than one ipv4 address: %s", dev6, af_inet4)
470 elif 'addr' in af_inet4[0]:
471 ipv4 = af_inet4[0]['addr']
472
akutzffc4dd52019-06-02 11:34:55 -0500473 return ipv4, ipv6
474
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530475# patched socket.getfqdn() - see https://bugs.python.org/issue5004
akutz10cd1402019-10-23 18:06:39 -0500476
477
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530478def getfqdn(name=''):
479 """Get fully qualified domain name from name.
480 An empty argument is interpreted as meaning the local host.
481 """
482 name = name.strip()
483 if not name or name == '0.0.0.0':
484 name = socket.gethostname()
485 try:
akutz10cd1402019-10-23 18:06:39 -0500486 addrs = socket.getaddrinfo(
487 name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME)
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530488 except socket.error:
489 pass
490 else:
491 for addr in addrs:
492 if addr[3]:
493 name = addr[3]
494 break
495 return name
akutzffc4dd52019-06-02 11:34:55 -0500496
akutz10cd1402019-10-23 18:06:39 -0500497
akutz0d1fce52019-06-01 18:54:29 -0500498def get_host_info():
499 '''
500 Returns host information such as the host name and network interfaces.
501 '''
akutz0d1fce52019-06-01 18:54:29 -0500502
503 host_info = {
504 'network': {
505 'interfaces': {
506 'by-mac': collections.OrderedDict(),
akutzffc4dd52019-06-02 11:34:55 -0500507 'by-ipv4': collections.OrderedDict(),
508 'by-ipv6': collections.OrderedDict(),
akutz0d1fce52019-06-01 18:54:29 -0500509 },
510 },
511 }
512
Keerthana Kf0d27ea2019-09-05 21:46:31 +0530513 hostname = getfqdn(socket.gethostname())
akutz0d1fce52019-06-01 18:54:29 -0500514 if hostname:
akutzffc4dd52019-06-02 11:34:55 -0500515 host_info['hostname'] = hostname
akutz0d1fce52019-06-01 18:54:29 -0500516 host_info['local-hostname'] = hostname
517
akutzffc4dd52019-06-02 11:34:55 -0500518 default_ipv4, default_ipv6 = get_default_ip_addrs()
519 if default_ipv4:
520 host_info['local-ipv4'] = default_ipv4
521 if default_ipv6:
522 host_info['local-ipv6'] = default_ipv6
523
akutz0d1fce52019-06-01 18:54:29 -0500524 by_mac = host_info['network']['interfaces']['by-mac']
akutzffc4dd52019-06-02 11:34:55 -0500525 by_ipv4 = host_info['network']['interfaces']['by-ipv4']
526 by_ipv6 = host_info['network']['interfaces']['by-ipv6']
akutz0d1fce52019-06-01 18:54:29 -0500527
528 ifaces = netifaces.interfaces()
529 for dev_name in ifaces:
530 addr_fams = netifaces.ifaddresses(dev_name)
531 af_link = addr_fams.get(netifaces.AF_LINK)
akutz0b519f72019-06-02 14:58:57 -0500532 af_inet4 = addr_fams.get(netifaces.AF_INET)
akutz0d1fce52019-06-01 18:54:29 -0500533 af_inet6 = addr_fams.get(netifaces.AF_INET6)
534
535 mac = None
536 if af_link and 'addr' in af_link[0]:
537 mac = af_link[0]['addr']
538
539 # Do not bother recording localhost
540 if mac == "00:00:00:00:00:00":
541 continue
542
akutz0b519f72019-06-02 14:58:57 -0500543 if mac and (af_inet4 or af_inet6):
akutz0d1fce52019-06-01 18:54:29 -0500544 key = mac
545 val = {}
akutz0b519f72019-06-02 14:58:57 -0500546 if af_inet4:
547 val["ipv4"] = af_inet4
akutz0d1fce52019-06-01 18:54:29 -0500548 if af_inet6:
akutzffc4dd52019-06-02 11:34:55 -0500549 val["ipv6"] = af_inet6
akutz0d1fce52019-06-01 18:54:29 -0500550 by_mac[key] = val
551
akutz0b519f72019-06-02 14:58:57 -0500552 if af_inet4:
553 for ip_info in af_inet4:
akutz0d1fce52019-06-01 18:54:29 -0500554 key = ip_info['addr']
akutzffc4dd52019-06-02 11:34:55 -0500555 if key == '127.0.0.1':
556 continue
akutz84389c82019-06-02 19:24:38 -0500557 val = copy.deepcopy(ip_info)
akutz0d1fce52019-06-01 18:54:29 -0500558 del val['addr']
559 if mac:
560 val['mac'] = mac
akutzffc4dd52019-06-02 11:34:55 -0500561 by_ipv4[key] = val
akutz0d1fce52019-06-01 18:54:29 -0500562
563 if af_inet6:
564 for ip_info in af_inet6:
565 key = ip_info['addr']
akutzffc4dd52019-06-02 11:34:55 -0500566 if key == '::1':
567 continue
akutz84389c82019-06-02 19:24:38 -0500568 val = copy.deepcopy(ip_info)
akutz0d1fce52019-06-01 18:54:29 -0500569 del val['addr']
570 if mac:
571 val['mac'] = mac
akutzffc4dd52019-06-02 11:34:55 -0500572 by_ipv6[key] = val
akutz0d1fce52019-06-01 18:54:29 -0500573
574 return host_info
575
576
akutz10cd1402019-10-23 18:06:39 -0500577def get_data_access_method():
578 if os.environ.get(VMX_GUESTINFO, ""):
579 return VMX_GUESTINFO
yvespp8d58dfd2019-10-29 20:42:09 +0100580 if VMWARE_RPCTOOL:
581 return VMWARE_RPCTOOL
akutz10cd1402019-10-23 18:06:39 -0500582 return None
583
584
akutzffc4dd52019-06-02 11:34:55 -0500585def main():
586 '''
587 Executed when this file is used as a program.
588 '''
589 metadata = {'network': {'config': {'dhcp': True}}}
590 host_info = get_host_info()
591 metadata = always_merger.merge(metadata, host_info)
592 print(util.json_dumps(metadata))
593
594
akutz0d1fce52019-06-01 18:54:29 -0500595if __name__ == "__main__":
akutzffc4dd52019-06-02 11:34:55 -0500596 main()
akutz0d1fce52019-06-01 18:54:29 -0500597
598# vi: ts=4 expandtab