blob: 332a4518ce750e291cdaded3621117c06c478943 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* 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
*
* http://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.android.tradefed.service;
import com.android.annotations.VisibleForTesting;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.ITestInformationReceiver;
import com.android.tradefed.util.StreamUtil;
import com.proto.tradefed.feature.ErrorInfo;
import com.proto.tradefed.feature.FeatureRequest;
import com.proto.tradefed.feature.FeatureResponse;
import com.proto.tradefed.feature.TradefedInformationGrpc.TradefedInformationImplBase;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.UUID;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
/** A server that responds to requests for triggering features. */
public class TradefedFeatureServer extends TradefedInformationImplBase {
public static final String SERVER_REFERENCE = "SERVER_REFERENCE";
public static final String TEST_INFORMATION_OBJECT = "TEST_INFORMATION";
private static final int DEFAULT_PORT = 8889;
private static final String TF_SERVICE_PORT = "TF_SERVICE_PORT";
private Server mServer;
private Map<String, IConfiguration> mRegisteredInvocation = new HashMap<>();
/** Returns the port used by the server. */
public static int getPort() {
return System.getenv(TF_SERVICE_PORT) != null
? Integer.parseInt(System.getenv(TF_SERVICE_PORT))
: DEFAULT_PORT;
}
public TradefedFeatureServer() {
this(ServerBuilder.forPort(getPort()));
}
@VisibleForTesting
TradefedFeatureServer(ServerBuilder<?> serverBuilder) {
mServer = serverBuilder.addService(this).build();
}
/** Start the grpc server to listen to requests. */
public void start() {
try {
CLog.d("Starting feature server.");
mServer.start();
} catch (IOException e) {
CLog.w("TradefedFeatureServer already started: %s", e.getMessage());
}
}
/** Stop the grpc server. */
public void shutdown() throws InterruptedException {
if (mServer != null) {
CLog.d("Stopping feature server.");
mServer.shutdown();
mServer.awaitTermination();
}
}
@Override
public void triggerFeature(
FeatureRequest request, StreamObserver<FeatureResponse> responseObserver) {
FeatureResponse response;
try {
response = createResponse(request);
} catch (RuntimeException exception) {
response = FeatureResponse.newBuilder()
.setErrorInfo(
ErrorInfo.newBuilder()
.setErrorTrace(StreamUtil.getStackTrace(exception)))
.build();
}
responseObserver.onNext(response);
responseObserver.onCompleted();
}
/** Register an invocation with a unique reference that can be queried */
public String registerInvocation(IConfiguration config) {
String referenceId = UUID.randomUUID().toString();
mRegisteredInvocation.put(referenceId, config);
config.getConfigurationDescription().addMetadata(SERVER_REFERENCE, referenceId);
return referenceId;
}
/** Unregister an invocation by its configuration. */
public void unregisterInvocation(IConfiguration reference) {
mRegisteredInvocation.remove(
reference
.getConfigurationDescription()
.getAllMetaData()
.getUniqueMap()
.get(SERVER_REFERENCE));
}
private FeatureResponse createResponse(FeatureRequest request) {
ServiceLoader<IRemoteFeature> serviceLoader = ServiceLoader.load(IRemoteFeature.class);
for (IRemoteFeature feature : serviceLoader) {
if (feature.getName().equals(request.getName())) {
if (feature instanceof IConfigurationReceiver) {
((IConfigurationReceiver) feature)
.setConfiguration(mRegisteredInvocation.get(request.getReferenceId()));
}
if (feature instanceof ITestInformationReceiver) {
if (mRegisteredInvocation.get(request.getReferenceId()) != null) {
((ITestInformationReceiver) feature)
.setTestInformation(
(TestInformation) mRegisteredInvocation
.get(request.getReferenceId())
.getConfigurationObject(TEST_INFORMATION_OBJECT));
}
}
try {
FeatureResponse rep = feature.execute(request);
if (rep == null) {
return FeatureResponse.newBuilder()
.setErrorInfo(
ErrorInfo.newBuilder()
.setErrorTrace(
String.format(
"Feature '%s' returned null response.",
request.getName())))
.build();
}
return rep;
} finally {
if (feature instanceof IConfigurationReceiver) {
((IConfigurationReceiver) feature).setConfiguration(null);
}
}
}
}
return FeatureResponse.newBuilder()
.setErrorInfo(
ErrorInfo.newBuilder()
.setErrorTrace(
String.format(
"No feature matching the requested one '%s'",
request.getName())))
.build();
}
}