blob: 5c87b20dc31db2be8798580ecff42717250705b3 [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.remote;
import build.bazel.remote.execution.v2.CacheCapabilities;
import build.bazel.remote.execution.v2.CapabilitiesGrpc;
import build.bazel.remote.execution.v2.CapabilitiesGrpc.CapabilitiesBlockingStub;
import build.bazel.remote.execution.v2.DigestFunction;
import build.bazel.remote.execution.v2.ExecutionCapabilities;
import build.bazel.remote.execution.v2.GetCapabilitiesRequest;
import build.bazel.remote.execution.v2.PriorityCapabilities;
import build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange;
import build.bazel.remote.execution.v2.ServerCapabilities;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.remote.options.RemoteOptions;
import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
import io.grpc.CallCredentials;
import io.grpc.Context;
import io.grpc.StatusRuntimeException;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/** Fetches the ServerCapabilities of the remote execution/cache server. */
class RemoteServerCapabilities {
@Nullable private final String instanceName;
private final ReferenceCountedChannel channel;
@Nullable private final CallCredentials callCredentials;
private final long callTimeoutSecs;
private final RemoteRetrier retrier;
public RemoteServerCapabilities(
@Nullable String instanceName,
ReferenceCountedChannel channel,
@Nullable CallCredentials callCredentials,
long callTimeoutSecs,
RemoteRetrier retrier) {
this.instanceName = instanceName;
this.channel = channel;
this.callCredentials = callCredentials;
this.callTimeoutSecs = callTimeoutSecs;
this.retrier = retrier;
}
private CapabilitiesBlockingStub capabilitiesBlockingStub() {
return CapabilitiesGrpc.newBlockingStub(channel)
.withInterceptors(TracingMetadataUtils.attachMetadataFromContextInterceptor())
.withCallCredentials(callCredentials)
.withDeadlineAfter(callTimeoutSecs, TimeUnit.SECONDS);
}
public ServerCapabilities get(String buildRequestId, String commandId)
throws IOException, InterruptedException {
Context withMetadata =
TracingMetadataUtils.contextWithMetadata(buildRequestId, commandId, "capabilities");
Context previous = withMetadata.attach();
try {
GetCapabilitiesRequest request =
instanceName == null
? GetCapabilitiesRequest.getDefaultInstance()
: GetCapabilitiesRequest.newBuilder().setInstanceName(instanceName).build();
return retrier.execute(() -> capabilitiesBlockingStub().getCapabilities(request));
} catch (StatusRuntimeException e) {
throw new IOException(e);
} finally {
withMetadata.detach(previous);
}
}
static class ClientServerCompatibilityStatus {
private final List<String> warnings;
private final List<String> errors;
private ClientServerCompatibilityStatus(List<String> warnings, List<String> errors) {
this.warnings = warnings;
this.errors = errors;
}
static class Builder {
private final ImmutableList.Builder<String> warnings = ImmutableList.builder();
private final ImmutableList.Builder<String> errors = ImmutableList.builder();
public void addWarning(String message) {
warnings.add(message);
}
public void addError(String message) {
errors.add(message);
}
public ClientServerCompatibilityStatus build() {
return new ClientServerCompatibilityStatus(warnings.build(), errors.build());
}
}
public boolean isOk() {
return warnings.isEmpty() && errors.isEmpty();
}
public List<String> getWarnings() {
return warnings;
}
public List<String> getErrors() {
return errors;
}
}
private static void checkPriorityInRange(
int priority,
String optionName,
PriorityCapabilities prCap,
ClientServerCompatibilityStatus.Builder result) {
if (priority != 0) {
boolean found = false;
StringBuilder rangeBuilder = new StringBuilder();
for (PriorityRange pr : prCap.getPrioritiesList()) {
rangeBuilder.append(String.format("%d-%d,", pr.getMinPriority(), pr.getMaxPriority()));
if (pr.getMinPriority() <= priority && priority <= pr.getMaxPriority()) {
found = true;
break;
}
}
if (!found) {
String range = rangeBuilder.toString();
if (!range.isEmpty()) {
range = range.substring(0, range.length() - 1);
}
result.addError(
String.format(
"--%s %d is outside of server supported range %s.", optionName, priority, range));
}
}
}
/** Compare the remote server capabilities with those requested by current execution. */
public static ClientServerCompatibilityStatus checkClientServerCompatibility(
ServerCapabilities capabilities,
RemoteOptions remoteOptions,
DigestFunction.Value digestFunction) {
ClientServerCompatibilityStatus.Builder result = new ClientServerCompatibilityStatus.Builder();
boolean remoteExecution = !Strings.isNullOrEmpty(remoteOptions.remoteExecutor);
if (!remoteExecution && Strings.isNullOrEmpty(remoteOptions.remoteCache)) {
return result.build();
}
// Check API version.
ApiVersion.ServerSupportedStatus st =
ApiVersion.current.checkServerSupportedVersions(capabilities);
if (st.isUnsupported()) {
result.addError(st.getMessage());
}
if (st.isDeprecated()) {
result.addWarning(st.getMessage());
}
// Check cache digest function.
CacheCapabilities cacheCap = capabilities.getCacheCapabilities();
if (!cacheCap.getDigestFunctionList().contains(digestFunction)) {
result.addError(
String.format(
"Cannot use hash function %s with remote cache. "
+ "Server supported functions are: %s",
digestFunction, cacheCap.getDigestFunctionList()));
}
if (remoteExecution) {
// Check remote execution is enabled.
ExecutionCapabilities execCap = capabilities.getExecutionCapabilities();
if (!execCap.getExecEnabled()) {
result.addError(
"Remote execution is not supported by the remote server, or the current "
+ "account is not authorized to use remote execution.");
return result.build(); // No point checking other execution fields.
}
// Check execution digest function.
if (execCap.getDigestFunction() == DigestFunction.Value.UNKNOWN) {
// Server side error -- this is not supposed to happen.
result.addError("Remote server error: UNKNOWN execution digest function.");
}
if (execCap.getDigestFunction() != digestFunction) {
result.addError(
String.format(
"Cannot use hash function %s with remote execution. "
+ "Server supported function is %s",
digestFunction, execCap.getDigestFunction()));
}
// Check updating remote cache is allowed, if we ever need to do that.
if (remoteOptions.remoteLocalFallback
&& remoteOptions.remoteUploadLocalResults
&& !cacheCap.getActionCacheUpdateCapabilities().getUpdateEnabled()) {
result.addError(
"--remote_local_fallback and --remote_upload_local_results are set, "
+ "but the current account is not authorized to write local results "
+ "to the remote cache.");
}
// Check execution priority is in the supported range.
checkPriorityInRange(
remoteOptions.remoteExecutionPriority,
"remote_execution_priority",
execCap.getExecutionPriorityCapabilities(),
result);
} else {
// Local execution: check updating remote cache is allowed.
if (remoteOptions.remoteUploadLocalResults
&& !cacheCap.getActionCacheUpdateCapabilities().getUpdateEnabled()) {
result.addError(
"--remote_upload_local_results is set, but the current account is not authorized "
+ "to write local results to the remote cache.");
}
}
// Check result cache priority is in the supported range.
checkPriorityInRange(
remoteOptions.remoteResultCachePriority,
"remote_result_cache_priority",
cacheCap.getCachePriorityCapabilities(),
result);
return result.build();
}
}