1 /*
2  * Copyright (C) 2018 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.invoker.sandbox;
17 
18 import com.android.annotations.VisibleForTesting;
19 import com.android.tradefed.build.BuildRetrievalError;
20 import com.android.tradefed.build.IBuildInfo;
21 import com.android.tradefed.config.Configuration;
22 import com.android.tradefed.config.ConfigurationException;
23 import com.android.tradefed.config.ConfigurationFactory;
24 import com.android.tradefed.config.IConfiguration;
25 import com.android.tradefed.config.IConfigurationFactory;
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.device.ITestDevice;
28 import com.android.tradefed.device.TestDeviceOptions;
29 import com.android.tradefed.device.cloud.GceManager;
30 import com.android.tradefed.invoker.IInvocationContext;
31 import com.android.tradefed.invoker.IRescheduler;
32 import com.android.tradefed.invoker.InvocationExecution;
33 import com.android.tradefed.invoker.TestInformation;
34 import com.android.tradefed.invoker.TestInvocation.Stage;
35 import com.android.tradefed.log.ITestLogger;
36 import com.android.tradefed.log.LogUtil.CLog;
37 import com.android.tradefed.result.ITestInvocationListener;
38 import com.android.tradefed.result.error.InfraErrorIdentifier;
39 import com.android.tradefed.sandbox.SandboxInvocationRunner;
40 import com.android.tradefed.sandbox.SandboxOptions;
41 import com.android.tradefed.targetprep.BuildError;
42 import com.android.tradefed.targetprep.TargetSetupError;
43 import com.android.tradefed.util.IRunUtil;
44 import com.android.tradefed.util.RunUtil;
45 
46 /**
47  * Version of {@link InvocationExecution} for the parent invocation special actions when running a
48  * sandbox.
49  */
50 public class ParentSandboxInvocationExecution extends InvocationExecution {
51 
52     private IConfiguration mParentPreparerConfig = null;
53 
54     @Override
fetchBuild( TestInformation testInfo, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener listener)55     public boolean fetchBuild(
56             TestInformation testInfo,
57             IConfiguration config,
58             IRescheduler rescheduler,
59             ITestInvocationListener listener)
60             throws DeviceNotAvailableException, BuildRetrievalError {
61         if (!testInfo.getContext().getBuildInfos().isEmpty()) {
62             CLog.d(
63                     "Context already contains builds: %s. Skipping download as we are in "
64                             + "sandbox-test-mode.",
65                     testInfo.getContext().getBuildInfos());
66             return true;
67         }
68         return super.fetchBuild(testInfo, config, rescheduler, listener);
69     }
70 
71     @Override
doSetup(TestInformation testInfo, IConfiguration config, ITestLogger logger)72     public void doSetup(TestInformation testInfo, IConfiguration config, ITestLogger logger)
73             throws TargetSetupError, BuildError, DeviceNotAvailableException {
74         // Skip
75         mParentPreparerConfig = getParentTargetConfig(config);
76         if (mParentPreparerConfig == null) {
77             return;
78         }
79         CLog.d("Using %s to run in the parent setup.", SandboxOptions.PARENT_PREPARER_CONFIG);
80         super.doSetup(testInfo, mParentPreparerConfig, logger);
81     }
82 
83     @Override
doTeardown( TestInformation testInfo, IConfiguration config, ITestLogger logger, Throwable exception)84     public void doTeardown(
85             TestInformation testInfo,
86             IConfiguration config,
87             ITestLogger logger,
88             Throwable exception)
89             throws Throwable {
90         // Skip
91         // If we are the parent invocation of the sandbox, setUp has been skipped since it's
92         // done in the sandbox, so tearDown should be skipped.
93         mParentPreparerConfig = getParentTargetConfig(config);
94         if (mParentPreparerConfig == null) {
95             return;
96         }
97         CLog.d("Using %s to run in the parent tear down.", SandboxOptions.PARENT_PREPARER_CONFIG);
98         super.doTeardown(testInfo, mParentPreparerConfig, logger, exception);
99     }
100 
101     @Override
doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception)102     public void doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception) {
103         // Skip
104         if (mParentPreparerConfig == null) {
105             return;
106         }
107         CLog.d("Using %s to run in the parent clean up.", SandboxOptions.PARENT_PREPARER_CONFIG);
108         super.doCleanUp(context, mParentPreparerConfig, exception);
109     }
110 
111     @Override
runTests( TestInformation info, IConfiguration config, ITestInvocationListener listener)112     public void runTests(
113             TestInformation info, IConfiguration config, ITestInvocationListener listener)
114             throws Throwable {
115         // If the invocation is sandboxed run as a sandbox instead.
116         boolean success = false;
117         try {
118             success = prepareAndRunSandbox(info, config, listener);
119         } finally {
120             if (!success) {
121                 String instanceName = null;
122                 boolean cleaned = false;
123                 for (IBuildInfo build : info.getContext().getBuildInfos()) {
124                     if (build.getBuildAttributes().get(GceManager.GCE_INSTANCE_NAME_KEY) != null) {
125                         instanceName =
126                                 build.getBuildAttributes().get(GceManager.GCE_INSTANCE_NAME_KEY);
127                     }
128                     if (build.getBuildAttributes().get(GceManager.GCE_INSTANCE_CLEANED_KEY)
129                             != null) {
130                         cleaned = true;
131                     }
132                 }
133                 if (instanceName != null && !cleaned) {
134                     // TODO: Handle other devices if needed.
135                     TestDeviceOptions options = config.getDeviceConfig().get(0).getDeviceOptions();
136                     CLog.w("Instance was not cleaned in sandbox subprocess, cleaning it now.");
137                     boolean res = GceManager.AcloudShutdown(options, getRunUtil(), instanceName);
138                     if (res) {
139                         info.getBuildInfo()
140                                 .addBuildAttribute(GceManager.GCE_INSTANCE_CLEANED_KEY, "true");
141                     }
142                 }
143             }
144         }
145     }
146 
147     @Override
reportLogs(ITestDevice device, ITestLogger logger, Stage stage)148     public void reportLogs(ITestDevice device, ITestLogger logger, Stage stage) {
149         // If it's not a major error we do not report it if no setup or teardown ran.
150         if (mParentPreparerConfig == null || !Stage.ERROR.equals(stage)) {
151             return;
152         }
153         super.reportLogs(device, logger, stage);
154     }
155 
156     /** Returns the {@link IConfigurationFactory} used to created configurations. */
157     @VisibleForTesting
getFactory()158     protected IConfigurationFactory getFactory() {
159         return ConfigurationFactory.getInstance();
160     }
161 
162     @VisibleForTesting
getRunUtil()163     protected IRunUtil getRunUtil() {
164         return RunUtil.getDefault();
165     }
166 
167     /** Returns the result status of running the sandbox. */
168     @VisibleForTesting
prepareAndRunSandbox( TestInformation info, IConfiguration config, ITestInvocationListener listener)169     protected boolean prepareAndRunSandbox(
170             TestInformation info, IConfiguration config, ITestInvocationListener listener)
171             throws Throwable {
172         return SandboxInvocationRunner.prepareAndRun(info, config, listener);
173     }
174 
getParentTargetConfig(IConfiguration config)175     private IConfiguration getParentTargetConfig(IConfiguration config) throws TargetSetupError {
176         if (mParentPreparerConfig != null) {
177             return mParentPreparerConfig;
178         }
179         SandboxOptions options =
180                 (SandboxOptions)
181                         config.getConfigurationObject(Configuration.SANBOX_OPTIONS_TYPE_NAME);
182         if (options != null && options.getParentPreparerConfig() != null) {
183             try {
184                 return getFactory()
185                         .createConfigurationFromArgs(
186                                 new String[] {options.getParentPreparerConfig()});
187             } catch (ConfigurationException e) {
188                 String message =
189                         String.format(
190                                 "Check your --%s option: %s",
191                                 SandboxOptions.PARENT_PREPARER_CONFIG, e.getMessage());
192                 CLog.e(message);
193                 CLog.e(e);
194                 throw new TargetSetupError(message, e, InfraErrorIdentifier.UNDETERMINED);
195             }
196         }
197         return null;
198     }
199 }
200