| akutz | dd794a4 | 2018-09-18 10:04:21 -0500 | [diff] [blame] | 1 | # 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. | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 12 | # | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 13 | # Authors: Anish Swaminathan <anishs@vmware.com> | 
 | 14 | #          Andrew Kutz <akutz@vmware.com> | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 15 | # | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 16 |  | 
 | 17 | ''' | 
 | 18 | A cloud init datasource for VMware GuestInfo. | 
 | 19 | ''' | 
 | 20 |  | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 21 | import base64 | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 22 | import collections | 
| akutz | 84389c8 | 2019-06-02 19:24:38 -0500 | [diff] [blame] | 23 | import copy | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 24 | from distutils.spawn import find_executable | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 25 | from distutils.util import strtobool | 
| akutz | 1cce7fa | 2020-03-24 11:01:45 -0500 | [diff] [blame] | 26 | import ipaddress | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 27 | import json | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 28 | import os | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 29 | import socket | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 30 | import string | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 31 | import time | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 32 | import zlib | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 33 |  | 
 | 34 | from cloudinit import log as logging | 
 | 35 | from cloudinit import sources | 
 | 36 | from cloudinit import util | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 37 | from cloudinit import safeyaml | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 38 |  | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 39 | from deepmerge import always_merger | 
 | 40 | import netifaces | 
 | 41 |  | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 42 | LOG = logging.getLogger(__name__) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 43 | NOVAL = "No value found" | 
| yvespp | 8d58dfd | 2019-10-29 20:42:09 +0100 | [diff] [blame] | 44 | VMWARE_RPCTOOL = find_executable("vmware-rpctool") | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 45 | VMX_GUESTINFO = "VMX_GUESTINFO" | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 46 | GUESTINFO_EMPTY_YAML_VAL = "---" | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 47 | LOCAL_IPV4 = 'local-ipv4' | 
 | 48 | LOCAL_IPV6 = 'local-ipv6' | 
| Yves Peter | 59c869d | 2019-12-11 13:09:14 +0100 | [diff] [blame] | 49 | CLEANUP_GUESTINFO = 'cleanup-guestinfo' | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 50 | WAIT_ON_NETWORK = 'wait-on-network' | 
 | 51 | WAIT_ON_NETWORK_IPV4 = 'ipv4' | 
 | 52 | WAIT_ON_NETWORK_IPV6 = 'ipv6' | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 53 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 54 |  | 
 | 55 | class NetworkConfigError(Exception): | 
 | 56 |     ''' | 
 | 57 |     NetworkConfigError is raised when there is an issue getting or | 
 | 58 |     applying network configuration. | 
 | 59 |     ''' | 
 | 60 |     pass | 
 | 61 |  | 
 | 62 |  | 
| Andrew Kutz | 4f66b8b | 2018-09-16 18:28:59 -0500 | [diff] [blame] | 63 | class DataSourceVMwareGuestInfo(sources.DataSource): | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 64 |     ''' | 
 | 65 |     This cloud-init datasource was designed for use with CentOS 7, | 
 | 66 |     which uses cloud-init 0.7.9. However, this datasource should | 
 | 67 |     work with any Linux distribution for which cloud-init is | 
 | 68 |     avaialble. | 
 | 69 |  | 
 | 70 |     The documentation for cloud-init 0.7.9's datasource is | 
 | 71 |     available at http://bit.ly/cloudinit-datasource-0-7-9. The | 
 | 72 |     current documentation for cloud-init is found at | 
 | 73 |     https://cloudinit.readthedocs.io/en/latest/. | 
 | 74 |  | 
 | 75 |     Setting the hostname: | 
 | 76 |         The hostname is set by way of the metadata key "local-hostname". | 
 | 77 |  | 
 | 78 |     Setting the instance ID: | 
 | 79 |         The instance ID may be set by way of the metadata key "instance-id". | 
 | 80 |         However, if this value is absent then then the instance ID is | 
 | 81 |         read from the file /sys/class/dmi/id/product_uuid. | 
 | 82 |  | 
 | 83 |     Configuring the network: | 
 | 84 |         The network is configured by setting the metadata key "network" | 
 | 85 |         with a value consistent with Network Config Versions 1 or 2, | 
 | 86 |         depending on the Linux distro's version of cloud-init: | 
 | 87 |  | 
 | 88 |             Network Config Version 1 - http://bit.ly/cloudinit-net-conf-v1 | 
 | 89 |             Network Config Version 2 - http://bit.ly/cloudinit-net-conf-v2 | 
 | 90 |  | 
 | 91 |         For example, CentOS 7's official cloud-init package is version | 
 | 92 |         0.7.9 and does not support Network Config Version 2. However, | 
 | 93 |         this datasource still supports supplying Network Config Version 2 | 
 | 94 |         data as long as the Linux distro's cloud-init package is new | 
 | 95 |         enough to parse the data. | 
 | 96 |  | 
 | 97 |         The metadata key "network.encoding" may be used to indicate the | 
 | 98 |         format of the metadata key "network". Valid encodings are base64 | 
 | 99 |         and gzip+base64. | 
 | 100 |     ''' | 
 | 101 |  | 
 | 102 |     dsname = 'VMwareGuestInfo' | 
 | 103 |  | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 104 |     def __init__(self, sys_cfg, distro, paths, ud_proc=None): | 
 | 105 |         sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc) | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 106 |         if not get_data_access_method(): | 
| yvespp | 8d58dfd | 2019-10-29 20:42:09 +0100 | [diff] [blame] | 107 |             LOG.error("Failed to find vmware-rpctool") | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 108 |  | 
 | 109 |     def get_data(self): | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 110 |         """ | 
 | 111 |         This method should really be _get_data in accordance with the most | 
 | 112 |         recent versions of cloud-init. However, because the datasource | 
 | 113 |         supports as far back as cloud-init 0.7.9, get_data is still used. | 
 | 114 |  | 
 | 115 |         Because of this the method attempts to do some of the same things | 
 | 116 |         that the get_data functions in newer versions of cloud-init do, | 
 | 117 |         such as calling persist_instance_data. | 
 | 118 |         """ | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 119 |         data_access_method = get_data_access_method() | 
 | 120 |         if not data_access_method: | 
| yvespp | 8d58dfd | 2019-10-29 20:42:09 +0100 | [diff] [blame] | 121 |             LOG.error("vmware-rpctool is required to fetch guestinfo value") | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 122 |             return False | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 123 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 124 |         # Get the metadata. | 
 | 125 |         self.metadata = load_metadata() | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 126 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 127 |         # Get the user data. | 
 | 128 |         self.userdata_raw = guestinfo('userdata') | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 129 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 130 |         # Get the vendor data. | 
 | 131 |         self.vendordata_raw = guestinfo('vendordata') | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 132 |  | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 133 |         # Check to see if any of the guestinfo data should be removed. | 
| Yves Peter | 59c869d | 2019-12-11 13:09:14 +0100 | [diff] [blame] | 134 |         if data_access_method == VMWARE_RPCTOOL and CLEANUP_GUESTINFO in self.metadata: | 
 | 135 |             clear_guestinfo_keys(self.metadata[CLEANUP_GUESTINFO]) | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 136 |  | 
| Keerthana K | f0d27ea | 2019-09-05 21:46:31 +0530 | [diff] [blame] | 137 |         if self.metadata or self.userdata_raw or self.vendordata_raw: | 
 | 138 |             return True | 
 | 139 |         else: | 
 | 140 |             return False | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 141 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 142 |     def setup(self, is_new_instance): | 
 | 143 |         """setup(is_new_instance) | 
 | 144 |  | 
 | 145 |         This is called before user-data and vendor-data have been processed. | 
 | 146 |  | 
 | 147 |         Unless the datasource has set mode to 'local', then networking | 
 | 148 |         per 'fallback' or per 'network_config' will have been written and | 
 | 149 |         brought up the OS at this point. | 
 | 150 |         """ | 
 | 151 |  | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 152 |         host_info = wait_on_network(self.metadata) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 153 |         LOG.info("got host-info: %s", host_info) | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 154 |  | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 155 |         # Reflect any possible local IPv4 or IPv6 addresses in the guest | 
 | 156 |         # info. | 
 | 157 |         advertise_local_ip_addrs(host_info) | 
 | 158 |  | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 159 |         # Ensure the metadata gets updated with information about the | 
 | 160 |         # host, including the network interfaces, default IP addresses, | 
 | 161 |         # etc. | 
 | 162 |         self.metadata = always_merger.merge(self.metadata, host_info) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 163 |  | 
 | 164 |         # Persist the instance data for versions of cloud-init that support | 
 | 165 |         # doing so. This occurs here rather than in the get_data call in | 
 | 166 |         # order to ensure that the network interfaces are up and can be | 
 | 167 |         # persisted with the metadata. | 
 | 168 |         try: | 
 | 169 |             self.persist_instance_data() | 
 | 170 |         except AttributeError: | 
 | 171 |             pass | 
 | 172 |  | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 173 |     @property | 
 | 174 |     def network_config(self): | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 175 |         if 'network' in self.metadata: | 
 | 176 |             LOG.debug("using metadata network config") | 
 | 177 |         else: | 
 | 178 |             LOG.debug("using fallback network config") | 
 | 179 |             self.metadata['network'] = { | 
 | 180 |                 'config': self.distro.generate_fallback_config(), | 
 | 181 |             } | 
 | 182 |         return self.metadata['network']['config'] | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 183 |  | 
 | 184 |     def get_instance_id(self): | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 185 |         # Pull the instance ID out of the metadata if present. Otherwise | 
 | 186 |         # read the file /sys/class/dmi/id/product_uuid for the instance ID. | 
 | 187 |         if self.metadata and 'instance-id' in self.metadata: | 
 | 188 |             return self.metadata['instance-id'] | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 189 |         with open('/sys/class/dmi/id/product_uuid', 'r') as id_file: | 
| Andrey Klimentyev | e1c5ed4 | 2019-10-09 12:32:44 +0300 | [diff] [blame] | 190 |             self.metadata['instance-id'] = str(id_file.read()).rstrip().lower() | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 191 |             return self.metadata['instance-id'] | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 192 |  | 
| Andrey Klimentyev | a23229d | 2019-10-17 15:30:00 +0300 | [diff] [blame] | 193 |     def get_public_ssh_keys(self): | 
 | 194 |         public_keys_data = "" | 
 | 195 |         if 'public-keys-data' in self.metadata: | 
 | 196 |             public_keys_data = self.metadata['public-keys-data'].splitlines() | 
 | 197 |  | 
 | 198 |         public_keys = [] | 
 | 199 |         if not public_keys_data: | 
 | 200 |             return public_keys | 
 | 201 |  | 
 | 202 |         for public_key in public_keys_data: | 
 | 203 |             public_keys.append(public_key) | 
 | 204 |  | 
 | 205 |         return public_keys | 
 | 206 |  | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 207 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 208 | def decode(key, enc_type, data): | 
 | 209 |     ''' | 
 | 210 |     decode returns the decoded string value of data | 
 | 211 |     key is a string used to identify the data being decoded in log messages | 
 | 212 |     ---- | 
 | 213 |     In py 2.7: | 
 | 214 |     json.loads method takes string as input | 
 | 215 |     zlib.decompress takes and returns a string | 
 | 216 |     base64.b64decode takes and returns a string | 
 | 217 |     ----- | 
 | 218 |     In py 3.6 and newer: | 
 | 219 |     json.loads method takes bytes or string as input | 
 | 220 |     zlib.decompress takes and returns a bytes | 
 | 221 |     base64.b64decode takes bytes or string and returns bytes | 
 | 222 |     ----- | 
 | 223 |     In py > 3, < 3.6: | 
 | 224 |     json.loads method takes string as input | 
 | 225 |     zlib.decompress takes and returns a bytes | 
 | 226 |     base64.b64decode takes bytes or string and returns bytes | 
 | 227 |     ----- | 
 | 228 |     Given the above conditions the output from zlib.decompress and | 
 | 229 |     base64.b64decode would be bytes with newer python and str in older | 
 | 230 |     version. Thus we would covert the output to str before returning | 
 | 231 |     ''' | 
 | 232 |     LOG.debug("Getting encoded data for key=%s, enc=%s", key, enc_type) | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 233 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 234 |     raw_data = None | 
 | 235 |     if enc_type == "gzip+base64" or enc_type == "gz+b64": | 
 | 236 |         LOG.debug("Decoding %s format %s", enc_type, key) | 
 | 237 |         raw_data = zlib.decompress(base64.b64decode(data), zlib.MAX_WBITS | 16) | 
 | 238 |     elif enc_type == "base64" or enc_type == "b64": | 
 | 239 |         LOG.debug("Decoding %s format %s", enc_type, key) | 
 | 240 |         raw_data = base64.b64decode(data) | 
 | 241 |     else: | 
 | 242 |         LOG.debug("Plain-text data %s", key) | 
 | 243 |         raw_data = data | 
| Sidharth Surana | 3a42168 | 2018-10-10 15:42:08 -0700 | [diff] [blame] | 244 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 245 |     if isinstance(raw_data, bytes): | 
 | 246 |         return raw_data.decode('utf-8') | 
 | 247 |     return raw_data | 
 | 248 |  | 
 | 249 |  | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 250 | def get_none_if_empty_val(val): | 
 | 251 |     ''' | 
 | 252 |     get_none_if_empty_val returns None if the provided value, once stripped | 
 | 253 |     of its trailing whitespace, is empty or equal to GUESTINFO_EMPTY_YAML_VAL. | 
 | 254 |  | 
 | 255 |     The return value is always a string, regardless of whether the input is | 
 | 256 |     a bytes class or a string. | 
 | 257 |     ''' | 
 | 258 |  | 
 | 259 |     # If the provided value is a bytes class, convert it to a string to | 
 | 260 |     # simplify the rest of this function's logic. | 
 | 261 |     if isinstance(val, bytes): | 
 | 262 |         val = val.decode() | 
 | 263 |  | 
 | 264 |     val = val.rstrip() | 
 | 265 |     if len(val) == 0 or val == GUESTINFO_EMPTY_YAML_VAL: | 
 | 266 |         return None | 
 | 267 |     return val | 
 | 268 |  | 
 | 269 |  | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 270 | def advertise_local_ip_addrs(host_info): | 
 | 271 |     ''' | 
 | 272 |     advertise_local_ip_addrs gets the local IP address information from | 
 | 273 |     the provided host_info map and sets the addresses in the guestinfo | 
 | 274 |     namespace | 
 | 275 |     ''' | 
 | 276 |     if not host_info: | 
 | 277 |         return | 
 | 278 |  | 
 | 279 |     # Reflect any possible local IPv4 or IPv6 addresses in the guest | 
 | 280 |     # info. | 
| Yves Peter | 9dcf3dc | 2019-12-11 13:12:34 +0100 | [diff] [blame] | 281 |     local_ipv4 = host_info.get(LOCAL_IPV4) | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 282 |     if local_ipv4: | 
 | 283 |         set_guestinfo_value(LOCAL_IPV4, local_ipv4) | 
 | 284 |         LOG.info("advertised local ipv4 address %s in guestinfo", local_ipv4) | 
 | 285 |  | 
| Yves Peter | 9dcf3dc | 2019-12-11 13:12:34 +0100 | [diff] [blame] | 286 |     local_ipv6 = host_info.get(LOCAL_IPV6) | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 287 |     if local_ipv6: | 
 | 288 |         set_guestinfo_value(LOCAL_IPV6, local_ipv6) | 
 | 289 |         LOG.info("advertised local ipv6 address %s in guestinfo", local_ipv6) | 
 | 290 |  | 
 | 291 |  | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 292 | def handle_returned_guestinfo_val(key, val): | 
 | 293 |     ''' | 
 | 294 |     handle_returned_guestinfo_val returns the provided value if it is | 
 | 295 |     not empty or set to GUESTINFO_EMPTY_YAML_VAL, otherwise None is | 
 | 296 |     returned | 
 | 297 |     ''' | 
 | 298 |     val = get_none_if_empty_val(val) | 
 | 299 |     if val: | 
 | 300 |         return val | 
 | 301 |     LOG.debug("No value found for key %s", key) | 
 | 302 |     return None | 
 | 303 |  | 
 | 304 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 305 | def get_guestinfo_value(key): | 
 | 306 |     ''' | 
 | 307 |     Returns a guestinfo value for the specified key. | 
 | 308 |     ''' | 
 | 309 |     LOG.debug("Getting guestinfo value for key %s", key) | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 310 |  | 
 | 311 |     data_access_method = get_data_access_method() | 
 | 312 |  | 
 | 313 |     if data_access_method == VMX_GUESTINFO: | 
 | 314 |         env_key = ("vmx.guestinfo." + key).upper().replace(".", "_", -1) | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 315 |         return handle_returned_guestinfo_val(key, os.environ.get(env_key, "")) | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 316 |  | 
| yvespp | 8d58dfd | 2019-10-29 20:42:09 +0100 | [diff] [blame] | 317 |     if data_access_method == VMWARE_RPCTOOL: | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 318 |         try: | 
 | 319 |             (stdout, stderr) = util.subp( | 
| yvespp | 8d58dfd | 2019-10-29 20:42:09 +0100 | [diff] [blame] | 320 |                 [VMWARE_RPCTOOL, "info-get guestinfo." + key]) | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 321 |             if stderr == NOVAL: | 
 | 322 |                 LOG.debug("No value found for key %s", key) | 
 | 323 |             elif not stdout: | 
 | 324 |                 LOG.error("Failed to get guestinfo value for key %s", key) | 
 | 325 |             else: | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 326 |                 return handle_returned_guestinfo_val(key, stdout) | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 327 |         except util.ProcessExecutionError as error: | 
 | 328 |             if error.stderr == NOVAL: | 
 | 329 |                 LOG.debug("No value found for key %s", key) | 
 | 330 |             else: | 
 | 331 |                 util.logexc( | 
 | 332 |                     LOG, "Failed to get guestinfo value for key %s: %s", key, error) | 
 | 333 |         except Exception: | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 334 |             util.logexc( | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 335 |                 LOG, "Unexpected error while trying to get guestinfo value for key %s", key) | 
 | 336 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 337 |     return None | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 338 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 339 |  | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 340 | def set_guestinfo_value(key, value): | 
 | 341 |     ''' | 
 | 342 |     Sets a guestinfo value for the specified key. Set value to an empty string | 
 | 343 |     to clear an existing guestinfo key. | 
 | 344 |     ''' | 
 | 345 |  | 
 | 346 |     # If value is an empty string then set it to a single space as it is not | 
 | 347 |     # possible to set a guestinfo key to an empty string. Setting a guestinfo | 
 | 348 |     # key to a single space is as close as it gets to clearing an existing | 
 | 349 |     # guestinfo key. | 
 | 350 |     if value == "": | 
 | 351 |         value = " " | 
 | 352 |  | 
 | 353 |     LOG.debug("Setting guestinfo key=%s to value=%s", key, value) | 
 | 354 |  | 
 | 355 |     data_access_method = get_data_access_method() | 
 | 356 |  | 
 | 357 |     if data_access_method == VMX_GUESTINFO: | 
 | 358 |         return True | 
 | 359 |  | 
 | 360 |     if data_access_method == VMWARE_RPCTOOL: | 
 | 361 |         try: | 
 | 362 |             util.subp( | 
 | 363 |                 [VMWARE_RPCTOOL, ("info-set guestinfo.%s %s" % (key, value))]) | 
 | 364 |             return True | 
 | 365 |         except util.ProcessExecutionError as error: | 
 | 366 |             util.logexc( | 
 | 367 |                 LOG, "Failed to set guestinfo key=%s to value=%s: %s", key, value, error) | 
 | 368 |         except Exception: | 
 | 369 |             util.logexc( | 
 | 370 |                 LOG, "Unexpected error while trying to set guestinfo key=%s to value=%s", key, value) | 
 | 371 |  | 
 | 372 |     return None | 
 | 373 |  | 
 | 374 |  | 
 | 375 | def clear_guestinfo_keys(keys): | 
 | 376 |     ''' | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 377 |     clear_guestinfo_keys clears guestinfo of all of the keys in the given list. | 
 | 378 |     each key will have its value set to "---". Since the value is valid YAML, | 
 | 379 |     cloud-init can still read it if it tries. | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 380 |     ''' | 
 | 381 |     if not keys: | 
 | 382 |         return | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 383 |     if not type(keys) in (list, tuple): | 
 | 384 |         keys = [keys] | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 385 |     for key in keys: | 
| akutz | daf81f1 | 2019-12-08 11:20:33 -0600 | [diff] [blame] | 386 |         LOG.info("clearing guestinfo.%s", key) | 
 | 387 |         if not set_guestinfo_value(key, GUESTINFO_EMPTY_YAML_VAL): | 
 | 388 |             LOG.error("failed to clear guestinfo.%s", key) | 
 | 389 |         LOG.info("clearing guestinfo.%s.encoding", key) | 
 | 390 |         if not set_guestinfo_value(key + ".encoding", ""): | 
 | 391 |             LOG.error("failed to clear guestinfo.%s.encoding", key) | 
| akutz | dbce3d9 | 2019-12-08 00:09:11 -0600 | [diff] [blame] | 392 |  | 
 | 393 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 394 | def guestinfo(key): | 
 | 395 |     ''' | 
 | 396 |     guestinfo returns the guestinfo value for the provided key, decoding | 
 | 397 |     the value when required | 
 | 398 |     ''' | 
 | 399 |     data = get_guestinfo_value(key) | 
 | 400 |     if not data: | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 401 |         return None | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 402 |     enc_type = get_guestinfo_value(key + '.encoding') | 
 | 403 |     return decode('guestinfo.' + key, enc_type, data) | 
 | 404 |  | 
 | 405 |  | 
 | 406 | def load(data): | 
 | 407 |     ''' | 
 | 408 |     load first attempts to unmarshal the provided data as JSON, and if | 
 | 409 |     that fails then attempts to unmarshal the data as YAML. If data is | 
 | 410 |     None then a new dictionary is returned. | 
 | 411 |     ''' | 
 | 412 |     if not data: | 
 | 413 |         return {} | 
 | 414 |     try: | 
 | 415 |         return json.loads(data) | 
 | 416 |     except: | 
 | 417 |         return safeyaml.load(data) | 
 | 418 |  | 
 | 419 |  | 
 | 420 | def load_metadata(): | 
 | 421 |     ''' | 
 | 422 |     load_metadata loads the metadata from the guestinfo data, optionally | 
 | 423 |     decoding the network config when required | 
 | 424 |     ''' | 
 | 425 |     data = load(guestinfo('metadata')) | 
| akutz | 84389c8 | 2019-06-02 19:24:38 -0500 | [diff] [blame] | 426 |     LOG.debug('loaded metadata %s', data) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 427 |  | 
 | 428 |     network = None | 
 | 429 |     if 'network' in data: | 
 | 430 |         network = data['network'] | 
 | 431 |         del data['network'] | 
 | 432 |  | 
 | 433 |     network_enc = None | 
 | 434 |     if 'network.encoding' in data: | 
 | 435 |         network_enc = data['network.encoding'] | 
 | 436 |         del data['network.encoding'] | 
 | 437 |  | 
 | 438 |     if network: | 
| akutz | 84389c8 | 2019-06-02 19:24:38 -0500 | [diff] [blame] | 439 |         LOG.debug('network data found') | 
 | 440 |         if isinstance(network, collections.Mapping): | 
 | 441 |             LOG.debug("network data copied to 'config' key") | 
 | 442 |             network = { | 
 | 443 |                 'config': copy.deepcopy(network) | 
 | 444 |             } | 
 | 445 |         else: | 
 | 446 |             LOG.debug("network data to be decoded %s", network) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 447 |             dec_net = decode('metadata.network', network_enc, network) | 
| akutz | 84389c8 | 2019-06-02 19:24:38 -0500 | [diff] [blame] | 448 |             network = { | 
 | 449 |                 'config': load(dec_net), | 
 | 450 |             } | 
 | 451 |  | 
 | 452 |         LOG.debug('network data %s', network) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 453 |         data['network'] = network | 
 | 454 |  | 
 | 455 |     return data | 
 | 456 |  | 
| akutz | 6501f90 | 2018-08-24 12:19:05 -0500 | [diff] [blame] | 457 |  | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 458 | def get_datasource_list(depends): | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 459 |     ''' | 
| akutz | 77457a6 | 2018-08-22 16:07:21 -0500 | [diff] [blame] | 460 |     Return a list of data sources that match this set of dependencies | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 461 |     ''' | 
| Andrew Kutz | 4f66b8b | 2018-09-16 18:28:59 -0500 | [diff] [blame] | 462 |     return [DataSourceVMwareGuestInfo] | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 463 |  | 
 | 464 |  | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 465 | def get_default_ip_addrs(): | 
 | 466 |     ''' | 
 | 467 |     Returns the default IPv4 and IPv6 addresses based on the device(s) used for | 
 | 468 |     the default route. Please note that None may be returned for either address | 
 | 469 |     family if that family has no default route or if there are multiple | 
 | 470 |     addresses associated with the device used by the default route for a given | 
 | 471 |     address. | 
 | 472 |     ''' | 
 | 473 |     gateways = netifaces.gateways() | 
 | 474 |     if 'default' not in gateways: | 
 | 475 |         return None, None | 
 | 476 |  | 
 | 477 |     default_gw = gateways['default'] | 
 | 478 |     if netifaces.AF_INET not in default_gw and netifaces.AF_INET6 not in default_gw: | 
 | 479 |         return None, None | 
 | 480 |  | 
 | 481 |     ipv4 = None | 
 | 482 |     ipv6 = None | 
 | 483 |  | 
 | 484 |     gw4 = default_gw.get(netifaces.AF_INET) | 
 | 485 |     if gw4: | 
 | 486 |         _, dev4 = gw4 | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 487 |         addr4_fams = netifaces.ifaddresses(dev4) | 
 | 488 |         if addr4_fams: | 
 | 489 |             af_inet4 = addr4_fams.get(netifaces.AF_INET) | 
 | 490 |             if af_inet4: | 
 | 491 |                 if len(af_inet4) > 1: | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 492 |                     LOG.warn( | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 493 |                         "device %s has more than one ipv4 address: %s", dev4, af_inet4) | 
 | 494 |                 elif 'addr' in af_inet4[0]: | 
 | 495 |                     ipv4 = af_inet4[0]['addr'] | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 496 |  | 
 | 497 |     # Try to get the default IPv6 address by first seeing if there is a default | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 498 |     # IPv6 route. | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 499 |     gw6 = default_gw.get(netifaces.AF_INET6) | 
 | 500 |     if gw6: | 
 | 501 |         _, dev6 = gw6 | 
 | 502 |         addr6_fams = netifaces.ifaddresses(dev6) | 
 | 503 |         if addr6_fams: | 
 | 504 |             af_inet6 = addr6_fams.get(netifaces.AF_INET6) | 
 | 505 |             if af_inet6: | 
 | 506 |                 if len(af_inet6) > 1: | 
 | 507 |                     LOG.warn( | 
 | 508 |                         "device %s has more than one ipv6 address: %s", dev6, af_inet6) | 
 | 509 |                 elif 'addr' in af_inet6[0]: | 
 | 510 |                     ipv6 = af_inet6[0]['addr'] | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 511 |  | 
 | 512 |     # If there is a default IPv4 address but not IPv6, then see if there is a | 
 | 513 |     # single IPv6 address associated with the same device associated with the | 
 | 514 |     # default IPv4 address. | 
 | 515 |     if ipv4 and not ipv6: | 
 | 516 |         af_inet6 = addr4_fams.get(netifaces.AF_INET6) | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 517 |         if af_inet6: | 
 | 518 |             if len(af_inet6) > 1: | 
 | 519 |                 LOG.warn( | 
 | 520 |                     "device %s has more than one ipv6 address: %s", dev4, af_inet6) | 
 | 521 |             elif 'addr' in af_inet6[0]: | 
 | 522 |                 ipv6 = af_inet6[0]['addr'] | 
 | 523 |  | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 524 |     # If there is a default IPv6 address but not IPv4, then see if there is a | 
 | 525 |     # single IPv4 address associated with the same device associated with the | 
 | 526 |     # default IPv6 address. | 
 | 527 |     if not ipv4 and ipv6: | 
 | 528 |         af_inet4 = addr6_fams.get(netifaces.AF_INET4) | 
 | 529 |         if af_inet4: | 
 | 530 |             if len(af_inet4) > 1: | 
 | 531 |                 LOG.warn( | 
 | 532 |                     "device %s has more than one ipv4 address: %s", dev6, af_inet4) | 
 | 533 |             elif 'addr' in af_inet4[0]: | 
 | 534 |                 ipv4 = af_inet4[0]['addr'] | 
 | 535 |  | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 536 |     return ipv4, ipv6 | 
 | 537 |  | 
| Keerthana K | f0d27ea | 2019-09-05 21:46:31 +0530 | [diff] [blame] | 538 | # patched socket.getfqdn() - see https://bugs.python.org/issue5004 | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 539 |  | 
 | 540 |  | 
| Keerthana K | f0d27ea | 2019-09-05 21:46:31 +0530 | [diff] [blame] | 541 | def getfqdn(name=''): | 
 | 542 |     """Get fully qualified domain name from name. | 
 | 543 |      An empty argument is interpreted as meaning the local host. | 
 | 544 |     """ | 
 | 545 |     name = name.strip() | 
 | 546 |     if not name or name == '0.0.0.0': | 
 | 547 |         name = socket.gethostname() | 
 | 548 |     try: | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 549 |         addrs = socket.getaddrinfo( | 
 | 550 |             name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME) | 
| Keerthana K | f0d27ea | 2019-09-05 21:46:31 +0530 | [diff] [blame] | 551 |     except socket.error: | 
 | 552 |         pass | 
 | 553 |     else: | 
 | 554 |         for addr in addrs: | 
 | 555 |             if addr[3]: | 
 | 556 |                 name = addr[3] | 
 | 557 |                 break | 
 | 558 |     return name | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 559 |  | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 560 |  | 
| akutz | 1cce7fa | 2020-03-24 11:01:45 -0500 | [diff] [blame] | 561 | def is_valid_ip_addr(val): | 
 | 562 |     """ | 
 | 563 |     Returns false if the address is loopback, link local or unspecified; | 
 | 564 |     otherwise true is returned. | 
 | 565 |     """ | 
 | 566 |     addr = None | 
 | 567 |     try: | 
 | 568 |         addr = ipaddress.ip_address(val) | 
 | 569 |     except: | 
 | 570 |         return False | 
 | 571 |     if addr.is_link_local or addr.is_loopback or addr.is_unspecified: | 
 | 572 |         return False | 
 | 573 |     return True | 
 | 574 |  | 
 | 575 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 576 | def get_host_info(): | 
 | 577 |     ''' | 
 | 578 |     Returns host information such as the host name and network interfaces. | 
 | 579 |     ''' | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 580 |  | 
 | 581 |     host_info = { | 
 | 582 |         'network': { | 
 | 583 |             'interfaces': { | 
 | 584 |                 'by-mac': collections.OrderedDict(), | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 585 |                 'by-ipv4': collections.OrderedDict(), | 
 | 586 |                 'by-ipv6': collections.OrderedDict(), | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 587 |             }, | 
 | 588 |         }, | 
 | 589 |     } | 
 | 590 |  | 
| Keerthana K | f0d27ea | 2019-09-05 21:46:31 +0530 | [diff] [blame] | 591 |     hostname = getfqdn(socket.gethostname()) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 592 |     if hostname: | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 593 |         host_info['hostname'] = hostname | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 594 |         host_info['local-hostname'] = hostname | 
| akutz | b7a193d | 2019-12-09 11:36:32 -0600 | [diff] [blame] | 595 |         host_info['local_hostname'] = hostname | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 596 |  | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 597 |     default_ipv4, default_ipv6 = get_default_ip_addrs() | 
 | 598 |     if default_ipv4: | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 599 |         host_info[LOCAL_IPV4] = default_ipv4 | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 600 |     if default_ipv6: | 
| akutz | 909cf9a | 2019-12-09 09:17:00 -0600 | [diff] [blame] | 601 |         host_info[LOCAL_IPV6] = default_ipv6 | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 602 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 603 |     by_mac = host_info['network']['interfaces']['by-mac'] | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 604 |     by_ipv4 = host_info['network']['interfaces']['by-ipv4'] | 
 | 605 |     by_ipv6 = host_info['network']['interfaces']['by-ipv6'] | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 606 |  | 
 | 607 |     ifaces = netifaces.interfaces() | 
 | 608 |     for dev_name in ifaces: | 
 | 609 |         addr_fams = netifaces.ifaddresses(dev_name) | 
 | 610 |         af_link = addr_fams.get(netifaces.AF_LINK) | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 611 |         af_inet4 = addr_fams.get(netifaces.AF_INET) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 612 |         af_inet6 = addr_fams.get(netifaces.AF_INET6) | 
 | 613 |  | 
 | 614 |         mac = None | 
 | 615 |         if af_link and 'addr' in af_link[0]: | 
 | 616 |             mac = af_link[0]['addr'] | 
 | 617 |  | 
 | 618 |         # Do not bother recording localhost | 
 | 619 |         if mac == "00:00:00:00:00:00": | 
 | 620 |             continue | 
 | 621 |  | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 622 |         if mac and (af_inet4 or af_inet6): | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 623 |             key = mac | 
 | 624 |             val = {} | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 625 |             if af_inet4: | 
| akutz | 1cce7fa | 2020-03-24 11:01:45 -0500 | [diff] [blame] | 626 |                 af_inet4_vals = [] | 
 | 627 |                 for ip_info in af_inet4: | 
 | 628 |                     if not is_valid_ip_addr(ip_info['addr']): | 
 | 629 |                         continue | 
 | 630 |                     af_inet4_vals.append(ip_info) | 
 | 631 |                 val["ipv4"] = af_inet4_vals | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 632 |             if af_inet6: | 
| akutz | 1cce7fa | 2020-03-24 11:01:45 -0500 | [diff] [blame] | 633 |                 af_inet6_vals = [] | 
 | 634 |                 for ip_info in af_inet6: | 
 | 635 |                     if not is_valid_ip_addr(ip_info['addr']): | 
 | 636 |                         continue | 
 | 637 |                     af_inet6_vals.append(ip_info) | 
 | 638 |                 val["ipv6"] = af_inet6_vals | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 639 |             by_mac[key] = val | 
 | 640 |  | 
| akutz | 0b519f7 | 2019-06-02 14:58:57 -0500 | [diff] [blame] | 641 |         if af_inet4: | 
 | 642 |             for ip_info in af_inet4: | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 643 |                 key = ip_info['addr'] | 
| akutz | 1cce7fa | 2020-03-24 11:01:45 -0500 | [diff] [blame] | 644 |                 if not is_valid_ip_addr(key): | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 645 |                     continue | 
| akutz | 84389c8 | 2019-06-02 19:24:38 -0500 | [diff] [blame] | 646 |                 val = copy.deepcopy(ip_info) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 647 |                 del val['addr'] | 
 | 648 |                 if mac: | 
 | 649 |                     val['mac'] = mac | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 650 |                 by_ipv4[key] = val | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 651 |  | 
 | 652 |         if af_inet6: | 
 | 653 |             for ip_info in af_inet6: | 
 | 654 |                 key = ip_info['addr'] | 
| akutz | 1cce7fa | 2020-03-24 11:01:45 -0500 | [diff] [blame] | 655 |                 if not is_valid_ip_addr(key): | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 656 |                     continue | 
| akutz | 84389c8 | 2019-06-02 19:24:38 -0500 | [diff] [blame] | 657 |                 val = copy.deepcopy(ip_info) | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 658 |                 del val['addr'] | 
 | 659 |                 if mac: | 
 | 660 |                     val['mac'] = mac | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 661 |                 by_ipv6[key] = val | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 662 |  | 
 | 663 |     return host_info | 
 | 664 |  | 
 | 665 |  | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 666 | def wait_on_network(metadata): | 
 | 667 |     # Determine whether we need to wait on the network coming online. | 
 | 668 |     wait_on_ipv4 = False | 
 | 669 |     wait_on_ipv6 = False | 
 | 670 |     if WAIT_ON_NETWORK in metadata: | 
 | 671 |         wait_on_network = metadata[WAIT_ON_NETWORK] | 
 | 672 |         if WAIT_ON_NETWORK_IPV4 in wait_on_network: | 
 | 673 |             wait_on_ipv4_val = wait_on_network[WAIT_ON_NETWORK_IPV4] | 
 | 674 |             if isinstance(wait_on_ipv4_val, bool): | 
 | 675 |                 wait_on_ipv4 = wait_on_ipv4_val | 
 | 676 |             else: | 
 | 677 |                 wait_on_ipv4 = bool(strtobool(wait_on_ipv4_val)) | 
 | 678 |         if WAIT_ON_NETWORK_IPV6 in wait_on_network: | 
 | 679 |             wait_on_ipv6_val = wait_on_network[WAIT_ON_NETWORK_IPV6] | 
 | 680 |             if isinstance(wait_on_ipv6_val, bool): | 
 | 681 |                 wait_on_ipv6 = wait_on_ipv6_val | 
 | 682 |             else: | 
 | 683 |                 wait_on_ipv6 = bool(strtobool(wait_on_ipv6_val)) | 
 | 684 |  | 
 | 685 |     # Get information about the host. | 
 | 686 |     host_info = None | 
 | 687 |     while host_info == None: | 
 | 688 |         host_info = get_host_info() | 
| akutz | e16f5e8 | 2020-03-24 11:11:07 -0500 | [diff] [blame] | 689 |         if wait_on_ipv4: | 
 | 690 |             ipv4_ready = False | 
 | 691 |             if 'network' in host_info: | 
 | 692 |                 if 'interfaces' in host_info['network']: | 
 | 693 |                     if 'by-ipv4' in host_info['network']['interfaces']: | 
 | 694 |                         if len(host_info['network']['interfaces']['by-ipv4']) > 0: | 
 | 695 |                             ipv4_ready = True | 
 | 696 |             if not ipv4_ready: | 
 | 697 |                 LOG.info("ipv4 not ready") | 
 | 698 |                 host_info = None | 
 | 699 |         if wait_on_ipv6: | 
 | 700 |             ipv6_ready = False | 
 | 701 |             if 'network' in host_info: | 
 | 702 |                 if 'interfaces' in host_info['network']: | 
 | 703 |                     if 'by-ipv6' in host_info['network']['interfaces']: | 
 | 704 |                         if len(host_info['network']['interfaces']['by-ipv6']) > 0: | 
 | 705 |                             ipv6_ready = True | 
 | 706 |             if not ipv6_ready: | 
 | 707 |                 LOG.info("ipv6 not ready") | 
 | 708 |                 host_info = None | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 709 |         if host_info == None: | 
 | 710 |             LOG.info("waiting on network") | 
 | 711 |             time.sleep(1) | 
 | 712 |  | 
 | 713 |     return host_info | 
 | 714 |  | 
 | 715 |  | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 716 | def get_data_access_method(): | 
 | 717 |     if os.environ.get(VMX_GUESTINFO, ""): | 
 | 718 |         return VMX_GUESTINFO | 
| yvespp | 8d58dfd | 2019-10-29 20:42:09 +0100 | [diff] [blame] | 719 |     if VMWARE_RPCTOOL: | 
 | 720 |         return VMWARE_RPCTOOL | 
| akutz | 10cd140 | 2019-10-23 18:06:39 -0500 | [diff] [blame] | 721 |     return None | 
 | 722 |  | 
 | 723 |  | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 724 | def main(): | 
 | 725 |     ''' | 
 | 726 |     Executed when this file is used as a program. | 
 | 727 |     ''' | 
| akutz | 33dbfc2 | 2020-03-23 13:43:07 -0500 | [diff] [blame] | 728 |     metadata = {'wait-on-network': {'ipv4': True, 'ipv6': "false"}, | 
 | 729 |                 'network': {'config': {'dhcp': True}}} | 
 | 730 |     host_info = wait_on_network(metadata) | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 731 |     metadata = always_merger.merge(metadata, host_info) | 
 | 732 |     print(util.json_dumps(metadata)) | 
 | 733 |  | 
 | 734 |  | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 735 | if __name__ == "__main__": | 
| akutz | ffc4dd5 | 2019-06-02 11:34:55 -0500 | [diff] [blame] | 736 |     main() | 
| akutz | 0d1fce5 | 2019-06-01 18:54:29 -0500 | [diff] [blame] | 737 |  | 
 | 738 | # vi: ts=4 expandtab |