1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.cluster; 17 18 import com.android.tradefed.command.remote.DeviceDescriptor; 19 import com.android.tradefed.config.GlobalConfiguration; 20 import com.android.tradefed.device.DeviceManager; 21 import com.android.tradefed.device.DeviceManager.FastbootDevice; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.util.VersionParser; 24 25 import com.google.common.base.Strings; 26 import com.google.common.net.HostAndPort; 27 import com.google.common.primitives.Longs; 28 29 import java.net.Inet6Address; 30 import java.net.InetAddress; 31 import java.net.NetworkInterface; 32 import java.net.SocketException; 33 import java.net.UnknownHostException; 34 import java.security.InvalidParameterException; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.Enumeration; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.UUID; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 /** Static util functions for TF Cluster to get global config instances, host information, etc. */ 45 public class ClusterHostUtil { 46 47 private static String sHostName = null; 48 49 private static String sHostIpAddress = null; 50 51 static final String DEFAULT_TF_VERSION = "(unknown)"; 52 static final String EMULATOR_SERIAL_PREFIX = "emulator-"; 53 static final String NULL_DEVICE_SERIAL_PLACEHOLDER = "(no device serial)"; 54 static final String UNKNOWN = "UNKNOWN"; 55 static final String TRADEFED = "TRADEFED"; 56 static final String LOCALHOST_IP = "127.0.0.1"; 57 static final String LOCALHOST_NAME = "localhost"; 58 59 private static long sTfStartTime = getCurrentTimeMillis(); 60 61 /** 62 * Gets the hostname. 63 * 64 * <p>1. Try to get hostname from InetAddress. 2. If fail, try to get hostname from HOSTNAME 65 * env. 3. If not set, generate a unique hostname. 66 * 67 * @return the hostname or null if we were unable to fetch it. 68 */ getHostName()69 public static String getHostName() { 70 if (sHostName != null) { 71 return sHostName; 72 } 73 try { 74 sHostName = InetAddress.getLocalHost().getHostName(); 75 return sHostName; 76 } catch (UnknownHostException e) { 77 CLog.w("Failed to get hostname from InetAddress: %s", e); 78 } 79 CLog.i("Get hostname from HOSTNAME env."); 80 sHostName = System.getenv("HOSTNAME"); 81 if (!Strings.isNullOrEmpty(sHostName)) { 82 return sHostName; 83 } 84 sHostName = "unknown-" + UUID.randomUUID().toString(); 85 CLog.i("No HOSTNAME env set. Generate hostname: %s.", sHostName); 86 return sHostName; 87 } 88 89 /** 90 * Returns a unique device serial for a device. 91 * 92 * <p>Non-physical devices (e.g. emulator) have pseudo serials which are not unique across 93 * hosts. This method prefixes those with a hostname to make them unique. 94 * 95 * @param device a device descriptor. 96 * @return a unique device serial. 97 */ getUniqueDeviceSerial(DeviceDescriptor device)98 public static String getUniqueDeviceSerial(DeviceDescriptor device) { 99 String serial = device.getSerial(); 100 if (Strings.isNullOrEmpty(serial)) { 101 return String.format("%s:%s", getHostName(), NULL_DEVICE_SERIAL_PLACEHOLDER); 102 } 103 if ((device.isStubDevice() 104 && !FastbootDevice.class.getSimpleName().equals(device.getDeviceClass())) 105 || serial.startsWith(EMULATOR_SERIAL_PREFIX)) { 106 return String.format("%s:%s", getHostName(), serial); 107 } 108 return serial; 109 } 110 111 /** 112 * Returns a local device serial for a given unique device serial. 113 * 114 * <p>TFC sends down unique device serials for non-physical devices which TF does not 115 * understand. This method converts them back to local device serials. 116 * 117 * @param serial a unique device serial from TFC. 118 * @return a local device serial. 119 */ getLocalDeviceSerial(String serial)120 public static String getLocalDeviceSerial(String serial) { 121 String prefix = getHostName() + ":"; 122 if (serial.startsWith(prefix)) { 123 return serial.substring(prefix.length()); 124 } 125 return serial; 126 } 127 /** 128 * Gets the IP address. 129 * 130 * @return the IPV4 address String or "UNKNOWN" if we were unable to fetch it. 131 */ getHostIpAddress()132 public static String getHostIpAddress() { 133 if (sHostIpAddress == null) { 134 List<InetAddress> addresses = new ArrayList<>(); 135 try { 136 Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); 137 if (interfaces == null) { 138 return UNKNOWN; 139 } 140 for (NetworkInterface networkInterface : Collections.list(interfaces)) { 141 if (!networkInterface.isUp() || networkInterface.isLoopback()) { 142 continue; 143 } 144 for (InetAddress address : 145 Collections.list(networkInterface.getInetAddresses())) { 146 if (address.isLinkLocalAddress() 147 || address.isLoopbackAddress() 148 || address instanceof Inet6Address) { 149 continue; 150 } 151 addresses.add(address); 152 } 153 } 154 } catch (SocketException e) { 155 CLog.w(e); 156 } 157 if (!addresses.isEmpty()) { 158 sHostIpAddress = addresses.get(0).getHostAddress(); 159 } 160 } 161 return sHostIpAddress == null ? UNKNOWN : sHostIpAddress; 162 } 163 164 /** 165 * Gets the TF version running on this host. 166 * 167 * @return this host's TF version. 168 */ getTfVersion()169 public static String getTfVersion() { 170 final String version = VersionParser.fetchVersion(); 171 return toValidTfVersion(version); 172 } 173 174 /** 175 * Validates a TF version and returns it if it is OK. 176 * 177 * @param version The string for a TF version provided by {@link VersionParser} 178 * @return the version if valid or a default if not. 179 */ toValidTfVersion(String version)180 protected static String toValidTfVersion(String version) { 181 if (Strings.isNullOrEmpty(version) || Longs.tryParse(version) == null) { 182 // Making sure the version is valid. It should be a build number 183 return DEFAULT_TF_VERSION; 184 } 185 return version; 186 } 187 188 /** 189 * Returns the run target for a given device descriptor. 190 * 191 * @param device {@link DeviceDescriptor} to get run target for. 192 * @return run target. 193 */ getRunTarget( DeviceDescriptor device, String runTargetFormat, Map<String, String> deviceTags)194 public static String getRunTarget( 195 DeviceDescriptor device, String runTargetFormat, Map<String, String> deviceTags) { 196 if (runTargetFormat != null) { 197 // Make sure the pattern is non-greedy. 198 Pattern p = Pattern.compile("\\{([^:\\}]+)(:.*)?\\}"); 199 Matcher m = p.matcher(runTargetFormat); 200 StringBuffer sb = new StringBuffer(); 201 while (m.find()) { 202 String pattern = m.group(1); 203 String key = null; 204 String txt = null; 205 switch (pattern) { 206 case "PRODUCT": 207 txt = device.getProduct(); 208 break; 209 case "PRODUCT_OR_DEVICE_CLASS": 210 // TODO: Refactor the logic to handle more flexible combinations. 211 txt = device.getProduct(); 212 if (device.isStubDevice()) { 213 String deviceClass = device.getDeviceClass(); 214 // If it's a fastboot device we report it as the product 215 if (!FastbootDevice.class.getSimpleName().equals(deviceClass)) { 216 txt = deviceClass; 217 } 218 } 219 break; 220 case "PRODUCT_VARIANT": 221 txt = device.getProductVariant(); 222 break; 223 case "API_LEVEL": 224 txt = device.getSdkVersion(); 225 break; 226 case "DEVICE_CLASS": 227 txt = device.getDeviceClass(); 228 break; 229 case "SERIAL": 230 txt = getUniqueDeviceSerial(device); 231 break; 232 case "TAG": 233 if (deviceTags == null || deviceTags.isEmpty()) { 234 // simply delete the placeholder if there's nothing to match 235 txt = ""; 236 } else { 237 txt = deviceTags.get(device.getSerial()); 238 if (txt == null) { 239 txt = ""; // simply delete it if a tag does not exist 240 } 241 } 242 break; 243 case "DEVICE_PROP": 244 key = m.group(2).substring(1); 245 txt = device.getProperty(key); 246 break; 247 default: 248 throw new InvalidParameterException( 249 String.format( 250 "Unsupported pattern '%s' found for run target '%s'", 251 pattern, runTargetFormat)); 252 } 253 if (txt == null || DeviceManager.UNKNOWN_DISPLAY_STRING.equals(txt)) { 254 return DeviceManager.UNKNOWN_DISPLAY_STRING; 255 } 256 m.appendReplacement(sb, Matcher.quoteReplacement(txt)); 257 } 258 m.appendTail(sb); 259 return sb.toString(); 260 } 261 // Default behavior. 262 // TODO: Remove this when we cluster default run target is changed. 263 String runTarget = device.getProduct(); 264 if (!runTarget.equals(device.getProductVariant())) { 265 runTarget += ":" + device.getProductVariant(); 266 } 267 return runTarget; 268 } 269 270 /** 271 * Checks if a given input is a localhost IP:PORT string. 272 * 273 * @param input a string to check 274 * @return true if the given input is a localhost IP:PORT string 275 */ isLocalhostIpPort(String input)276 public static boolean isLocalhostIpPort(String input) { 277 try { 278 HostAndPort hostAndPort = HostAndPort.fromString(input); 279 return (LOCALHOST_IP.equals(hostAndPort.getHost()) 280 || LOCALHOST_NAME.equals(hostAndPort.getHost())); 281 } catch (IllegalArgumentException e) { 282 return false; 283 } 284 } 285 286 /** 287 * Returns the current system time. 288 * 289 * @return time in millis. 290 */ getCurrentTimeMillis()291 public static long getCurrentTimeMillis() { 292 return System.currentTimeMillis(); 293 } 294 getTfStartTimeMillis()295 public static long getTfStartTimeMillis() { 296 return sTfStartTime; 297 } 298 299 /** Get the {@link IClusterOptions} instance used to store cluster-related settings. */ getClusterOptions()300 public static IClusterOptions getClusterOptions() { 301 IClusterOptions clusterOptions = 302 (IClusterOptions) 303 GlobalConfiguration.getInstance() 304 .getConfigurationObject(ClusterOptions.TYPE_NAME); 305 if (clusterOptions == null) { 306 throw new IllegalStateException( 307 "cluster_options not defined. You must add this " 308 + "object to your global config. See google/atp/cluster.xml."); 309 } 310 311 return clusterOptions; 312 } 313 314 /** Get the {@link IClusterClient} instance used to interact with the TFC backend. */ getClusterClient()315 public static IClusterClient getClusterClient() { 316 IClusterClient ClusterClient = 317 (IClusterClient) 318 GlobalConfiguration.getInstance() 319 .getConfigurationObject(IClusterClient.TYPE_NAME); 320 if (ClusterClient == null) { 321 throw new IllegalStateException( 322 "cluster_client not defined. You must add this " 323 + "object to your global config. See google/atp/cluster.xml."); 324 } 325 326 return ClusterClient; 327 } 328 getTestHarness()329 public static String getTestHarness() { 330 return TRADEFED; 331 } 332 } 333