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