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