Skip to content

Commit

Permalink
Internal change
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 710892889
  • Loading branch information
DeviceInfra authored and copybara-github committed Dec 31, 2024
1 parent 84e381b commit b597222
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.devtools.mobileharness.api.devicemanager.detector;

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.AndroidEmulatorType;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.DetectionResult;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.DetectionResult.DetectionType;
import com.google.devtools.mobileharness.shared.util.flags.Flags;
import javax.annotation.Nullable;

/** Detector for Android JIT emulators. */
public final class AndroidJitEmulatorDetector implements Detector {
/** Initialized in {@link #precondition()}. */
@Nullable private volatile ImmutableList<String> emulatorIds;

// Acloud creates cuttlefish emulator with port 6520 + index, where index is instance id minus
// one. Example: "acloud create --local-instance 1" will create a local emulator with port 6520.
// And instance 2 will be 6521, so on and so forth.
private static final int EMULATOR_BASE_PORT = 6520;

public AndroidJitEmulatorDetector() {}

/**
* Precondition of the detector.
*
* @return whether the precondition meets
*/
@Override
public boolean precondition() {
int emulatorNumber = Flags.instance().androidJitEmulatorNum.getNonNull();
ImmutableList.Builder<String> emulatorIdsBuilder = ImmutableList.builder();
for (int i = 0; i < emulatorNumber; i++) {
emulatorIdsBuilder.add("0.0.0.0:" + (i + EMULATOR_BASE_PORT));
}
emulatorIds = emulatorIdsBuilder.build();
return emulatorNumber > 0;
}

/**
* Detects the Android JIT emulators.
*
* @return the detection result of the Android JIT emulators
*/
@Override
public ImmutableList<DetectionResult> detectDevices() {
return emulatorIds.stream()
.map(id -> DetectionResult.of(id, DetectionType.EMULATOR, AndroidEmulatorType.JIT_EMULATOR))
.collect(toImmutableList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ package(
default_visibility = ["//:deviceinfra_devicemanagement_pkg"],
)

java_library(
name = "android_jit_emulator_detector",
srcs = ["AndroidJitEmulatorDetector.java"],
deps = [
":base",
"//src/java/com/google/devtools/mobileharness/api/devicemanager/detector/model",
"//src/java/com/google/devtools/mobileharness/api/devicemanager/detector/model:android_emulator_type",
"//src/java/com/google/devtools/mobileharness/shared/util/flags",
"@maven//:com_google_code_findbugs_jsr305",
"@maven//:com_google_guava_guava",
],
)

java_library(
name = "base",
srcs = ["Detector.java"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@
package com.google.devtools.mobileharness.api.devicemanager.detector.model;

/** Android emulator device types */
public enum AndroidEmulatorType {}
public enum AndroidEmulatorType {
JIT_EMULATOR,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.devtools.mobileharness.api.devicemanager.dispatcher;

import static com.google.common.collect.ImmutableMap.toImmutableMap;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.AndroidEmulatorType;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.DetectionResult;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.DetectionResult.DetectionType;
import com.google.devtools.mobileharness.api.devicemanager.detector.model.DetectionResults;
import com.google.devtools.mobileharness.api.devicemanager.dispatcher.model.DispatchResult.DispatchType;
import com.google.devtools.mobileharness.api.devicemanager.dispatcher.model.DispatchResults;
import com.google.devtools.mobileharness.api.devicemanager.dispatcher.util.DeviceIdGenerator;
import com.google.devtools.mobileharness.api.model.lab.DeviceId;

/** Dispatcher for Android JIT Emulator devices. */
public final class AndroidJitEmulatorDispatcher implements Dispatcher {
private final DeviceIdGenerator deviceIdGenerator;

public AndroidJitEmulatorDispatcher() {
this(new DeviceIdGenerator());
}

@VisibleForTesting
AndroidJitEmulatorDispatcher(DeviceIdGenerator deviceIdGenerator) {
this.deviceIdGenerator = deviceIdGenerator;
}

@Override
public ImmutableMap<String, DispatchType> dispatchDevices(
DetectionResults detectionResults, DispatchResults dispatchResults) {
return detectionResults
.getByTypeAndDetail(DetectionType.EMULATOR, AndroidEmulatorType.JIT_EMULATOR)
.stream()
.collect(toImmutableMap(DetectionResult::deviceControlId, s -> DispatchType.LIVE));
}

@Override
public DeviceId generateDeviceId(String deviceControlId) {
return deviceIdGenerator.getAndroidDeviceId(deviceControlId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ java_library(
],
)

java_library(
name = "android_jit_emulator_dispatcher",
srcs = ["AndroidJitEmulatorDispatcher.java"],
deps = [
":base",
"//src/java/com/google/devtools/mobileharness/api/devicemanager/detector/model",
"//src/java/com/google/devtools/mobileharness/api/devicemanager/detector/model:android_emulator_type",
"//src/java/com/google/devtools/mobileharness/api/devicemanager/dispatcher/model",
"//src/java/com/google/devtools/mobileharness/api/devicemanager/dispatcher/util:device_id_descriptor_generator",
"//src/java/com/google/devtools/mobileharness/api/model/lab:device_id_descriptor",
"@maven//:com_google_guava_guava",
],
)

java_library(
name = "android_real_device",
srcs = ["AndroidRealDeviceDispatcher.java"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ public class Flags {
converter = Flag.StringConverter.class)
public Flag<String> aaptPath = aaptPathDefault;

private static final Flag<String> acloudPathDefault = Flag.value("/bin/acloud_prebuilt");

@com.beust.jcommander.Parameter(
names = "--acloud_path",
description = "Path to the acloud binary.",
converter = Flag.StringConverter.class)
public Flag<String> acloudPath = acloudPathDefault;

private static final Flag<Integer> adbCommandRetryAttemptsDefault = Flag.value(2);

@com.beust.jcommander.Parameter(
Expand Down Expand Up @@ -216,6 +224,16 @@ public class Flags {
converter = DurationFlag.DurationConverter.class)
public Flag<Duration> androidFactoryResetWaitTime = androidFactoryResetWaitTimeDefault;

private static final Flag<Integer> androidJitEmulatorNumDefault = Flag.value(0);

@com.beust.jcommander.Parameter(
names = "--android_jit_emulator_num",
description =
"The naximum number of android Just-in-time emulators that could be run on the server"
+ " simultaneously.",
converter = Flag.IntegerConverter.class)
public Flag<Integer> androidJitEmulatorNum = androidJitEmulatorNumDefault;

private static final Flag<String> apiConfigFileDefault = Flag.value("");

@com.beust.jcommander.Parameter(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.wireless.qa.mobileharness.shared.api.device;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.flogger.FluentLogger;
import com.google.devtools.mobileharness.api.model.error.MobileHarnessException;
import com.google.devtools.mobileharness.api.model.proto.Device.PostTestDeviceOp;
import com.google.devtools.mobileharness.infra.controller.device.config.ApiConfig;
import com.google.devtools.mobileharness.platform.android.sdktool.adb.AndroidAdbUtil;
import com.google.devtools.mobileharness.platform.android.sdktool.adb.AndroidProperty;
import com.google.devtools.mobileharness.shared.util.command.Command;
import com.google.devtools.mobileharness.shared.util.command.CommandExecutor;
import com.google.devtools.mobileharness.shared.util.command.Timeout;
import com.google.devtools.mobileharness.shared.util.file.local.LocalFileUtil;
import com.google.devtools.mobileharness.shared.util.flags.Flags;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.wireless.qa.mobileharness.shared.api.validator.ValidatorFactory;
import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo;
import java.nio.file.Path;
import java.time.Duration;
import javax.annotation.Nullable;

/** Android emulator device class. This emulator is created by acloud on demand of test requests. */
public class AndroidJitEmulator extends AndroidDevice {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();

public static final String JIT_EMULATOR_IMAGE_ZIP = "JIT_EMULATOR_IMAGE_ZIP";
public static final String JIT_EMULATOR_HOST_PACKAGE = "JIT_EMULATOR_HOST_PACKAGE";
public static final String JIT_EMULATOR_ACLOUD = "JIT_EMULATOR_ACLOUD";

// TODO: Move to a shared class.
public static final int EMULATOR_BASE_PORT = 6520;
public static final int EMULATOR_INSTANCE_ID_BASE = 1;

private final String deviceId;
private final AndroidAdbUtil androidAdbUtil;
private final LocalFileUtil localFileUtil;
private final CommandExecutor commandExecutor;

public AndroidJitEmulator(String deviceId) {
this(
deviceId,
ApiConfig.getInstance(),
new ValidatorFactory(),
new CommandExecutor(),
new AndroidAdbUtil(),
new LocalFileUtil());
}

@VisibleForTesting
AndroidJitEmulator(
String deviceId,
@Nullable ApiConfig apiConfig,
@Nullable ValidatorFactory validatorFactory,
CommandExecutor commandExecutor,
AndroidAdbUtil androidAdbUtil,
LocalFileUtil localFileUtil) {
super(deviceId, apiConfig, validatorFactory);
this.deviceId = deviceId;
this.commandExecutor = commandExecutor;
this.androidAdbUtil = androidAdbUtil;
this.localFileUtil = localFileUtil;
}

@Override
public void setUp() throws MobileHarnessException, InterruptedException {
logger.atInfo().log("JIT emulator start setup. %s", deviceId);
addDimension(Ascii.toLowerCase(AndroidProperty.ABILIST.name()), "x86_64");
addDimension(Ascii.toLowerCase(AndroidProperty.ABI.name()), "x86_64");
addSupportedDriver("NoOpDriver");
addSupportedDriver("AndroidInstrumentation");
addSupportedDriver("MoblyTest");
addSupportedDriver("XtsTradefedTest");

addSupportedDecorator("AndroidHdVideoDecorator");
addSupportedDecorator("AndroidDeviceSettingsDecorator");
addSupportedDecorator("AndroidAdbShellDecorator");

addSupportedDeviceType(AndroidJitEmulator.class.getSimpleName());
addSupportedDeviceType(AndroidDevice.class.getSimpleName());
basicAndroidDecoratorConfiguration();

logger.atInfo().log("JIT emulator %s is Ready", deviceId);
}

@Override
public void preRunTest(TestInfo testInfo) throws MobileHarnessException, InterruptedException {
String imgZip = testInfo.jobInfo().files().getSingle("device");
String hostPkg = testInfo.jobInfo().files().getSingle("cvd-host_package.tar.gz");
Path imageDir = Path.of(testInfo.getTmpFileDir()).resolve("image_dir");
localFileUtil.prepareDir(imageDir);

Path hostPkgDir = Path.of(testInfo.getTmpFileDir()).resolve("cvd-host_package");
String acloudPath = Flags.instance().acloudPath.getNonNull();
localFileUtil.prepareDir(hostPkgDir);
localFileUtil.unzipFile(imgZip, imageDir.toString());

Command unzipHostPkgCmd = Command.of("tar", "-xvzf", hostPkg, "-C", hostPkgDir.toString());
String unzipHostPkgOutput = commandExecutor.run(unzipHostPkgCmd);
logger.atFine().log("unzipHostPkgOutput: %s", unzipHostPkgOutput);

localFileUtil.grantFileOrDirFullAccessRecursively(imageDir);
localFileUtil.grantFileOrDirFullAccessRecursively(hostPkgDir);
localFileUtil.grantFileOrDirFullAccess(acloudPath);

Command command =
Command.of(
acloudPath,
"create",
"--local-instance",
getInstanceId(),
"--local-image",
imageDir.toString(),
"--local-tool",
hostPkgDir.toString(),
"--yes",
"--no-autoconnect",
"--skip-pre-run-check")
.timeout(Timeout.fixed(Duration.ofMinutes(2)));
String output = commandExecutor.run(command);
logger.atInfo().log("acloud create output: %s", output);
String deviceIpAddress = getDeviceId();
// Attempt to establish an ADB connection to a device's IP address, with retry mechanism
androidAdbUtil.connect(deviceIpAddress);
}

@CanIgnoreReturnValue
@Override
public PostTestDeviceOp postRunTest(TestInfo testInfo)
throws MobileHarnessException, InterruptedException {
String acloudPath = Flags.instance().acloudPath.getNonNull();
localFileUtil.grantFileOrDirFullAccess(acloudPath);
Command command =
Command.of(acloudPath, "delete", "--instance-names", "local-instance-" + getInstanceId());
String output = commandExecutor.run(command);
logger.atInfo().log("acloud delete output: %s", output);
return PostTestDeviceOp.NONE;
}

@Override
public boolean canReboot() {
return false;
}

@Override
public void reboot() throws MobileHarnessException, InterruptedException {
logger.atSevere().log("Unexpected attempt to reboot a non-rebootable device");
}

@Override
public boolean isRooted() {
// We suppose all emulators are rooted.
return true;
}

// TODO: Move instance id logic to a shared class to share with
// AndroidJitEmulatorDetector.
private String getInstanceId() {
return String.valueOf(
Integer.parseInt(getDeviceId().substring(getDeviceId().indexOf(':') + 1))
- EMULATOR_BASE_PORT
+ EMULATOR_INSTANCE_ID_BASE);
}
}
Loading

0 comments on commit b597222

Please sign in to comment.