Skip to content

Commit

Permalink
remote: Improve error handling for --remote_* cmd line flags. Fixes b…
Browse files Browse the repository at this point in the history
…azelbuild#3361, bazelbuild#3358

- Move flag handling into RemoteModule to fail as early as possible.
- Make error messages from flag handling human readable.
- Fix a bug where remote execution would only support TLS with a root
certificate being specified.
- If a remote executor without a remote cache is specified, assume the
remote cache to be the same as the executor.

PiperOrigin-RevId: 161946029
  • Loading branch information
buchgr authored and laszlocsomor committed Jul 14, 2017
1 parent 21fc8eb commit bcbd2da
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.common.options.Options;
import io.grpc.CallCredentials;
import io.grpc.auth.MoreCallCredentials;
import io.grpc.netty.GrpcSslContexts;
import io.netty.handler.ssl.SslContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;

/** Instantiate all authentication helpers from build options. */
@ThreadSafe
Expand All @@ -38,7 +37,6 @@ public final class ChannelOptions {
private final SslContext sslContext;
private final String tlsAuthorityOverride;
private final CallCredentials credentials;
public static final ChannelOptions DEFAULT = create(Options.getDefaults(AuthAndTLSOptions.class));

private ChannelOptions(
boolean tlsEnabled,
Expand Down Expand Up @@ -67,52 +65,70 @@ public SslContext getSslContext() {
return sslContext;
}

public static ChannelOptions create(AuthAndTLSOptions options) {
try {
return create(
options,
options.authCredentials != null
? new FileInputStream(options.authCredentials)
: null);
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed initializing auth credentials for remote cache/execution " + e);
public static ChannelOptions create(AuthAndTLSOptions options) throws IOException {
if (options.authCredentials != null) {
try (InputStream authFile = new FileInputStream(options.authCredentials)) {
return create(options, authFile);
} catch (FileNotFoundException e) {
String message = String.format("Could not open auth credentials file '%s': %s",
options.authCredentials, e.getMessage());
throw new IOException(message, e);
}
} else {
return create(options, null);
}
}

@VisibleForTesting
public static ChannelOptions create(
static ChannelOptions create(
AuthAndTLSOptions options,
@Nullable InputStream credentialsInputStream) {
boolean tlsEnabled = options.tlsEnabled;
SslContext sslContext = null;
String tlsAuthorityOverride = options.tlsAuthorityOverride;
CallCredentials credentials = null;
if (options.tlsEnabled && options.tlsCertificate != null) {
try {
sslContext =
GrpcSslContexts.forClient().trustManager(new File(options.tlsCertificate)).build();
} catch (SSLException e) {
throw new IllegalArgumentException(
"SSL error initializing cert " + options.tlsCertificate + " : " + e);
@Nullable InputStream credentialsFile) throws IOException {
final SslContext sslContext =
options.tlsEnabled ? createSSlContext(options.tlsCertificate) : null;

final CallCredentials callCredentials =
options.authEnabled ? createCallCredentials(credentialsFile, options.authScope) : null;

return new ChannelOptions(
sslContext != null, sslContext, options.tlsAuthorityOverride, callCredentials);
}

private static CallCredentials createCallCredentials(@Nullable InputStream credentialsFile,
@Nullable String authScope) throws IOException {
try {
GoogleCredentials creds =
credentialsFile == null
? GoogleCredentials.getApplicationDefault()
: GoogleCredentials.fromStream(credentialsFile);
if (authScope != null) {
creds = creds.createScoped(ImmutableList.of(authScope));
}
return MoreCallCredentials.from(creds);
} catch (IOException e) {
String message = "Failed to init auth credentials for remote caching/execution: "
+ e.getMessage();
throw new IOException(message, e);
}
if (options.authEnabled) {
}

private static SslContext createSSlContext(@Nullable String rootCert) throws IOException {
if (rootCert == null) {
try {
return GrpcSslContexts.forClient().build();
} catch (Exception e) {
String message = "Failed to init TLS infrastructure for remote caching/execution: "
+ e.getMessage();
throw new IOException(message, e);
}
} else {
try {
GoogleCredentials creds =
credentialsInputStream == null
? GoogleCredentials.getApplicationDefault()
: GoogleCredentials.fromStream(credentialsInputStream);
if (options.authScope != null) {
creds = creds.createScoped(ImmutableList.of(options.authScope));
}
credentials = MoreCallCredentials.from(creds);
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed initializing auth credentials for remote cache/execution " + e);
return GrpcSslContexts.forClient().trustManager(new File(rootCert)).build();
} catch (Exception e) {
String message = "Failed to init TLS infrastructure for remote caching/execution using "
+ "'%s' as root certificate: %s";
message = String.format(message, rootCert, e.getMessage());
throw new IOException(message, e);
}
}
return new ChannelOptions(
tlsEnabled, sslContext, tlsAuthorityOverride, credentials);
}
}
29 changes: 19 additions & 10 deletions src/main/java/com/google/devtools/build/lib/remote/GrpcUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,30 @@
import io.grpc.ManagedChannel;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import java.io.IOException;

/** Helper methods for gRPC calls */
@ThreadSafe
final class GrpcUtils {
public static ManagedChannel createChannel(String target, ChannelOptions channelOptions) {
NettyChannelBuilder builder =
NettyChannelBuilder.forTarget(target)
.negotiationType(
channelOptions.tlsEnabled() ? NegotiationType.TLS : NegotiationType.PLAINTEXT);
if (channelOptions.getSslContext() != null) {
builder.sslContext(channelOptions.getSslContext());
if (channelOptions.getTlsAuthorityOverride() != null) {
builder.overrideAuthority(channelOptions.getTlsAuthorityOverride());
public static ManagedChannel createChannel(String target, ChannelOptions channelOptions)
throws IOException {
try {
NettyChannelBuilder builder =
NettyChannelBuilder.forTarget(target)
.negotiationType(
channelOptions.tlsEnabled() ? NegotiationType.TLS : NegotiationType.PLAINTEXT);
if (channelOptions.getSslContext() != null) {
builder.sslContext(channelOptions.getSslContext());
if (channelOptions.getTlsAuthorityOverride() != null) {
builder.overrideAuthority(channelOptions.getTlsAuthorityOverride());
}
}
return builder.build();
} catch (RuntimeException e) {
// gRPC might throw all kinds of RuntimeExceptions: StatusRuntimeException,
// IllegalStateException, NullPointerException, ...
String message = "Failed to connect to the remote cache/executor '%s': %s";
throw new IOException(String.format(message, target, e.getMessage()));
}
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
// limitations under the License.
package com.google.devtools.build.lib.remote;

import com.google.common.base.Preconditions;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.ActionContext;
import com.google.devtools.build.lib.actions.ActionInputFileCache;
import com.google.devtools.build.lib.actions.ResourceManager;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.exec.ActionContextProvider;
import com.google.devtools.build.lib.exec.ActionInputPrefetcher;
import com.google.devtools.build.lib.exec.ExecutionOptions;
Expand All @@ -29,61 +29,40 @@
import com.google.devtools.build.lib.exec.local.LocalSpawnRunner;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.util.OS;
import javax.annotation.Nullable;

/**
* Provide a remote execution context.
*/
final class RemoteActionContextProvider extends ActionContextProvider {
private final CommandEnvironment env;
private final RemoteActionCache cache;
private final GrpcRemoteExecutor executor;


private RemoteSpawnRunner spawnRunner;
private RemoteSpawnStrategy spawnStrategy;

RemoteActionContextProvider(CommandEnvironment env) {
RemoteActionContextProvider(CommandEnvironment env, @Nullable RemoteActionCache cache,
@Nullable GrpcRemoteExecutor executor) {
this.env = env;
this.executor = executor;
this.cache = cache;
}

@Override
public void init(
ActionInputFileCache actionInputFileCache, ActionInputPrefetcher actionInputPrefetcher) {
ExecutionOptions executionOptions = env.getOptions().getOptions(ExecutionOptions.class);
RemoteOptions remoteOptions = env.getOptions().getOptions(RemoteOptions.class);
AuthAndTLSOptions authAndTlsOptions = env.getOptions().getOptions(AuthAndTLSOptions.class);
ChannelOptions channelOptions = ChannelOptions.create(authAndTlsOptions);

Retrier retrier = new Retrier(remoteOptions);
ExecutionOptions executionOptions =
checkNotNull(env.getOptions().getOptions(ExecutionOptions.class));
RemoteOptions remoteOptions = checkNotNull(env.getOptions().getOptions(RemoteOptions.class));

RemoteActionCache remoteCache;
if (SimpleBlobStoreFactory.isRemoteCacheOptions(remoteOptions)) {
remoteCache = new SimpleBlobStoreActionCache(SimpleBlobStoreFactory.create(remoteOptions));
} else if (GrpcRemoteCache.isRemoteCacheOptions(remoteOptions)) {
remoteCache =
new GrpcRemoteCache(
GrpcUtils.createChannel(remoteOptions.remoteCache, channelOptions),
channelOptions,
remoteOptions,
retrier);
} else {
remoteCache = null;
}

// Otherwise remoteCache remains null and remote caching/execution are disabled.
GrpcRemoteExecutor remoteExecutor;
if (remoteCache != null && remoteOptions.remoteExecutor != null) {
remoteExecutor =
new GrpcRemoteExecutor(
GrpcUtils.createChannel(remoteOptions.remoteExecutor, channelOptions),
channelOptions.getCallCredentials(),
remoteOptions.remoteTimeout,
retrier);
} else {
remoteExecutor = null;
}
spawnRunner = new RemoteSpawnRunner(
env.getExecRoot(),
remoteOptions,
createFallbackRunner(actionInputPrefetcher),
remoteCache,
remoteExecutor);
cache,
executor);
spawnStrategy =
new RemoteSpawnStrategy(
"remote",
Expand All @@ -109,7 +88,7 @@ private SpawnRunner createFallbackRunner(ActionInputPrefetcher actionInputPrefet

@Override
public Iterable<? extends ActionContext> getActionContexts() {
return ImmutableList.of(Preconditions.checkNotNull(spawnStrategy));
return ImmutableList.of(checkNotNull(spawnStrategy));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@

package com.google.devtools.build.lib.remote;

import static com.google.common.base.Preconditions.checkState;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.buildeventstream.PathConverter;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.exec.ExecutorBuilder;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.ServerBuilder;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsProvider;
Expand Down Expand Up @@ -73,6 +77,9 @@ public String apply(Path path) {

private final CasPathConverter converter = new CasPathConverter();

private CommandEnvironment env;
private RemoteActionContextProvider actionContextProvider;

@Override
public void serverInit(OptionsProvider startupOptions, ServerBuilder builder)
throws AbruptExitException {
Expand All @@ -82,16 +89,67 @@ public void serverInit(OptionsProvider startupOptions, ServerBuilder builder)
@Override
public void beforeCommand(CommandEnvironment env) {
env.getEventBus().register(this);
this.env = env;
}

@Override
public void handleOptions(OptionsProvider optionsProvider) {
converter.options = optionsProvider.getOptions(RemoteOptions.class);
checkState(env != null);

RemoteOptions remoteOptions = optionsProvider.getOptions(RemoteOptions.class);
AuthAndTLSOptions authAndTlsOptions = optionsProvider.getOptions(AuthAndTLSOptions.class);
converter.options = remoteOptions;

// Quit if no remote options specified.
if (remoteOptions == null) {
return;
}

try {
ChannelOptions channelOpts = ChannelOptions.create(authAndTlsOptions);

boolean restCache = SimpleBlobStoreFactory.isRemoteCacheOptions(remoteOptions);
boolean grpcCache = GrpcRemoteCache.isRemoteCacheOptions(remoteOptions);

Retrier retrier = new Retrier(remoteOptions);
final RemoteActionCache cache;
if (restCache) {
cache = new SimpleBlobStoreActionCache(SimpleBlobStoreFactory.create(remoteOptions));
} else if (grpcCache) {
cache = new GrpcRemoteCache(GrpcUtils.createChannel(remoteOptions.remoteCache, channelOpts),
channelOpts, remoteOptions, retrier);
} else if (remoteOptions.remoteExecutor != null) {
// If a remote executor but no remote cache is specified, assume both at the same target.
cache =
new GrpcRemoteCache(GrpcUtils.createChannel(remoteOptions.remoteExecutor, channelOpts),
channelOpts, remoteOptions, retrier);
} else {
cache = null;
}

final GrpcRemoteExecutor executor;
if (remoteOptions.remoteExecutor != null) {
executor = new GrpcRemoteExecutor(
GrpcUtils.createChannel(remoteOptions.remoteExecutor, channelOpts),
channelOpts.getCallCredentials(),
remoteOptions.remoteTimeout,
retrier);
} else {
executor = null;
}

actionContextProvider = new RemoteActionContextProvider(env, cache, executor);
} catch (IOException e) {
env.getReporter().handle(Event.error(e.getMessage()));
env.getBlazeModuleEnvironment().exit(new AbruptExitException(ExitCode.COMMAND_LINE_ERROR));
}
}

@Override
public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) {
builder.addActionContextProvider(new RemoteActionContextProvider(env));
if (actionContextProvider != null) {
builder.addActionContextProvider(actionContextProvider);
}
}

@Override
Expand Down
Loading

0 comments on commit bcbd2da

Please sign in to comment.