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.net.InetAddresses;
28 import com.google.common.primitives.Longs;
29 
30 import java.net.InetAddress;
31 import java.net.UnknownHostException;
32 import java.security.InvalidParameterException;
33 import java.util.Map;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 
37 /** Static util functions for TF Cluster to get global config instances, host information, etc. */
38 public class ClusterHostUtil {
39 
40     private static String sHostName = null;
41 
42     static final String DEFAULT_TF_VERSION = "(unknown)";
43 
44     private static long sTfStartTime = getCurrentTimeMillis();
45 
46     /**
47      * Gets the hostname.
48      *
49      * @return the hostname or null if we were unable to fetch it
50      */
getHostName()51     public static String getHostName() {
52         if (sHostName == null) {
53             try {
54                 sHostName = InetAddress.getLocalHost().getHostName();
55             } catch (UnknownHostException e) {
56                 CLog.w("failed to get hostname: %s", e);
57             }
58         }
59         return sHostName;
60     }
61 
62     /**
63      * Gets the TF version running on this host.
64      *
65      * @return this host's TF version.
66      */
getTfVersion()67     public static String getTfVersion() {
68         final String version = VersionParser.fetchVersion();
69         return toValidTfVersion(version);
70     }
71 
72     /**
73      * Validates a TF version and returns it if it is OK.
74      *
75      * @param version The string for a TF version provided by {@link VersionParser}
76      * @return the version if valid or a default if not.
77      */
toValidTfVersion(String version)78     protected static String toValidTfVersion(String version) {
79         if (Strings.isNullOrEmpty(version) || Longs.tryParse(version) == null) {
80             // Making sure the version is valid. It should be a build number
81             return DEFAULT_TF_VERSION;
82         }
83         return version;
84     }
85 
86     /**
87      * Returns the run target for a given device descriptor.
88      *
89      * @param device {@link DeviceDescriptor} to get run target for.
90      * @return run target.
91      */
getRunTarget( DeviceDescriptor device, String runTargetFormat, Map<String, String> deviceTags)92     public static String getRunTarget(
93             DeviceDescriptor device, String runTargetFormat, Map<String, String> deviceTags) {
94         if (runTargetFormat != null) {
95             // Make sure the pattern is non-greedy.
96             Pattern p = Pattern.compile("\\{([^:\\}]+)(:.*)?\\}");
97             Matcher m = p.matcher(runTargetFormat);
98             StringBuffer sb = new StringBuffer();
99             while (m.find()) {
100                 String pattern = m.group(1);
101                 String key = null;
102                 String txt = null;
103                 switch (pattern) {
104                     case "PRODUCT":
105                         txt = device.getProduct();
106                         break;
107                     case "PRODUCT_OR_DEVICE_CLASS":
108                         // TODO: Refactor the logic to handle more flexible combinations.
109                         txt = device.getProduct();
110                         if (device.isStubDevice()) {
111                             String deviceClass = device.getDeviceClass();
112                             // If it's a fastboot device we report it as the product
113                             if (!FastbootDevice.class.getSimpleName().equals(deviceClass)) {
114                                 txt = deviceClass;
115                             }
116                         }
117                         break;
118                     case "PRODUCT_VARIANT":
119                         txt = device.getProductVariant();
120                         break;
121                     case "API_LEVEL":
122                         txt = device.getSdkVersion();
123                         break;
124                     case "DEVICE_CLASS":
125                         txt = device.getDeviceClass();
126                         break;
127                     case "SERIAL":
128                         txt = device.getSerial();
129                         break;
130                     case "TAG":
131                         if (deviceTags == null || deviceTags.isEmpty()) {
132                             // simply delete the placeholder if there's nothing to match
133                             txt = "";
134                         } else {
135                             txt = deviceTags.get(device.getSerial());
136                             if (txt == null) {
137                                 txt = ""; // simply delete it if a tag does not exist
138                             }
139                         }
140                         break;
141                     case "DEVICE_PROP":
142                         key = m.group(2).substring(1);
143                         txt = device.getProperty(key);
144                         break;
145                     default:
146                         throw new InvalidParameterException(
147                                 String.format(
148                                         "Unsupported pattern '%s' found for run target '%s'",
149                                         pattern, runTargetFormat));
150                 }
151                 if (txt == null || DeviceManager.UNKNOWN_DISPLAY_STRING.equals(txt)) {
152                     return DeviceManager.UNKNOWN_DISPLAY_STRING;
153                 }
154                 m.appendReplacement(sb, Matcher.quoteReplacement(txt));
155             }
156             m.appendTail(sb);
157             return sb.toString();
158         }
159         // Default behavior.
160         // TODO: Remove this when we cluster default run target is changed.
161         String runTarget = device.getProduct();
162         if (!runTarget.equals(device.getProductVariant())) {
163             runTarget += ":" + device.getProductVariant();
164         }
165         return runTarget;
166     }
167 
168     /**
169      * Checks if a given input is a valid IP:PORT string.
170      *
171      * @param input a string to check
172      * @return true if the given input is an IP:PORT string
173      */
isIpPort(String input)174     public static boolean isIpPort(String input) {
175         try {
176             HostAndPort hostAndPort = HostAndPort.fromString(input);
177             return InetAddresses.isInetAddress(hostAndPort.getHost());
178         } catch (IllegalArgumentException e) {
179             return false;
180         }
181     }
182 
183     /**
184      * Returns the current system time.
185      *
186      * @return time in millis.
187      */
getCurrentTimeMillis()188     public static long getCurrentTimeMillis() {
189         return System.currentTimeMillis();
190     }
191 
getTfStartTimeMillis()192     public static long getTfStartTimeMillis() {
193         return sTfStartTime;
194     }
195 
196     /** Get the {@link IClusterOptions} instance used to store cluster-related settings. */
getClusterOptions()197     public static IClusterOptions getClusterOptions() {
198         IClusterOptions clusterOptions =
199                 (IClusterOptions)
200                         GlobalConfiguration.getInstance()
201                                 .getConfigurationObject(ClusterOptions.TYPE_NAME);
202         if (clusterOptions == null) {
203             throw new IllegalStateException(
204                     "cluster_options not defined. You must add this "
205                             + "object to your global config. See google/atp/cluster.xml.");
206         }
207 
208         return clusterOptions;
209     }
210 
211     /** Get the {@link IClusterClient} instance used to interact with the TFC backend. */
getClusterClient()212     public static IClusterClient getClusterClient() {
213         IClusterClient ClusterClient =
214                 (IClusterClient)
215                         GlobalConfiguration.getInstance()
216                                 .getConfigurationObject(IClusterClient.TYPE_NAME);
217         if (ClusterClient == null) {
218             throw new IllegalStateException(
219                     "cluster_client not defined. You must add this "
220                             + "object to your global config. See google/atp/cluster.xml.");
221         }
222 
223         return ClusterClient;
224     }
225 }
226