Skip to content

Commit

Permalink
feat: support custom SpannerSettings in SessionPoolManager
Browse files Browse the repository at this point in the history
Support setting custom SpannerSettings when creating a SessionPoolManager.
  • Loading branch information
olavloite authored and jskeet committed Jul 21, 2021
1 parent 0ab6b8b commit 6016ef0
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 68 deletions.
Expand Up @@ -50,7 +50,7 @@ static SessionPoolManager()
/// is specified on construction.
/// </summary>
public static SessionPoolManager Default { get; } =
new SessionPoolManager(new SessionPoolOptions(), Logger.DefaultLogger, CreateClientAsync);
new SessionPoolManager(new SessionPoolOptions(), DefaultSpannerSettings(), Logger.DefaultLogger, CreateClientAsync);

private readonly Func<SpannerClientCreationOptions, SpannerSettings, Logger, Task<SpannerClient>> _clientFactory;

Expand All @@ -74,28 +74,32 @@ static SessionPoolManager()
/// Currently the default settings are used in all cases, but with the "gccl" version header added to specify the version of Google.Cloud.Spanner.Data
/// being used.
/// </summary>
internal SpannerSettings SpannerSettings { get; } = CreateSpannerSettingsWithVersionHeader();
internal SpannerSettings SpannerSettings { get; }

private static SpannerSettings CreateSpannerSettingsWithVersionHeader()
private static SpannerSettings AppendVersionHeader(SpannerSettings settings)
{
var settings = new SpannerSettings();
settings.VersionHeaderBuilder.AppendAssemblyVersion("gccl", typeof(SessionPoolManager));
return settings;
}

private static SpannerSettings DefaultSpannerSettings() => new SpannerSettings();

/// <summary>
/// Constructor for test purposes, allowing the SpannerClient creation to be customized (e.g. for
/// fake clients).
/// </summary>
/// <param name="options">The session pool options to use. Must not be null.</param>
/// <param name="spannerSettings">The SpannerSettings to use. Must not be null.</param>
/// <param name="logger">The logger to use. Must not be null.</param>
/// <param name="clientFactory">The client factory delegate to use. Must not be null.</param>
internal SessionPoolManager(
SessionPoolOptions options,
SpannerSettings spannerSettings,
Logger logger,
Func<SpannerClientCreationOptions, SpannerSettings, Logger, Task<SpannerClient>> clientFactory)
{
SessionPoolOptions = GaxPreconditions.CheckNotNull(options, nameof(options));
SpannerSettings = AppendVersionHeader(GaxPreconditions.CheckNotNull(spannerSettings, nameof(spannerSettings)));
Logger = GaxPreconditions.CheckNotNull(logger, nameof(logger));
_clientFactory = GaxPreconditions.CheckNotNull(clientFactory, nameof(clientFactory));
}
Expand All @@ -107,7 +111,17 @@ private static SpannerSettings CreateSpannerSettingsWithVersionHeader()
/// <param name="logger">The logger to use. May be null, in which case the default logger is used.</param>
/// <returns>A <see cref="SessionPoolManager"/> with the given options.</returns>
public static SessionPoolManager Create(SessionPoolOptions options, Logger logger = null) =>
new SessionPoolManager(options, logger ?? Logger.DefaultLogger, CreateClientAsync);
new SessionPoolManager(options, DefaultSpannerSettings(), logger ?? Logger.DefaultLogger, CreateClientAsync);

/// <summary>
/// Creates a <see cref="SessionPoolManager"/> with the specified options.
/// </summary>
/// <param name="spannerSettings">The SpannerSettings to use. May be null, in which case the default settings are used.</param>
/// <param name="options">The options to use. Must not be null.</param>
/// <param name="logger">The logger to use. May be null, in which case the default logger is used.</param>
/// <returns>A <see cref="SessionPoolManager"/> with the given options.</returns>
public static SessionPoolManager Create(SessionPoolOptions options, SpannerSettings spannerSettings, Logger logger = null) =>
new SessionPoolManager(options, spannerSettings ?? DefaultSpannerSettings(), logger ?? Logger.DefaultLogger, CreateClientAsync);

internal Task<SessionPool> AcquireSessionPoolAsync(SpannerClientCreationOptions options)
{
Expand Down Expand Up @@ -307,12 +321,6 @@ private static async Task<SpannerClient> CreateClientAsync(SpannerClientCreation
new ChannelOption(GcpCallInvoker.ApiConfigChannelArg, apiConfig.ToString())
};

if (!string.IsNullOrEmpty(channelOptions.AdditionalVersionHeaderName))
{
spannerSettings.VersionHeaderBuilder.AppendVersion(channelOptions.AdditionalVersionHeaderName,
channelOptions.AdditionalVersionHeaderVersion);
}

var callInvoker = new GcpCallInvoker(channelOptions.Endpoint, credentials, grpcOptions);

return new SpannerClientBuilder
Expand Down
Expand Up @@ -45,13 +45,6 @@ private static async Task<ChannelCredentials> CreatedScopedDefaultCredentials()
/// </summary>
internal string Endpoint { get; }

/// <summary>
/// Any additional version header to add to the Spanner client.
/// This should only be set by frameworks that are developed by Google and not by normal end user applications.
/// </summary>
internal string AdditionalVersionHeaderName { get; }
internal string AdditionalVersionHeaderVersion { get; }

/// <summary>
/// The number of gRPC channels to use (passed to Grpc.Gcp)
/// </summary>
Expand Down Expand Up @@ -84,8 +77,6 @@ internal SpannerClientCreationOptions(SpannerConnectionStringBuilder builder)
// If the client connects to the emulator use its endpoint (regardless of builder.Endpoint)
Endpoint = emulatorBuilder?.Endpoint ?? builder.EndPoint;
_credentialsFile = builder.CredentialFile;
AdditionalVersionHeaderName = builder.VersionHeaderName;
AdditionalVersionHeaderVersion = builder.VersionHeaderVersion;

// If the client connects to the emulator, use its credentials (regardless of builder.CredentialOverride)
_credentialsOverride = emulatorBuilder?.ChannelCredentials ?? builder.CredentialOverride;
Expand All @@ -102,9 +93,7 @@ internal SpannerClientCreationOptions(SpannerConnectionStringBuilder builder)
Equals(_credentialsOverride, other._credentialsOverride) &&
UsesEmulator == other.UsesEmulator &&
MaximumGrpcChannels == other.MaximumGrpcChannels &&
MaximumConcurrentStreamsLowWatermark == other.MaximumConcurrentStreamsLowWatermark &&
AdditionalVersionHeaderName == other.AdditionalVersionHeaderName &&
AdditionalVersionHeaderVersion == other.AdditionalVersionHeaderVersion;
MaximumConcurrentStreamsLowWatermark == other.MaximumConcurrentStreamsLowWatermark;

public override int GetHashCode()
{
Expand All @@ -117,8 +106,6 @@ public override int GetHashCode()
hash = hash * 23 + UsesEmulator.GetHashCode();
hash = hash * 23 + MaximumGrpcChannels;
hash = hash * 23 + (int) MaximumConcurrentStreamsLowWatermark;
hash = hash * 23 + (AdditionalVersionHeaderName?.GetHashCode() ?? 0);
hash = hash * 23 + (AdditionalVersionHeaderVersion?.GetHashCode() ?? 0);
return hash;
}
}
Expand All @@ -139,10 +126,6 @@ public override string ToString()
builder.Append($"; CredentialsOverride: True");
}
builder.Append($"; UsesEmulator: {UsesEmulator}");
if (!string.IsNullOrEmpty(AdditionalVersionHeaderName))
{
builder.Append($"; AdditionalVersionHeader: {AdditionalVersionHeaderName}/{AdditionalVersionHeaderVersion}");
}
return builder.ToString();
}

Expand Down
Expand Up @@ -53,7 +53,6 @@ public sealed class SpannerConnectionStringBuilder : DbConnectionStringBuilder
private const string EnableGetSchemaTableKeyword = "EnableGetSchemaTable";
private const string LogCommitStatsKeyword = "LogCommitStats";
private const string EmulatorDetectionKeyword = "EmulatorDetection";
private const string VersionHeaderKeyword = "VersionHeader";

private InstanceName _instanceName;
private DatabaseName _databaseName;
Expand Down Expand Up @@ -335,40 +334,6 @@ public EmulatorDetection EmulatorDetection
}
}

/// <summary>
/// Other known clients may set an additional value for the x-goog-api-client version header. This
/// array contains the only valid values.
/// </summary>
private static readonly string[] s_allowedVersionHeaders = { "efcore" };

private string ValidateVersionHeader(string header)
{
GaxPreconditions.CheckNotNullOrEmpty(header, nameof(header));
GaxPreconditions.CheckArgument(header.Contains('/'), nameof(header), "Invalid header value. It must contain a '/' between the name and version.");
var parts = header.Split('/');
GaxPreconditions.CheckArgument(
s_allowedVersionHeaders.Contains(parts[0]),
nameof(header),
"Invalid header value. This property is not for public use and should not be set by end users."
);
GaxPreconditions.CheckNotNullOrEmpty(parts[1], "version");
return header;
}

/// <summary>
/// Sets an additional value for the x-goog-api-client version header.
/// End-users should never use this property; it is intended for use in Google libraries which provide
/// a higher level abstraction over the normal client library.
/// </summary>
public string VersionHeader
{
get => GetValueOrDefault(VersionHeaderKeyword, null);
set => this[VersionHeaderKeyword] = ValidateVersionHeader(value);
}

internal string VersionHeaderName => string.IsNullOrEmpty(VersionHeader) ? null : VersionHeader.Split('/')[0];
internal string VersionHeaderVersion => string.IsNullOrEmpty(VersionHeader) ? null : VersionHeader.Split('/')[1];

internal ChannelCredentials CredentialOverride { get; }

private SessionPoolManager _sessionPoolManager = SessionPoolManager.Default;
Expand Down Expand Up @@ -491,10 +456,6 @@ private string GetValueOrDefault(string key, string defaultValue = "")
{
value = ValidatedDataSource((string)value);
}
if (string.Equals(keyword, VersionHeaderKeyword, StringComparison.OrdinalIgnoreCase))
{
value = ValidateVersionHeader((string) value);
}
base[keyword] = value;
}
}
Expand Down

0 comments on commit 6016ef0

Please sign in to comment.