зеркало из https://github.com/microsoft/BuildXL.git
Merged PR 640762: Support out-of-proc mode for cache service
This PR adds a new mode of running cache out-of-proc with the current cache bits. I'll check this change in CI and will create a CloudBuild PR with the required changes before merging the change in. Related work items: #1856822
This commit is contained in:
Родитель
1d0dc08a64
Коммит
3f94e57d5e
|
@ -27,6 +27,7 @@ using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.InterfacesTest.Results;
|
using BuildXL.Cache.ContentStore.InterfacesTest.Results;
|
||||||
using BuildXL.Cache.ContentStore.Service;
|
using BuildXL.Cache.ContentStore.Service;
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Cache.Host.Service;
|
using BuildXL.Cache.Host.Service;
|
||||||
using BuildXL.Cache.Host.Service.Internal;
|
using BuildXL.Cache.Host.Service.Internal;
|
||||||
|
@ -307,7 +308,7 @@ namespace ContentStoreTest.Distributed.Sessions
|
||||||
|
|
||||||
if (UseGrpcServer)
|
if (UseGrpcServer)
|
||||||
{
|
{
|
||||||
var server = (ILocalContentServer<TStore>)new CacheServerFactory(arguments).Create();
|
var server = (ILocalContentServer<TStore>)new CacheServerFactory(arguments).CreateAsync(new OperationContext(context)).GetAwaiter().GetResult();
|
||||||
TStore store = server.StoresByName["Default"];
|
TStore store = server.StoresByName["Default"];
|
||||||
//if (store is MultiplexedContentStore multiplexedStore)
|
//if (store is MultiplexedContentStore multiplexedStore)
|
||||||
//{
|
//{
|
||||||
|
@ -398,9 +399,9 @@ namespace ContentStoreTest.Distributed.Sessions
|
||||||
return _secrets[key];
|
return _secrets[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Dictionary<string, Secret>> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
public Task<RetrievedSecrets> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
||||||
{
|
{
|
||||||
return Task.FromResult(requests.ToDictionary(r => r.Name, r => (Secret)new PlainTextSecret(_secrets[r.Name])));
|
return Task.FromResult(new RetrievedSecrets(requests.ToDictionary(r => r.Name, r => (Secret)new PlainTextSecret(_secrets[r.Name]))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStartedService() { }
|
public void OnStartedService() { }
|
||||||
|
|
|
@ -305,7 +305,7 @@ namespace BuildXL.Cache.ContentStore.Distributed.Test
|
||||||
|
|
||||||
private class TestSecretsProvider : ISecretsProvider
|
private class TestSecretsProvider : ISecretsProvider
|
||||||
{
|
{
|
||||||
public Task<Dictionary<string, Secret>> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
public Task<RetrievedSecrets> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
||||||
{
|
{
|
||||||
var secrets = new Dictionary<string, Secret>();
|
var secrets = new Dictionary<string, Secret>();
|
||||||
|
|
||||||
|
@ -318,16 +318,17 @@ namespace BuildXL.Cache.ContentStore.Distributed.Test
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
request.Kind.Should().Be(SecretKind.SasToken);
|
request.Kind.Should().Be(SecretKind.SasToken);
|
||||||
secrets.Add(request.Name, new UpdatingSasToken(new SasToken()
|
secrets.Add(
|
||||||
{
|
request.Name,
|
||||||
StorageAccount = $"https://{request.Name}.azure.blob.com/",
|
new UpdatingSasToken(
|
||||||
ResourcePath = "ResourcePath",
|
new SasToken(
|
||||||
Token = Guid.NewGuid().ToString()
|
storageAccount: $"https://{request.Name}.azure.blob.com/",
|
||||||
}));
|
resourcePath: "ResourcePath",
|
||||||
|
token: Guid.NewGuid().ToString())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(secrets);
|
return Task.FromResult(new RetrievedSecrets(secrets));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.ContractsLight;
|
using System.Diagnostics.ContractsLight;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace BuildXL.Cache.ContentStore.Interfaces.Secrets
|
namespace BuildXL.Cache.ContentStore.Interfaces.Secrets
|
||||||
{
|
{
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
|
@ -17,8 +19,35 @@ namespace BuildXL.Cache.ContentStore.Interfaces.Secrets
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public abstract class Secret
|
public abstract class Secret : IEquatable<Secret>
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract bool Equals(Secret? other);
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public abstract override int GetHashCode();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, obj))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.GetType() != GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Equals((Secret)obj);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
|
@ -33,19 +62,67 @@ namespace BuildXL.Cache.ContentStore.Interfaces.Secrets
|
||||||
Contract.Requires(!string.IsNullOrEmpty(secret));
|
Contract.Requires(!string.IsNullOrEmpty(secret));
|
||||||
Secret = secret;
|
Secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(Secret? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Secret == ((PlainTextSecret)other).Secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Secret.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public class SasToken
|
public sealed class SasToken : IEquatable<SasToken>
|
||||||
{
|
{
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public string? Token { get; set; }
|
public string Token { get; }
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public string? StorageAccount { get; set; }
|
public string StorageAccount { get; }
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public string? ResourcePath { get; set; }
|
public string? ResourcePath { get; init; }
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public SasToken(string token, string storageAccount, string? resourcePath = null)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
StorageAccount = storageAccount;
|
||||||
|
ResourcePath = resourcePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(SasToken? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Token == other.Token && StorageAccount == other.StorageAccount && ResourcePath == other.ResourcePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as SasToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (Token, StorageAccount, ResourcePath ?? string.Empty).GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
|
@ -66,9 +143,27 @@ namespace BuildXL.Cache.ContentStore.Interfaces.Secrets
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public void UpdateToken(SasToken token)
|
public void UpdateToken(SasToken token)
|
||||||
{
|
{
|
||||||
Contract.RequiresNotNull(token);
|
Contract.Requires(token != null);
|
||||||
|
|
||||||
Token = token;
|
Token = token;
|
||||||
TokenUpdated?.Invoke(this, token);
|
TokenUpdated?.Invoke(this, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(Secret? other)
|
||||||
|
{
|
||||||
|
if (other is not UpdatingSasToken otherToken)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Token.Equals(otherToken.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Token.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace BuildXL.Cache.ContentStore.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets set of environment variables used to launch service in child process using <see cref="RunDeployedInterruptableServiceAsync"/>
|
/// Gets set of environment variables used to launch service in child process using <see cref="RunDeployedInterruptableServiceAsync{T}"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, string> GetDeployedInterruptableServiceVariables(string serviceId)
|
public IDictionary<string, string> GetDeployedInterruptableServiceVariables(string serviceId)
|
||||||
{
|
{
|
||||||
|
@ -95,7 +95,7 @@ namespace BuildXL.Cache.ContentStore.Service
|
||||||
private string PreventStartupFile(string serviceId) => Path.Combine(SignalFileRoot.Path, $"{serviceId}.preventstartup");
|
private string PreventStartupFile(string serviceId) => Path.Combine(SignalFileRoot.Path, $"{serviceId}.preventstartup");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run a service which can be interrupted by another service (via <see cref="RunInterrupterServiceAsync"/>)
|
/// Run a service which can be interrupted by another service (via <see cref="RunInterrupterServiceAsync{T}"/>)
|
||||||
/// or shutdown (via <see cref="ShutdownServiceAsync"/>).
|
/// or shutdown (via <see cref="ShutdownServiceAsync"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<T> RunInterruptableServiceAsync<T>(OperationContext context, string serviceId, Func<CancellationToken, Task<T>> runAsync)
|
public async Task<T> RunInterruptableServiceAsync<T>(OperationContext context, string serviceId, Func<CancellationToken, Task<T>> runAsync)
|
||||||
|
@ -117,7 +117,7 @@ namespace BuildXL.Cache.ContentStore.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a service which can interrupt another service started with <see cref="RunInterruptableServiceAsync"/>
|
/// Runs a service which can interrupt another service started with <see cref="RunInterruptableServiceAsync{T}"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<T> RunInterrupterServiceAsync<T>(OperationContext context, string serviceId, string serviceToInterruptId, Func<CancellationToken, Task<T>> runAsync)
|
public async Task<T> RunInterrupterServiceAsync<T>(OperationContext context, string serviceId, string serviceToInterruptId, Func<CancellationToken, Task<T>> runAsync)
|
||||||
{
|
{
|
||||||
|
@ -131,7 +131,7 @@ namespace BuildXL.Cache.ContentStore.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals and waits for shutdown a service run under <see cref="RunInterruptableServiceAsync"/>
|
/// Signals and waits for shutdown a service run under <see cref="RunInterruptableServiceAsync{T}"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task ShutdownServiceAsync(OperationContext context, string serviceId)
|
public Task ShutdownServiceAsync(OperationContext context, string serviceId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,12 +6,13 @@ using System.Diagnostics;
|
||||||
using System.Diagnostics.ContractsLight;
|
using System.Diagnostics.ContractsLight;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BuildXL.Cache.ContentStore.FileSystem;
|
using BuildXL.Cache.ContentStore.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Utilities;
|
||||||
|
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
||||||
|
|
||||||
namespace BuildXL.Cache.ContentStore.Tracing
|
namespace BuildXL.Cache.ContentStore.Tracing
|
||||||
{
|
{
|
||||||
|
@ -187,7 +188,8 @@ namespace BuildXL.Cache.ContentStore.Tracing
|
||||||
|
|
||||||
LifetimeTrackerHelper = Tracing.LifetimeTrackerHelper.Starting(clock.GetUtcNow(), processStartupTime ?? GetProcessStartupTimeUtc(), offlineTime.Then(v => Result.Success(v.lastServiceHeartbeatTime)));
|
LifetimeTrackerHelper = Tracing.LifetimeTrackerHelper.Starting(clock.GetUtcNow(), processStartupTime ?? GetProcessStartupTimeUtc(), offlineTime.Then(v => Result.Success(v.lastServiceHeartbeatTime)));
|
||||||
|
|
||||||
Trace(context, $"Starting CaSaaS instance{offlineTime.ToStringSelect(r => $". LastHeartBeatTime={r.lastServiceHeartbeatTime}, ShutdownCorrectly={r.shutdownCorrectly}")}");
|
var runtime = OperatingSystemHelper.GetRuntimeFrameworkNameAndVersion();
|
||||||
|
Trace(context, $"Starting CaSaaS instance. Runtime={runtime}{offlineTime.ToStringSelect(r => $". LastHeartBeatTime={r.lastServiceHeartbeatTime}, ShutdownCorrectly={r.shutdownCorrectly}")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -34,6 +34,11 @@ namespace BuildXL.Cache.ContentStore.Tracing.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class OperationContextExtensions
|
public static class OperationContextExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Detaches a <see cref="CancellationToken"/> instance from <paramref name="context"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static OperationContext WithoutCancellationToken(this OperationContext context) => new OperationContext(context.TracingContext, token: CancellationToken.None);
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public static PerformAsyncOperationBuilder<TResult> CreateOperation<TResult>(this OperationContext context, Tracer tracer, Func<Task<TResult>> operation) where TResult : ResultBase
|
public static PerformAsyncOperationBuilder<TResult> CreateOperation<TResult>(this OperationContext context, Tracer tracer, Func<Task<TResult>> operation) where TResult : ResultBase
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace BuildXL.Cache.ContentStore.UtilitiesCore.Internal
|
namespace BuildXL.Cache.ContentStore.UtilitiesCore.Internal
|
||||||
{
|
{
|
||||||
|
@ -29,6 +28,16 @@ namespace BuildXL.Cache.ContentStore.UtilitiesCore.Internal
|
||||||
public static readonly T[] EmptyArray = new T[] { };
|
public static readonly T[] EmptyArray = new T[] { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class Empty<TKey, TValue>
|
||||||
|
{
|
||||||
|
public static readonly Dictionary<TKey, TValue> EmptyDictionary = new Dictionary<TKey, TValue>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an empty instance of <see cref="IReadOnlyDictionary{TKey,TValue}"/>
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyDictionary<TKey, TValue> EmptyDictionary<TKey, TValue>() => Empty<TKey, TValue>.EmptyDictionary;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows deconstructing a key value pair to a tuple
|
/// Allows deconstructing a key value pair to a tuple
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -144,9 +144,9 @@ namespace BuildXL.Cache.Host.Configuration
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of time the secret can be cached before needing to be requeried
|
/// The amount of time the secret can be cached before needing to be re-queried.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan TimeToLive { get; set; }
|
public TimeSpan TimeToLive { get; set; } = TimeSpan.FromHours(1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overrides the key vault uri used to retrieve this secret
|
/// Overrides the key vault uri used to retrieve this secret
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.ContractsLight;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
@ -68,6 +70,30 @@ namespace BuildXL.Cache.Host.Configuration
|
||||||
{
|
{
|
||||||
return $"Machine={Machine} Stamp={Stamp}";
|
return $"Machine={Machine} Stamp={Stamp}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ApplyFromTelemetryProviderIfNeeded(ITelemetryFieldsProvider telemetryProvider)
|
||||||
|
{
|
||||||
|
if (telemetryProvider is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ring ??= telemetryProvider.Ring;
|
||||||
|
Stamp ??= telemetryProvider.Stamp;
|
||||||
|
Machine ??= telemetryProvider.MachineName;
|
||||||
|
MachineFunction ??= telemetryProvider.MachineName;
|
||||||
|
Environment ??= telemetryProvider.APEnvironment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HostParameters FromTelemetryProvider(ITelemetryFieldsProvider telemetryProvider)
|
||||||
|
{
|
||||||
|
Contract.Requires(telemetryProvider is not null);
|
||||||
|
|
||||||
|
var result = new HostParameters();
|
||||||
|
result.ApplyFromTelemetryProviderIfNeeded(telemetryProvider);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeploymentParameters : HostParameters
|
public class DeploymentParameters : HostParameters
|
||||||
|
|
|
@ -12,7 +12,9 @@ using BuildXL.Cache.ContentStore.Interfaces.Distributed;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Utils;
|
using BuildXL.Cache.ContentStore.Interfaces.Utils;
|
||||||
using ContentStore.Grpc;
|
using ContentStore.Grpc;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Configuration
|
namespace BuildXL.Cache.Host.Configuration
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -63,6 +65,22 @@ namespace BuildXL.Cache.Host.Configuration
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public LauncherSettings LauncherSettings { get; set; } = null;
|
public LauncherSettings LauncherSettings { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Settings for running cache out of proc as a .net core process.
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public OutOfProcCacheSettings OutOfProcCacheSettings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true the cache should run out of proc as a .net core process.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If this property is true and <see cref="OutOfProcCacheSettings"/> is null, that property should be created
|
||||||
|
/// by the host and set <see cref="BuildXL.Cache.Host.Configuration.OutOfProcCacheSettings.CacheConfigPath"/> property.
|
||||||
|
/// </remarks>
|
||||||
|
[DataMember]
|
||||||
|
public bool? RunCacheOutOfProc { get; set; }
|
||||||
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public LogManagerConfiguration LogManager { get; set; } = null;
|
public LogManagerConfiguration LogManager { get; set; } = null;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Configuration
|
||||||
|
{
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
public class OutOfProcCacheSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A path to the cache configuration that the launched process will use.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property needs to be set in CloudBuild in order to use 'out-of-proc' cache.
|
||||||
|
/// </remarks>
|
||||||
|
public string? CacheConfigPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A relative path from the current executing assembly to the stand-alone cache service that will be launched in a separate process.
|
||||||
|
/// </summary>
|
||||||
|
public string? Executable { get; set; }
|
||||||
|
|
||||||
|
public int? ServiceLifetimePollingIntervalSeconds { get; set; }
|
||||||
|
|
||||||
|
public int? ShutdownTimeoutSeconds { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import * as Managed from "Sdk.Managed";
|
import * as Managed from "Sdk.Managed";
|
||||||
import * as BuildXLSdk from "Sdk.BuildXL";
|
import * as BuildXLSdk from "Sdk.BuildXL";
|
||||||
import { NetFx } from "Sdk.BuildXL";
|
|
||||||
|
|
||||||
namespace LauncherServer {
|
namespace LauncherServer {
|
||||||
|
|
||||||
|
@ -30,7 +29,6 @@ namespace LauncherServer {
|
||||||
importFrom("Azure.Core").pkg,
|
importFrom("Azure.Core").pkg,
|
||||||
importFrom("Microsoft.Identity.Client").pkg,
|
importFrom("Microsoft.Identity.Client").pkg,
|
||||||
|
|
||||||
|
|
||||||
// AspNetCore assemblies
|
// AspNetCore assemblies
|
||||||
Managed.Factory.filterRuntimeSpecificBinaries(BuildXLSdk.WebFramework.getFrameworkPackage(), [
|
Managed.Factory.filterRuntimeSpecificBinaries(BuildXLSdk.WebFramework.getFrameworkPackage(), [
|
||||||
importFrom("System.IO.Pipelines").pkg
|
importFrom("System.IO.Pipelines").pkg
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.ContractsLight;
|
using System.Diagnostics.ContractsLight;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Logging;
|
using BuildXL.Cache.ContentStore.Logging;
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
@ -16,15 +11,10 @@ using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Cache.Host.Service;
|
using BuildXL.Cache.Host.Service;
|
||||||
using BuildXL.Launcher.Server.Controllers;
|
using BuildXL.Launcher.Server.Controllers;
|
||||||
using BuildXL.Utilities.Collections;
|
using BuildXL.Utilities.Collections;
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.HttpsPolicy;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using ILogger = BuildXL.Cache.ContentStore.Interfaces.Logging.ILogger;
|
using ILogger = BuildXL.Cache.ContentStore.Interfaces.Logging.ILogger;
|
||||||
|
|
||||||
namespace BuildXL.Launcher.Server
|
namespace BuildXL.Launcher.Server
|
||||||
|
@ -64,13 +54,24 @@ namespace BuildXL.Launcher.Server
|
||||||
var logger = new Logger(consoleLog);
|
var logger = new Logger(consoleLog);
|
||||||
|
|
||||||
var cacheConfigurationPath = configuration["CacheConfigurationPath"];
|
var cacheConfigurationPath = configuration["CacheConfigurationPath"];
|
||||||
var standalone = configuration.GetValue<bool>("standalone", true);
|
var standalone = configuration.GetValue("standalone", true);
|
||||||
|
var secretsProviderKind = configuration.GetValue("secretsProviderKind", CrossProcessSecretsCommunicationKind.Environment);
|
||||||
|
|
||||||
return CacheServiceRunner.RunCacheServiceAsync(
|
return CacheServiceRunner.RunCacheServiceAsync(
|
||||||
new OperationContext(new Context(logger), token),
|
new OperationContext(new Context(logger), token),
|
||||||
cacheConfigurationPath,
|
cacheConfigurationPath,
|
||||||
(hostParameters, config, token) =>
|
createHost: (hostParameters, config, token) =>
|
||||||
{
|
{
|
||||||
var serviceHost = new ServiceHost(commandLineArgs, config, hostParameters);
|
// If this process was started as a standalone cache service, we need to change the mode
|
||||||
|
// this time to avoid trying to start the cache service process again.
|
||||||
|
config.DistributedContentSettings.RunCacheOutOfProc = false;
|
||||||
|
if (config.DataRootPath is null)
|
||||||
|
{
|
||||||
|
// The required property is not set, so it should be passed through the command line options by the parent process.
|
||||||
|
config.DataRootPath = configuration.GetValue("DataRootPath", "Unknown DataRootPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceHost = new ServiceHost(commandLineArgs, config, hostParameters, retrieveAllSecretsFromSingleEnvironmentVariable: secretsProviderKind == CrossProcessSecretsCommunicationKind.EnvironmentSingleEntry);
|
||||||
return serviceHost;
|
return serviceHost;
|
||||||
},
|
},
|
||||||
requireServiceInterruptable: !standalone);
|
requireServiceInterruptable: !standalone);
|
||||||
|
@ -120,29 +121,35 @@ namespace BuildXL.Launcher.Server
|
||||||
services.AddSingleton<HostParameters>(hostParameters);
|
services.AddSingleton<HostParameters>(hostParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add ProxyServiceConfiguration as a singleton in service provider
|
// Only the launcher-based invocation would have 'ProxyConfigurationPath' and
|
||||||
services.AddSingleton(sp =>
|
// the out-of-proc case would not.
|
||||||
|
var configurationPath = Configuration.GetValue<string>("ProxyConfigurationPath", null);
|
||||||
|
|
||||||
|
if (configurationPath is not null)
|
||||||
{
|
{
|
||||||
var context = sp.GetRequiredService<BoxRef<OperationContext>>().Value;
|
// Add ProxyServiceConfiguration as a singleton in service provider
|
||||||
var configurationPath = Configuration["ProxyConfigurationPath"];
|
services.AddSingleton(sp =>
|
||||||
var hostParameters = sp.GetService<HostParameters>();
|
{
|
||||||
|
var context = sp.GetRequiredService<BoxRef<OperationContext>>().Value;
|
||||||
|
var hostParameters = sp.GetService<HostParameters>();
|
||||||
|
|
||||||
return context.PerformOperation(
|
return context.PerformOperation(
|
||||||
new Tracer(nameof(CacheServiceStartup)),
|
new Tracer(nameof(CacheServiceStartup)),
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
var proxyConfiguration = CacheServiceRunner.LoadAndWatchPreprocessedConfig<DeploymentConfiguration, ProxyServiceConfiguration>(
|
var proxyConfiguration = CacheServiceRunner.LoadAndWatchPreprocessedConfig<DeploymentConfiguration, ProxyServiceConfiguration>(
|
||||||
context,
|
context,
|
||||||
configurationPath,
|
configurationPath,
|
||||||
configHash: out _,
|
configHash: out _,
|
||||||
hostParameters: hostParameters,
|
hostParameters: hostParameters,
|
||||||
extractConfig: c => c.Proxy.ServiceConfiguration);
|
extractConfig: c => c.Proxy.ServiceConfiguration);
|
||||||
|
|
||||||
return Result.Success(proxyConfiguration);
|
return Result.Success(proxyConfiguration);
|
||||||
},
|
},
|
||||||
messageFactory: r => $"ConfigurationPath=[{configurationPath}], Port={r.GetValueOrDefault()?.Port}",
|
messageFactory: r => $"ConfigurationPath=[{configurationPath}], Port={r.GetValueOrDefault()?.Port}",
|
||||||
caller: "LoadConfiguration").ThrowIfFailure();
|
caller: "LoadConfiguration").ThrowIfFailure();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add DeploymentProxyService as a singleton in service provider
|
// Add DeploymentProxyService as a singleton in service provider
|
||||||
services.AddSingleton(sp =>
|
services.AddSingleton(sp =>
|
||||||
|
@ -188,7 +195,8 @@ namespace BuildXL.Launcher.Server
|
||||||
/// Constructs the service host and takes command line arguments because
|
/// Constructs the service host and takes command line arguments because
|
||||||
/// ASP.Net core application host is used to parse command line.
|
/// ASP.Net core application host is used to parse command line.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ServiceHost(string[] commandLineArgs, DistributedCacheServiceConfiguration configuration, HostParameters hostParameters)
|
public ServiceHost(string[] commandLineArgs, DistributedCacheServiceConfiguration configuration, HostParameters hostParameters, bool retrieveAllSecretsFromSingleEnvironmentVariable)
|
||||||
|
: base(retrieveAllSecretsFromSingleEnvironmentVariable)
|
||||||
{
|
{
|
||||||
HostParameters = hostParameters;
|
HostParameters = hostParameters;
|
||||||
ServiceConfiguration = configuration;
|
ServiceConfiguration = configuration;
|
||||||
|
|
|
@ -1,21 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
using BuildXL.Cache.ContentStore.Logging;
|
using BuildXL.Cache.ContentStore.Logging;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Cache.Host.Service;
|
using BuildXL.Cache.Host.Service;
|
||||||
using BuildXL.Launcher.Server.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.HttpsPolicy;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace BuildXL.Launcher.Server
|
||||||
_client = new SecretClient(new Uri(keyVaultUri), new DefaultAzureCredential());
|
_client = new SecretClient(new Uri(keyVaultUri), new DefaultAzureCredential());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, Secret>> RetrieveSecretsAsync(
|
public async Task<RetrievedSecrets> RetrieveSecretsAsync(
|
||||||
List<RetrieveSecretsRequest> requests,
|
List<RetrieveSecretsRequest> requests,
|
||||||
CancellationToken token)
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
|
@ -38,7 +38,7 @@ namespace BuildXL.Launcher.Server
|
||||||
secrets[request.Name] = new PlainTextSecret(secret);
|
secrets[request.Name] = new PlainTextSecret(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
return secrets;
|
return new RetrievedSecrets(secrets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace BuildXL.Launcher.Server
|
namespace BuildXL.Launcher.Server
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
|
||||||
using BuildXL.Cache.ContentStore.Logging;
|
|
||||||
using BuildXL.Launcher.Server.Controllers;
|
using BuildXL.Launcher.Server.Controllers;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.HttpsPolicy;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using ILogger = BuildXL.Cache.ContentStore.Interfaces.Logging.ILogger;
|
|
||||||
|
|
||||||
namespace BuildXL.Launcher.Server
|
namespace BuildXL.Launcher.Server
|
||||||
{
|
{
|
||||||
|
|
|
@ -100,7 +100,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
public static async Task RunCacheServiceAsync(
|
public static async Task RunCacheServiceAsync(
|
||||||
OperationContext context,
|
OperationContext context,
|
||||||
string configurationPath,
|
string configurationPath,
|
||||||
Func<HostParameters, DistributedCacheServiceConfiguration, CancellationToken, IDistributedCacheServiceHost> createhost,
|
Func<HostParameters, DistributedCacheServiceConfiguration, CancellationToken, IDistributedCacheServiceHost> createHost,
|
||||||
HostParameters hostParameters = null,
|
HostParameters hostParameters = null,
|
||||||
bool requireServiceInterruptable = true)
|
bool requireServiceInterruptable = true)
|
||||||
{
|
{
|
||||||
|
@ -122,7 +122,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
var hostInfo = new HostInfo(hostParameters.Stamp, hostParameters.Ring, new List<string>());
|
var hostInfo = new HostInfo(hostParameters.Stamp, hostParameters.Ring, new List<string>());
|
||||||
|
|
||||||
var host = createhost(hostParameters, config, token);
|
var host = createHost(hostParameters, config, token);
|
||||||
|
|
||||||
await DistributedCacheServiceFacade.RunWithConfigurationAsync(
|
await DistributedCacheServiceFacade.RunWithConfigurationAsync(
|
||||||
logger: context.TracingContext.Logger,
|
logger: context.TracingContext.Logger,
|
||||||
|
|
|
@ -6,44 +6,83 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics.ContractsLight;
|
using System.Diagnostics.ContractsLight;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
||||||
using BuildXL.Cache.Host.Service;
|
using BuildXL.Cache.Host.Service.Internal;
|
||||||
using Microsoft.WindowsAzure.Storage;
|
using Microsoft.WindowsAzure.Storage;
|
||||||
|
|
||||||
// ReSharper disable once UnusedMember.Global
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host where secrets are derived from environment variables
|
/// Host where secrets are derived from environment variables.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EnvironmentVariableHost : IDistributedCacheServiceHost
|
public class EnvironmentVariableHost : IDistributedCacheServiceHost
|
||||||
{
|
{
|
||||||
|
private readonly bool _retrieveAllSecretsFromSingleEnvironmentVariable;
|
||||||
|
private Result<RetrievedSecrets> _secrets;
|
||||||
|
|
||||||
public CancellationTokenSource TeardownCancellationTokenSource { get; } = new CancellationTokenSource();
|
public CancellationTokenSource TeardownCancellationTokenSource { get; } = new CancellationTokenSource();
|
||||||
|
|
||||||
|
public EnvironmentVariableHost(bool retrieveAllSecretsFromSingleEnvironmentVariable = false)
|
||||||
|
{
|
||||||
|
_retrieveAllSecretsFromSingleEnvironmentVariable = retrieveAllSecretsFromSingleEnvironmentVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void RequestTeardown(string reason)
|
public virtual void RequestTeardown(string reason)
|
||||||
{
|
{
|
||||||
TeardownCancellationTokenSource.Cancel();
|
TeardownCancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetSecretStoreValue(string key)
|
private string GetSecretStoreValue(string key)
|
||||||
{
|
{
|
||||||
return Environment.GetEnvironmentVariable(key);
|
return Environment.GetEnvironmentVariable(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void OnStartedService()
|
public virtual void OnStartedService()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual Task OnStartingServiceAsync()
|
public virtual Task OnStartingServiceAsync()
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void OnTeardownCompleted()
|
public virtual void OnTeardownCompleted()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<RetrievedSecrets> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
||||||
|
{
|
||||||
|
// Checking the mode first: out-of-proc cache service passes all secrets via a single environment variable.
|
||||||
|
if (_retrieveAllSecretsFromSingleEnvironmentVariable)
|
||||||
|
{
|
||||||
|
var secretsResult = LazyInitializer.EnsureInitialized(ref _secrets, () => DeserializeFromEnvironmentVariable());
|
||||||
|
|
||||||
public Task<Dictionary<string, Secret>> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
secretsResult.ThrowIfFailure();
|
||||||
|
return Task.FromResult(secretsResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RetrieveSecretsCoreAsync(requests, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<RetrievedSecrets> DeserializeFromEnvironmentVariable()
|
||||||
|
{
|
||||||
|
var variableName = RetrievedSecretsSerializer.SerializedSecretsKeyName;
|
||||||
|
var variable = Environment.GetEnvironmentVariable(variableName);
|
||||||
|
if (string.IsNullOrEmpty(variable))
|
||||||
|
{
|
||||||
|
return Result.FromErrorMessage<RetrievedSecrets>($"Environment variable '{variableName}' is null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return RetrievedSecretsSerializer.Deserialize(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<RetrievedSecrets> RetrieveSecretsCoreAsync(List<RetrieveSecretsRequest> requests, CancellationToken token)
|
||||||
{
|
{
|
||||||
var secrets = new Dictionary<string, Secret>();
|
var secrets = new Dictionary<string, Secret>();
|
||||||
|
|
||||||
|
@ -75,7 +114,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
secrets[request.Name] = secret;
|
secrets[request.Name] = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(secrets);
|
return Task.FromResult(new RetrievedSecrets(secrets));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Secret CreateSasTokenSecret(RetrieveSecretsRequest request, string secretValue)
|
private Secret CreateSasTokenSecret(RetrieveSecretsRequest request, string secretValue)
|
||||||
|
@ -112,11 +151,10 @@ namespace BuildXL.Cache.Host.Service
|
||||||
IPAddressOrRange = null,
|
IPAddressOrRange = null,
|
||||||
});
|
});
|
||||||
|
|
||||||
var internalSasToken = new SasToken()
|
var internalSasToken = new SasToken(
|
||||||
{
|
token: sasToken,
|
||||||
Token = sasToken,
|
storageAccount: cloudStorageAccount.Credentials.AccountName,
|
||||||
StorageAccount = cloudStorageAccount.Credentials.AccountName,
|
resourcePath: null);
|
||||||
};
|
|
||||||
return new UpdatingSasToken(internalSasToken);
|
return new UpdatingSasToken(internalSasToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using BuildXL.Cache.Host.Service.OutOfProc;
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Service
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines which communication process is used for passing secrets between the current and the launched processes.
|
||||||
|
/// </summary>
|
||||||
|
public enum CrossProcessSecretsCommunicationKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The mode used by the launcher via <see cref="EnvironmentVariableHost"/> when all the secrets serialized through environment variables one by one.
|
||||||
|
/// </summary>
|
||||||
|
Environment,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mode used by <see cref="CacheServiceWrapper"/> when all the <see cref="RetrievedSecrets"/> serialized in a single environment variable.
|
||||||
|
/// </summary>
|
||||||
|
EnvironmentSingleEntry,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not implemented yet: will be used by <see cref="CacheServiceWrapper"/> when the secrets will be communicated via memory mapped file that will also support updates.
|
||||||
|
/// </summary>
|
||||||
|
MemoryMappedFile,
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,17 +8,13 @@ using System.Diagnostics.ContractsLight;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using BuildXL.Cache.ContentStore.Distributed;
|
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
using BuildXL.Cache.ContentStore.Stores;
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
|
|
@ -4,23 +4,17 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.ContractsLight;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
|
||||||
using BuildXL.Cache.ContentStore.Distributed;
|
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
|
||||||
using BuildXL.Cache.ContentStore.Service;
|
using BuildXL.Cache.ContentStore.Service;
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
using BuildXL.Cache.ContentStore.Stores;
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
@ -28,12 +22,10 @@ using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
using BuildXL.Cache.ContentStore.UtilitiesCore.Internal;
|
using BuildXL.Cache.ContentStore.UtilitiesCore.Internal;
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
using BuildXL.Cache.ContentStore.Utils;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Launcher.Server;
|
|
||||||
using BuildXL.Native.IO;
|
using BuildXL.Native.IO;
|
||||||
using BuildXL.Processes;
|
using BuildXL.Processes;
|
||||||
using BuildXL.Utilities.ParallelAlgorithms;
|
using BuildXL.Utilities.ParallelAlgorithms;
|
||||||
using BuildXL.Utilities.Tasks;
|
using BuildXL.Utilities.Tasks;
|
||||||
using Microsoft.Win32.SafeHandles;
|
|
||||||
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
|
@ -407,12 +399,12 @@ namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _mutex = TaskUtilities.CreateMutex();
|
private readonly SemaphoreSlim _mutex = TaskUtilities.CreateMutex();
|
||||||
|
|
||||||
|
private LauncherManagedProcess _runningProcess;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The active process for the tool
|
/// The active process for the tool
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ILauncherProcess RunningProcess { get; private set; }
|
public ILauncherProcess RunningProcess => _runningProcess?.Process;
|
||||||
|
|
||||||
private TaskSourceSlim<bool> ProcessExitSource { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether the tool process is running
|
/// Gets whether the tool process is running
|
||||||
|
@ -430,6 +422,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
public DisposableDirectory Directory { get; }
|
public DisposableDirectory Directory { get; }
|
||||||
|
|
||||||
private DeploymentLauncher Launcher { get; }
|
private DeploymentLauncher Launcher { get; }
|
||||||
|
|
||||||
public PinRequest PinRequest { get; set; }
|
public PinRequest PinRequest { get; set; }
|
||||||
|
|
||||||
public AbsolutePath DirectoryPath => Directory.Path;
|
public AbsolutePath DirectoryPath => Directory.Path;
|
||||||
|
@ -511,6 +504,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
protected override Task<BoolResult> StartupCoreAsync(OperationContext context)
|
protected override Task<BoolResult> StartupCoreAsync(OperationContext context)
|
||||||
{
|
{
|
||||||
int? processId = null;
|
int? processId = null;
|
||||||
|
var tool = Manifest.Tool;
|
||||||
return context.PerformOperationAsync(
|
return context.PerformOperationAsync(
|
||||||
Tracer,
|
Tracer,
|
||||||
async () =>
|
async () =>
|
||||||
|
@ -519,9 +513,9 @@ namespace BuildXL.Cache.Host.Service
|
||||||
// Or maybe process should terminate itself if its not healthy?
|
// Or maybe process should terminate itself if its not healthy?
|
||||||
using (await _mutex.AcquireAsync(context.Token))
|
using (await _mutex.AcquireAsync(context.Token))
|
||||||
{
|
{
|
||||||
if (RunningProcess == null)
|
if (_runningProcess == null)
|
||||||
{
|
{
|
||||||
var executablePath = (Directory.Path / Manifest.Tool.Executable).Path;
|
var executablePath = (Directory.Path / tool.Executable).Path;
|
||||||
if (!File.Exists(executablePath))
|
if (!File.Exists(executablePath))
|
||||||
{
|
{
|
||||||
return new BoolResult($"Executable '{executablePath}' does not exist.");
|
return new BoolResult($"Executable '{executablePath}' does not exist.");
|
||||||
|
@ -532,26 +526,22 @@ namespace BuildXL.Cache.Host.Service
|
||||||
return new BoolResult($"Executable permissions could not be set on '{executablePath}'.");
|
return new BoolResult($"Executable permissions could not be set on '{executablePath}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
RunningProcess = Launcher._host.CreateProcess(new ProcessStartInfo()
|
var process = Launcher._host.CreateProcess(
|
||||||
{
|
new ProcessStartInfo()
|
||||||
UseShellExecute = false,
|
|
||||||
FileName = executablePath,
|
|
||||||
Arguments = string.Join(" ", Manifest.Tool.Arguments.Select(arg => QuoteArgumentIfNecessary(ExpandTokens(arg)))),
|
|
||||||
Environment =
|
|
||||||
{
|
{
|
||||||
Launcher.Settings.DeploymentParameters.ToEnvironment(),
|
UseShellExecute = false,
|
||||||
Manifest.Tool.EnvironmentVariables.ToDictionary(kvp => kvp.Key, kvp => ExpandTokens(kvp.Value)),
|
FileName = executablePath,
|
||||||
Launcher.LifetimeManager.GetDeployedInterruptableServiceVariables(Manifest.Tool.ServiceId)
|
Arguments = string.Join(" ", tool.Arguments.Select(arg => QuoteArgumentIfNecessary(ExpandTokens(arg)))),
|
||||||
}
|
Environment =
|
||||||
});
|
{
|
||||||
|
Launcher.Settings.DeploymentParameters.ToEnvironment(),
|
||||||
|
tool.EnvironmentVariables.ToDictionary(kvp => kvp.Key, kvp => ExpandTokens(kvp.Value)),
|
||||||
|
Launcher.LifetimeManager.GetDeployedInterruptableServiceVariables(tool.ServiceId)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_runningProcess = new LauncherManagedProcess(process, tool.ServiceId, Launcher.LifetimeManager);
|
||||||
|
|
||||||
ProcessExitSource = TaskSourceSlim.Create<bool>();
|
_runningProcess.Start(context).ThrowIfFailure();
|
||||||
RunningProcess.Exited += () =>
|
|
||||||
{
|
|
||||||
OnExited(context, "ProcessExitedEvent");
|
|
||||||
};
|
|
||||||
|
|
||||||
RunningProcess.Start(context);
|
|
||||||
processId = RunningProcess.Id;
|
processId = RunningProcess.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,35 +549,22 @@ namespace BuildXL.Cache.Host.Service
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
traceOperationStarted: true,
|
traceOperationStarted: true,
|
||||||
extraStartMessage: $"ServiceId={Manifest.Tool.ServiceId}",
|
extraStartMessage: $"ServiceId={tool.ServiceId}",
|
||||||
extraEndMessage: r => $"ProcessId={processId ?? -1}, ServiceId={Manifest.Tool.ServiceId}");
|
extraEndMessage: r => $"ProcessId={processId ?? -1}, ServiceId={tool.ServiceId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnExited(OperationContext context, string trigger)
|
public static string QuoteArgumentIfNecessary(string arg)
|
||||||
{
|
|
||||||
context.PerformOperation(
|
|
||||||
Launcher.Tracer,
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
ProcessExitSource.TrySetResult(true);
|
|
||||||
return Result.Success(RunningProcess.ExitCode.ToString());
|
|
||||||
},
|
|
||||||
caller: "ServiceExited",
|
|
||||||
messageFactory: r => $"ProcessId={RunningProcess.Id}, ServiceId={Manifest.Tool.ServiceId}, ExitCode={r.GetValueOrDefault(string.Empty)} Trigger={trigger}").IgnoreFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string QuoteArgumentIfNecessary(string arg)
|
|
||||||
{
|
{
|
||||||
return arg.Contains(" ") ? $"\"{arg}\"" : arg;
|
return arg.Contains(" ") ? $"\"{arg}\"" : arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ExpandTokens(string value)
|
public string ExpandTokens(string value)
|
||||||
{
|
{
|
||||||
value = ExpandToken(value, "ServiceDir", DirectoryPath.Path);
|
value = ExpandToken(value, "ServiceDir", DirectoryPath.Path);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ExpandToken(string value, string tokenName, string tokenValue)
|
public static string ExpandToken(string value, string tokenName, string tokenValue)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(tokenValue))
|
if (!string.IsNullOrEmpty(tokenValue))
|
||||||
{
|
{
|
||||||
|
@ -600,80 +577,30 @@ namespace BuildXL.Cache.Host.Service
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Terminates the tool process
|
/// Terminates the tool process
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override Task<BoolResult> ShutdownCoreAsync(OperationContext context)
|
protected override async Task<BoolResult> ShutdownCoreAsync(OperationContext context)
|
||||||
{
|
{
|
||||||
var tool = Manifest.Tool;
|
var tool = Manifest.Tool;
|
||||||
|
|
||||||
return context.PerformOperationWithTimeoutAsync<BoolResult>(
|
using (await _mutex.AcquireAsync(context.Token))
|
||||||
Tracer,
|
{
|
||||||
async nestedContext =>
|
try
|
||||||
{
|
{
|
||||||
using (await _mutex.AcquireAsync(context.Token))
|
if (_runningProcess != null)
|
||||||
{
|
{
|
||||||
try
|
return await _runningProcess
|
||||||
{
|
.StopAsync(context, TimeSpan.FromSeconds(tool.ShutdownTimeoutSeconds))
|
||||||
if (RunningProcess != null && !RunningProcess.HasExited)
|
.ThrowIfFailure();
|
||||||
{
|
|
||||||
using var registration = nestedContext.Token.Register(() =>
|
|
||||||
{
|
|
||||||
TerminateService(context);
|
|
||||||
ProcessExitSource.TrySetCanceled();
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.PerformOperationAsync(
|
|
||||||
Tracer,
|
|
||||||
async () =>
|
|
||||||
{
|
|
||||||
await Launcher.LifetimeManager.ShutdownServiceAsync(nestedContext, tool.ServiceId);
|
|
||||||
return BoolResult.Success;
|
|
||||||
},
|
|
||||||
caller: "GracefulShutdownService").IgnoreFailure();
|
|
||||||
|
|
||||||
if (RunningProcess.HasExited)
|
|
||||||
{
|
|
||||||
OnExited(context, "ShutdownAlreadyExited");
|
|
||||||
}
|
|
||||||
|
|
||||||
await ProcessExitSource.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await PinRequest.PinContext.DisposeAsync();
|
|
||||||
|
|
||||||
Directory.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return BoolResult.Success;
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
timeout: TimeSpan.FromSeconds(tool.ShutdownTimeoutSeconds),
|
finally
|
||||||
extraStartMessage: $"ProcessId={RunningProcess?.Id}, ServiceId={Manifest.Tool.ServiceId}",
|
|
||||||
extraEndMessage: r => $"ProcessId={RunningProcess?.Id}, ServiceId={Manifest.Tool.ServiceId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Terminates the service by killing the process
|
|
||||||
/// </summary>
|
|
||||||
private void TerminateService(OperationContext context)
|
|
||||||
{
|
|
||||||
context.PerformOperation(
|
|
||||||
Tracer,
|
|
||||||
() =>
|
|
||||||
{
|
{
|
||||||
if (RunningProcess.HasExited)
|
await PinRequest.PinContext.DisposeAsync();
|
||||||
{
|
|
||||||
OnExited(context, "TerminateServiceAlreadyExited");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RunningProcess.Kill(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BoolResult.Success;
|
Directory.Dispose();
|
||||||
},
|
}
|
||||||
extraStartMessage: $"ProcessId={RunningProcess?.Id}, ServiceId={Manifest.Tool.ServiceId}",
|
|
||||||
messageFactory: r => $"ProcessId={RunningProcess?.Id}, ServiceId={Manifest.Tool.ServiceId}").IgnoreFailure();
|
return BoolResult.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,13 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Utilities.Collections;
|
|
||||||
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
|
@ -117,87 +106,5 @@ namespace BuildXL.Cache.Host.Service
|
||||||
// Do nothing. This instance is reused
|
// Do nothing. This instance is reused
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LauncherProcess : ILauncherProcess
|
|
||||||
{
|
|
||||||
private static readonly Tracer _tracer = new Tracer(nameof(LauncherProcess));
|
|
||||||
|
|
||||||
private readonly Process _process;
|
|
||||||
|
|
||||||
public LauncherProcess(ProcessStartInfo info)
|
|
||||||
{
|
|
||||||
info.RedirectStandardOutput = true;
|
|
||||||
info.RedirectStandardError = true;
|
|
||||||
|
|
||||||
_process = new Process()
|
|
||||||
{
|
|
||||||
StartInfo = info,
|
|
||||||
EnableRaisingEvents = true
|
|
||||||
};
|
|
||||||
|
|
||||||
_process.Exited += (sender, e) => Exited?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ExitCode => _process.ExitCode;
|
|
||||||
|
|
||||||
public int Id => _process.Id;
|
|
||||||
|
|
||||||
public bool HasExited => _process.HasExited;
|
|
||||||
|
|
||||||
public event Action Exited;
|
|
||||||
|
|
||||||
public void Kill(OperationContext context)
|
|
||||||
{
|
|
||||||
_process.Kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start(OperationContext context)
|
|
||||||
{
|
|
||||||
// Using nagle queues to "batch" messages together and to avoid writing them to the logs one by one.
|
|
||||||
var outputMessagesNagleQueue = NagleQueue<string>.Create(
|
|
||||||
messages =>
|
|
||||||
{
|
|
||||||
_tracer.Debug(context, $"Service Output: {string.Join(Environment.NewLine, messages)}");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
},
|
|
||||||
maxDegreeOfParallelism: 1, interval: TimeSpan.FromSeconds(10), batchSize: 1024);
|
|
||||||
|
|
||||||
var errorMessagesNagleQueue = NagleQueue<string>.Create(
|
|
||||||
messages =>
|
|
||||||
{
|
|
||||||
_tracer.Error(context, $"Service Error: {string.Join(Environment.NewLine, messages)}");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
},
|
|
||||||
maxDegreeOfParallelism: 1, interval: TimeSpan.FromSeconds(10), batchSize: 1024);
|
|
||||||
|
|
||||||
_process.OutputDataReceived += (s, e) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(e.Data))
|
|
||||||
{
|
|
||||||
outputMessagesNagleQueue.Enqueue(e.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_process.ErrorDataReceived += (s, e) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(e.Data))
|
|
||||||
{
|
|
||||||
errorMessagesNagleQueue.Enqueue(e.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_process.Exited += (sender, args) =>
|
|
||||||
{
|
|
||||||
// Dispose will drain all the existing items from the message queues.
|
|
||||||
outputMessagesNagleQueue.Dispose();
|
|
||||||
errorMessagesNagleQueue.Dispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
_process.Start();
|
|
||||||
|
|
||||||
_process.BeginOutputReadLine();
|
|
||||||
_process.BeginErrorReadLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,26 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed;
|
|
||||||
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
||||||
|
using BuildXL.Cache.ContentStore.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
|
||||||
using BuildXL.Cache.Host.Configuration;
|
|
||||||
using BuildXL.Cache.Host.Service;
|
|
||||||
using BuildXL.Utilities;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Extensions;
|
|
||||||
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
|
||||||
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
|
||||||
using BuildXL.Utilities.ParallelAlgorithms;
|
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Text;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.FileSystem;
|
using BuildXL.Cache.ContentStore.Stores;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Cache.ContentStore.Utils;
|
||||||
|
using BuildXL.Cache.Host.Configuration;
|
||||||
|
using BuildXL.Utilities;
|
||||||
|
using BuildXL.Utilities.ParallelAlgorithms;
|
||||||
|
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
||||||
|
|
||||||
namespace BuildXL.Launcher.Server
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service used ensure deployments are uploaded to target storage accounts and provide manifest for with download urls and tools to launch
|
/// Service used ensure deployments are uploaded to target storage accounts and provide manifest for with download urls and tools to launch
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.ContractsLight;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -8,30 +11,25 @@ using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed;
|
using BuildXL.Cache.ContentStore.Distributed;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.Extensions;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Cache.ContentStore.UtilitiesCore;
|
||||||
|
using BuildXL.Cache.ContentStore.Utils;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Cache.Host.Service;
|
|
||||||
using BuildXL.Utilities;
|
using BuildXL.Utilities;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Extensions;
|
using BuildXL.Utilities.Collections;
|
||||||
|
using BuildXL.Utilities.ParallelAlgorithms;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
||||||
using static BuildXL.Cache.Host.Service.DeploymentUtilities;
|
using static BuildXL.Cache.Host.Service.DeploymentUtilities;
|
||||||
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
||||||
using BuildXL.Utilities.ParallelAlgorithms;
|
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Text;
|
|
||||||
using BuildXL.Cache.ContentStore.UtilitiesCore;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using BuildXL.Utilities.Collections;
|
|
||||||
using System.Diagnostics.ContractsLight;
|
|
||||||
|
|
||||||
namespace BuildXL.Launcher.Server
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service used ensure deployments are uploaded to target storage accounts and provide manifest for with download urls and tools to launch
|
/// Service used ensure deployments are uploaded to target storage accounts and provide manifest for with download urls and tools to launch
|
||||||
|
|
|
@ -6,16 +6,13 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.Utilities;
|
using BuildXL.Cache.ContentStore.Distributed.Utilities;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
using BuildXL.Cache.ContentStore.Stores;
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,12 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using static BuildXL.Cache.Host.Configuration.DeploymentManifest;
|
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
|
@ -24,7 +17,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
public interface IDeploymentLauncherHost
|
public interface IDeploymentLauncherHost
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an unstarted process using the given start info
|
/// Creates an unstarted process using the given start info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ILauncherProcess CreateProcess(ProcessStartInfo info);
|
ILauncherProcess CreateProcess(ProcessStartInfo info);
|
||||||
|
|
||||||
|
@ -56,7 +49,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a launched system process
|
/// Represents a light-weight wrapper around launched system process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ILauncherProcess
|
public interface ILauncherProcess
|
||||||
{
|
{
|
||||||
|
@ -91,6 +84,40 @@ namespace BuildXL.Cache.Host.Service
|
||||||
bool HasExited { get; }
|
bool HasExited { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Represents a launched system process
|
||||||
|
///// </summary>
|
||||||
|
//public interface ILauncherProcess
|
||||||
|
//{
|
||||||
|
// /// <summary>
|
||||||
|
// /// Starts the process.
|
||||||
|
// /// </summary>
|
||||||
|
// BoolResult Start(OperationContext context);
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Stop the service gracefully and kill it if it won't shutdown on time.
|
||||||
|
// /// </summary>
|
||||||
|
// /// <remarks>
|
||||||
|
// /// If the shutdown is successful the result contains an exit code.
|
||||||
|
// /// </remarks>
|
||||||
|
// Task<Result<int>> StopAsync(OperationContext context, TimeSpan shutdownTimeout);
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// The id of the process.
|
||||||
|
// /// </summary>
|
||||||
|
// int Id { get; }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// The id of the service that this process represents.
|
||||||
|
// /// </summary>
|
||||||
|
// string ServiceId { get; }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Indicates if the process has exited.
|
||||||
|
// /// </summary>
|
||||||
|
// bool HasExited { get; }
|
||||||
|
//}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a tool deployed and launched by the <see cref="DeploymentLauncher"/>
|
/// Represents a tool deployed and launched by the <see cref="DeploymentLauncher"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -116,4 +143,4 @@ namespace BuildXL.Cache.Host.Service
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AbsolutePath DirectoryPath { get; }
|
AbsolutePath DirectoryPath { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
|
using BuildXL.Cache.ContentStore.Service;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Utilities.Tasks;
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Service
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapper around launched processed managed by <see cref="ServiceLifetimeManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class LauncherManagedProcess
|
||||||
|
{
|
||||||
|
private static readonly Tracer Tracer = new Tracer(nameof(LauncherManagedProcess));
|
||||||
|
|
||||||
|
private readonly ILauncherProcess _process;
|
||||||
|
private readonly ServiceLifetimeManager _lifetimeManager;
|
||||||
|
private readonly TaskSourceSlim<int> _processExitSource = TaskSourceSlim.Create<int>();
|
||||||
|
|
||||||
|
public LauncherManagedProcess(ILauncherProcess process, string serviceId, ServiceLifetimeManager lifetimeManager)
|
||||||
|
{
|
||||||
|
_process = process;
|
||||||
|
_lifetimeManager = lifetimeManager;
|
||||||
|
ServiceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public string ServiceId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an underlying process that this class manages lifetime of.
|
||||||
|
/// </summary>
|
||||||
|
public ILauncherProcess Process => _process;
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public int ProcessId => _process.Id;
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public bool HasExited => _process.HasExited;
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public BoolResult Start(OperationContext context)
|
||||||
|
{
|
||||||
|
return context.PerformOperation(
|
||||||
|
Tracer,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
_process.Start(context);
|
||||||
|
return Result.Success(_process.Id);
|
||||||
|
},
|
||||||
|
traceOperationStarted: true,
|
||||||
|
extraStartMessage: $"ServiceId={ServiceId}",
|
||||||
|
messageFactory: r => $"ProcessId={r.GetValueOrDefault(defaultValue: -1)}, ServiceId={ServiceId}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public Task<Result<int>> StopAsync(OperationContext context, TimeSpan shutdownTimeout)
|
||||||
|
{
|
||||||
|
bool alreadyExited = false;
|
||||||
|
return context.PerformOperationWithTimeoutAsync(
|
||||||
|
Tracer,
|
||||||
|
async nestedContext =>
|
||||||
|
{
|
||||||
|
if (HasExited)
|
||||||
|
{
|
||||||
|
alreadyExited = true;
|
||||||
|
return Result.Success(_process.ExitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminating the process after timeout if it won't shutdown gracefully.
|
||||||
|
using var registration = nestedContext.Token.Register(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// It is important to pass 'context' and not 'nestedContext',
|
||||||
|
// because 'nestedContext' will be canceled at a time we call TerminateService.
|
||||||
|
Kill(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.PerformOperationAsync(
|
||||||
|
Tracer,
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await _lifetimeManager.ShutdownServiceAsync(nestedContext, ServiceId);
|
||||||
|
return BoolResult.Success;
|
||||||
|
},
|
||||||
|
caller: "GracefulShutdownService").IgnoreFailure();
|
||||||
|
|
||||||
|
return await _processExitSource.Task;
|
||||||
|
},
|
||||||
|
timeout: shutdownTimeout,
|
||||||
|
extraStartMessage: $"ProcessId={ProcessId}, ServiceId={ServiceId}",
|
||||||
|
extraEndMessage: r => $"ProcessId={ProcessId}, ServiceId={ServiceId}, ExitCode={r.GetValueOrDefault(-1)}, AlreadyExited={alreadyExited}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Kill(OperationContext context)
|
||||||
|
{
|
||||||
|
context
|
||||||
|
.WithoutCancellationToken() // Not using the cancellation token from the context.
|
||||||
|
.PerformOperation(
|
||||||
|
Tracer,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// Using Result<string> for tracing purposes.
|
||||||
|
if (HasExited)
|
||||||
|
{
|
||||||
|
OnExited(context, "TerminateServiceAlreadyExited");
|
||||||
|
return Result.Success("AlreadyExited");
|
||||||
|
}
|
||||||
|
|
||||||
|
_process.Kill(context);
|
||||||
|
return Result.Success("ProcessKilled");
|
||||||
|
|
||||||
|
},
|
||||||
|
extraStartMessage: $"ProcessId={ProcessId}, ServiceId={ServiceId}",
|
||||||
|
messageFactory: r => $"ProcessId={ProcessId}, ServiceId={ServiceId}, {r}")
|
||||||
|
.IgnoreFailure();
|
||||||
|
|
||||||
|
// Intentionally trying to set the result that indicates the cancellation after PerformOperation call that will never throw.
|
||||||
|
_processExitSource.TrySetResult(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExited(OperationContext context, string trigger)
|
||||||
|
{
|
||||||
|
// It is important to disable the cancellation here because in some cases the token associated
|
||||||
|
// with the context can be set.
|
||||||
|
// But the operation that we do here is very fast and we use context for tracing purposes only.
|
||||||
|
context
|
||||||
|
.WithoutCancellationToken()
|
||||||
|
.PerformOperation(
|
||||||
|
Tracer,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
_processExitSource.TrySetResult(_process.ExitCode);
|
||||||
|
return Result.Success(_process.ExitCode.ToString());
|
||||||
|
},
|
||||||
|
caller: "ServiceExited",
|
||||||
|
messageFactory: r => $"ProcessId={ProcessId}, ServiceId={ServiceId}, ExitCode={r.GetValueOrDefault(string.Empty)} Trigger={trigger}")
|
||||||
|
.IgnoreFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Utilities.Collections;
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Service
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A lightweight wrapper around launched process.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class LauncherProcess : ILauncherProcess
|
||||||
|
{
|
||||||
|
private static readonly Tracer _tracer = new Tracer(nameof(LauncherProcess));
|
||||||
|
|
||||||
|
private bool _started;
|
||||||
|
private readonly Process _process;
|
||||||
|
|
||||||
|
public LauncherProcess(ProcessStartInfo info)
|
||||||
|
{
|
||||||
|
info.RedirectStandardOutput = true;
|
||||||
|
info.RedirectStandardError = true;
|
||||||
|
|
||||||
|
_process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = info,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_process.Exited += (sender, e) => Exited?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int ExitCode => _process.ExitCode;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Id => _started ? _process.Id : -1;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasExited => _process.HasExited;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action Exited;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Kill(OperationContext context)
|
||||||
|
{
|
||||||
|
_process.Kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Start(OperationContext context)
|
||||||
|
{
|
||||||
|
// Using nagle queues to "batch" messages together and to avoid writing them to the logs one by one.
|
||||||
|
var outputMessagesNagleQueue = NagleQueue<string>.Create(
|
||||||
|
messages =>
|
||||||
|
{
|
||||||
|
_tracer.Debug(context, $"Service Output: {string.Join(Environment.NewLine, messages)}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
maxDegreeOfParallelism: 1, interval: TimeSpan.FromSeconds(1), batchSize: 1024);
|
||||||
|
|
||||||
|
var errorMessagesNagleQueue = NagleQueue<string>.Create(
|
||||||
|
messages =>
|
||||||
|
{
|
||||||
|
_tracer.Error(context, $"Service Error: {string.Join(Environment.NewLine, messages)}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
maxDegreeOfParallelism: 1, interval: TimeSpan.FromSeconds(1), batchSize: 1024);
|
||||||
|
|
||||||
|
_process.OutputDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(e.Data))
|
||||||
|
{
|
||||||
|
outputMessagesNagleQueue.Enqueue(e.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_process.ErrorDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(e.Data))
|
||||||
|
{
|
||||||
|
errorMessagesNagleQueue.Enqueue(e.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_process.Exited += (sender, args) =>
|
||||||
|
{
|
||||||
|
// Dispose will drain all the existing items from the message queues.
|
||||||
|
outputMessagesNagleQueue.Dispose();
|
||||||
|
errorMessagesNagleQueue.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
_process.Start();
|
||||||
|
|
||||||
|
_process.BeginOutputReadLine();
|
||||||
|
_process.BeginErrorReadLine();
|
||||||
|
_started = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
|
using BuildXL.Cache.ContentStore.Service;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Cache.ContentStore.UtilitiesCore.Internal;
|
||||||
|
using BuildXL.Cache.ContentStore.Utils;
|
||||||
|
using BuildXL.Cache.Host.Configuration;
|
||||||
|
using BuildXL.Cache.Host.Service.Internal;
|
||||||
|
using BuildXL.Utilities.ConfigurationHelpers;
|
||||||
|
// The next using is needed in order to create ProcessStartInfo.EnvironmentVariables with collection initialization syntax.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Service.OutOfProc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A helper class that "wraps" an out-of-proc cache service.
|
||||||
|
/// </summary>
|
||||||
|
public class CacheServiceWrapper : StartupShutdownBase
|
||||||
|
{
|
||||||
|
private readonly CacheServiceWrapperConfiguration _configuration;
|
||||||
|
private readonly ServiceLifetimeManager _serviceLifetimeManager;
|
||||||
|
private readonly RetrievedSecrets _secrets;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override Tracer Tracer { get; } = new Tracer(nameof(CacheServiceWrapper));
|
||||||
|
|
||||||
|
private LauncherManagedProcess? _runningProcess;
|
||||||
|
|
||||||
|
public CacheServiceWrapper(CacheServiceWrapperConfiguration configuration, ServiceLifetimeManager serviceLifetimeManager, RetrievedSecrets secrets)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_serviceLifetimeManager = serviceLifetimeManager;
|
||||||
|
_secrets = secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates <see cref="CacheServiceWrapper"/> from <paramref name="configuration"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<Result<CacheServiceWrapper>> CreateAsync(DistributedCacheServiceArguments configuration)
|
||||||
|
{
|
||||||
|
// Validating the cache configuration
|
||||||
|
|
||||||
|
var wrapperConfiguration = tryCreateConfiguration(configuration);
|
||||||
|
if (!wrapperConfiguration.Succeeded)
|
||||||
|
{
|
||||||
|
const string BaseError = "Can't start cache service as a separate process because";
|
||||||
|
return Result.FromErrorMessage<CacheServiceWrapper>($"{BaseError} {wrapperConfiguration.ErrorMessage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtaining the secrets and creating a wrapper.
|
||||||
|
var serviceLifetimeManager = new ServiceLifetimeManager(wrapperConfiguration.Value.WorkingDirectory, wrapperConfiguration.Value.ServiceLifetimePollingInterval);
|
||||||
|
var secretsRetriever = new DistributedCacheSecretRetriever(configuration);
|
||||||
|
|
||||||
|
var secrets = await secretsRetriever.TryRetrieveSecretsAsync();
|
||||||
|
if (!secrets.Succeeded)
|
||||||
|
{
|
||||||
|
return new Result<CacheServiceWrapper>(secrets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success(new CacheServiceWrapper(wrapperConfiguration.Value, serviceLifetimeManager, secrets.Value));
|
||||||
|
|
||||||
|
// Creating final configuration based on provided settings and by using reasonable defaults.
|
||||||
|
static Result<CacheServiceWrapperConfiguration> tryCreateConfiguration(DistributedCacheServiceArguments configuration)
|
||||||
|
{
|
||||||
|
var outOfProcSettings = configuration.Configuration.DistributedContentSettings.OutOfProcCacheSettings;
|
||||||
|
|
||||||
|
if (outOfProcSettings is null)
|
||||||
|
{
|
||||||
|
return Result.FromErrorMessage<CacheServiceWrapperConfiguration>($"{nameof(configuration.Configuration.DistributedContentSettings.OutOfProcCacheSettings)} should not be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outOfProcSettings.Executable is null)
|
||||||
|
{
|
||||||
|
return Result.FromErrorMessage<CacheServiceWrapperConfiguration>($"{nameof(outOfProcSettings.Executable)} is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(outOfProcSettings.Executable))
|
||||||
|
{
|
||||||
|
// This is not a bullet proof check, but if the executable is not found we should not even trying to create an out of proc cache service.
|
||||||
|
return Result.FromErrorMessage<CacheServiceWrapperConfiguration>($"the executable is not found at '{outOfProcSettings.Executable}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outOfProcSettings.CacheConfigPath is null)
|
||||||
|
{
|
||||||
|
return Result.FromErrorMessage<CacheServiceWrapperConfiguration>($"{nameof(outOfProcSettings.CacheConfigPath)} is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(outOfProcSettings.CacheConfigPath))
|
||||||
|
{
|
||||||
|
// This is not a bullet proof check, but if the executable is not found we should not even trying to create an out of proc cache service.
|
||||||
|
return Result.FromErrorMessage<CacheServiceWrapperConfiguration>($"the cache configuration is not found at '{outOfProcSettings.CacheConfigPath}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next layout should be in sync with CloudBuild.
|
||||||
|
AbsolutePath executable = getExecutingPath() / outOfProcSettings.Executable;
|
||||||
|
var workingDirectory = getRootPath(configuration.Configuration);
|
||||||
|
|
||||||
|
var hostParameters = HostParameters.FromTelemetryProvider(configuration.TelemetryFieldsProvider);
|
||||||
|
|
||||||
|
var resultingConfiguration = new CacheServiceWrapperConfiguration(
|
||||||
|
serviceId: "OutOfProcCache",
|
||||||
|
executable: executable,
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
hostParameters: hostParameters,
|
||||||
|
cacheConfigPath: new AbsolutePath(outOfProcSettings.CacheConfigPath),
|
||||||
|
// DataRootPath is set in CloudBuild and we need to propagate this configuration to the launched process.
|
||||||
|
dataRootPath: new AbsolutePath(configuration.Configuration.DataRootPath));
|
||||||
|
|
||||||
|
outOfProcSettings.ServiceLifetimePollingIntervalSeconds.ApplyIfNotNull(v => resultingConfiguration.ServiceLifetimePollingInterval = TimeSpan.FromSeconds(v));
|
||||||
|
outOfProcSettings.ShutdownTimeoutSeconds.ApplyIfNotNull(v => resultingConfiguration.ShutdownTimeout = TimeSpan.FromSeconds(v));
|
||||||
|
|
||||||
|
return resultingConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AbsolutePath getRootPath(DistributedCacheServiceConfiguration configuration) => configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName);
|
||||||
|
|
||||||
|
static AbsolutePath getExecutingPath() => new AbsolutePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override Task<BoolResult> StartupCoreAsync(OperationContext context)
|
||||||
|
{
|
||||||
|
var executablePath = _configuration.Executable.Path;
|
||||||
|
if (!File.Exists(executablePath))
|
||||||
|
{
|
||||||
|
return Task.FromResult(new BoolResult($"Executable '{executablePath}' does not exist."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to specify through the arguments what type of secrets provider to use.
|
||||||
|
// Currently we serialize all the secrets as a single string.
|
||||||
|
var secretsProviderKind = CrossProcessSecretsCommunicationKind.EnvironmentSingleEntry;
|
||||||
|
|
||||||
|
var argumentsList = new []
|
||||||
|
{
|
||||||
|
"CacheService",
|
||||||
|
// If cacheConfigPath is null the validation will fail and the process won't be started.
|
||||||
|
"--cacheConfigurationPath", _configuration.CacheConfigPath?.Path ?? string.Empty,
|
||||||
|
// This is not a standalone cache service, it is controlled by ServiceLifetimeManager.
|
||||||
|
"--standalone", "false",
|
||||||
|
"--secretsProviderKind", secretsProviderKind.ToString(),
|
||||||
|
"--dataRootPath", _configuration.DataRootPath.ToString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var environment = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
_configuration.HostParameters.ToEnvironment(),
|
||||||
|
_serviceLifetimeManager.GetDeployedInterruptableServiceVariables(_configuration.ServiceId),
|
||||||
|
|
||||||
|
// Passing the secrets via environment variable in a single value.
|
||||||
|
// This may be problematic if the serialized size will exceed some size (like 32K), but
|
||||||
|
// it should not be the case for now.
|
||||||
|
{ RetrievedSecretsSerializer.SerializedSecretsKeyName, RetrievedSecretsSerializer.Serialize(_secrets) },
|
||||||
|
getDotNetEnvironmentVariables()
|
||||||
|
};
|
||||||
|
|
||||||
|
var process = new LauncherProcess(
|
||||||
|
new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
UseShellExecute = false,
|
||||||
|
FileName = executablePath,
|
||||||
|
Arguments = string.Join(" ", argumentsList),
|
||||||
|
// A strange cast to a nullable dictionary is needed to avoid warnings from the C# compiler.
|
||||||
|
Environment = { (IDictionary<string, string?>)environment },
|
||||||
|
});
|
||||||
|
|
||||||
|
_runningProcess = new LauncherManagedProcess(process, _configuration.ServiceId, _serviceLifetimeManager);
|
||||||
|
Tracer.Info(context, "Starting out-of-proc cache process.");
|
||||||
|
var result = _runningProcess.Start(context);
|
||||||
|
Tracer.Info(context, $"Started out-of-proc cache process (Id={process.Id}). Result: {result}.");
|
||||||
|
return Task.FromResult(result);
|
||||||
|
|
||||||
|
static IDictionary<string, string> getDotNetEnvironmentVariables()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["COMPlus_GCCpuGroup"] = "1",
|
||||||
|
["DOTNET_GCCpuGroup"] = "1", // This is the same option that is used by .net6+
|
||||||
|
["COMPlus_Thread_UseAllCpuGroups"] = "1",
|
||||||
|
["DOTNET_Thread_UseAllCpuGroups"] = "1", // This is the same option that is used by .net6+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task<BoolResult> ShutdownCoreAsync(OperationContext context)
|
||||||
|
{
|
||||||
|
if (_runningProcess != null)
|
||||||
|
{
|
||||||
|
return await _runningProcess.StopAsync(context, _configuration.ShutdownTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BoolResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
|
using BuildXL.Cache.ContentStore.Service;
|
||||||
|
using BuildXL.Cache.Host.Configuration;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Service.OutOfProc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration class used by <see cref="CacheServiceWrapper"/>.
|
||||||
|
/// </summary>
|
||||||
|
public record class CacheServiceWrapperConfiguration
|
||||||
|
{
|
||||||
|
/// <nodoc />
|
||||||
|
public CacheServiceWrapperConfiguration(
|
||||||
|
string serviceId,
|
||||||
|
AbsolutePath executable,
|
||||||
|
AbsolutePath workingDirectory,
|
||||||
|
HostParameters hostParameters,
|
||||||
|
AbsolutePath cacheConfigPath,
|
||||||
|
AbsolutePath dataRootPath)
|
||||||
|
{
|
||||||
|
ServiceId = serviceId;
|
||||||
|
Executable = executable;
|
||||||
|
WorkingDirectory = workingDirectory;
|
||||||
|
HostParameters = hostParameters;
|
||||||
|
CacheConfigPath = cacheConfigPath;
|
||||||
|
DataRootPath = dataRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The identifier used to identify the service for service lifetime management and interruption
|
||||||
|
/// </summary>
|
||||||
|
public string ServiceId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the executable used when launching the tool relative to the layout root
|
||||||
|
/// </summary>
|
||||||
|
public AbsolutePath Executable { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A working directory used for a process's lifetime tracking and other.
|
||||||
|
/// </summary>
|
||||||
|
public AbsolutePath WorkingDirectory { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters of the running machine like Stamp, Region etc.
|
||||||
|
/// </summary>
|
||||||
|
public HostParameters HostParameters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A path to the cache configuration (CacheConfiguration.json) file that the child process will use.
|
||||||
|
/// </summary>
|
||||||
|
public AbsolutePath CacheConfigPath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A root of the data directory (CloudBuild sets this property for the in-proc-mode).
|
||||||
|
/// </summary>
|
||||||
|
public AbsolutePath DataRootPath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The polling interval of the <see cref="ServiceLifetimeManager"/> used for lifetime tracking of a launched process.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan ServiceLifetimePollingInterval { get; set; } = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time to wait for service to shutdown before terminating the process
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When this functor is present, and assuming the cache replaces the host's logger with its own, it is
|
/// When this functor is present, and assuming the cache replaces the host's logger with its own, it is
|
||||||
/// expected to buid <see cref="Copier"/> and <see cref="CopyRequester"/>.
|
/// expected to build <see cref="Copier"/> and <see cref="CopyRequester"/>.
|
||||||
///
|
///
|
||||||
/// This is done this way because constructing those elements requires access to an <see cref="ILogger"/>,
|
/// This is done this way because constructing those elements requires access to an <see cref="ILogger"/>,
|
||||||
/// which will be replaced cache-side.
|
/// which will be replaced cache-side.
|
||||||
|
|
|
@ -109,7 +109,8 @@ namespace BuildXL.Cache.Host.Service
|
||||||
var context = new Context(arguments.Logger);
|
var context = new Context(arguments.Logger);
|
||||||
var operationContext = new OperationContext(context, arguments.Cancellation);
|
var operationContext = new OperationContext(context, arguments.Cancellation);
|
||||||
|
|
||||||
InitializeActivityTrackerIfNeeded(context, arguments.Configuration.DistributedContentSettings);
|
var distributedSettings = arguments.Configuration.DistributedContentSettings;
|
||||||
|
InitializeActivityTrackerIfNeeded(context, distributedSettings);
|
||||||
|
|
||||||
AdjustCopyInfrastructure(arguments);
|
AdjustCopyInfrastructure(arguments);
|
||||||
|
|
||||||
|
@ -120,7 +121,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
// Technically, this method doesn't own the file copier, but no one actually owns it.
|
// Technically, this method doesn't own the file copier, but no one actually owns it.
|
||||||
// So to clean up the resources (and print some stats) we dispose it here.
|
// So to clean up the resources (and print some stats) we dispose it here.
|
||||||
using (arguments.Copier as IDisposable)
|
using (arguments.Copier as IDisposable)
|
||||||
using (var server = factory.Create())
|
using (var server = await factory.CreateAsync(operationContext))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -130,7 +131,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
throw new CacheException(startupResult.ToString());
|
throw new CacheException(startupResult.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
await ReportServiceStartedAsync(operationContext, server, host);
|
await ReportServiceStartedAsync(operationContext, server, host, distributedSettings);
|
||||||
using var cancellationAwaiter = arguments.Cancellation.ToAwaitable();
|
using var cancellationAwaiter = arguments.Cancellation.ToAwaitable();
|
||||||
await cancellationAwaiter.CompletionTask;
|
await cancellationAwaiter.CompletionTask;
|
||||||
await ReportShuttingDownServiceAsync(operationContext, host);
|
await ReportShuttingDownServiceAsync(operationContext, host);
|
||||||
|
@ -142,7 +143,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
var timeoutInMinutes = arguments.Configuration?.DistributedContentSettings?.MaxShutdownDurationInMinutes ?? 5;
|
var timeoutInMinutes = distributedSettings?.MaxShutdownDurationInMinutes ?? 5;
|
||||||
var result = await server
|
var result = await server
|
||||||
.ShutdownAsync(context)
|
.ShutdownAsync(context)
|
||||||
.WithTimeoutAsync("Server shutdown", TimeSpan.FromMinutes(timeoutInMinutes));
|
.WithTimeoutAsync("Server shutdown", TimeSpan.FromMinutes(timeoutInMinutes));
|
||||||
|
@ -187,12 +188,16 @@ namespace BuildXL.Cache.Host.Service
|
||||||
private static async Task ReportServiceStartedAsync(
|
private static async Task ReportServiceStartedAsync(
|
||||||
OperationContext context,
|
OperationContext context,
|
||||||
StartupShutdownSlimBase server,
|
StartupShutdownSlimBase server,
|
||||||
IDistributedCacheServiceHost host)
|
IDistributedCacheServiceHost host,
|
||||||
|
DistributedContentSettings distributedContentSettings)
|
||||||
{
|
{
|
||||||
LifetimeTracker.ServiceStarted(context);
|
LifetimeTracker.ServiceStarted(context);
|
||||||
host.OnStartedService();
|
host.OnStartedService();
|
||||||
|
|
||||||
if (host is IDistributedCacheServiceHostInternal hostInternal
|
if (
|
||||||
|
// Don't need to call the following callback for out-of-proc cache
|
||||||
|
distributedContentSettings.OutOfProcCacheSettings is null &&
|
||||||
|
host is IDistributedCacheServiceHostInternal hostInternal
|
||||||
&& server is IServicesProvider sp
|
&& server is IServicesProvider sp
|
||||||
&& sp.TryGetService<ICacheServerServices>(out var services))
|
&& sp.TryGetService<ICacheServerServices>(out var services))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
|
|
|
@ -8,14 +8,19 @@ using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service
|
namespace BuildXL.Cache.Host.Service
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains all the secrets returned by <see cref="ISecretsProvider.RetrieveSecretsAsync"/>
|
||||||
|
/// </summary>
|
||||||
|
public record RetrievedSecrets(IReadOnlyDictionary<string, Secret> Secrets);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to provide secrets
|
/// Used to provide secrets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISecretsProvider
|
public interface ISecretsProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves secrets from key vault
|
/// Retrieves secrets from key vault or some other provider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<Dictionary<string, Secret>> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token);
|
Task<RetrievedSecrets> RetrieveSecretsAsync(List<RetrieveSecretsRequest> requests, CancellationToken token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.ContractsLight;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
using BuildXL.Cache.ContentStore.Distributed.NuCache;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.FileSystem;
|
using BuildXL.Cache.ContentStore.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
|
@ -25,8 +26,11 @@ using BuildXL.Cache.MemoizationStore.Vsts;
|
||||||
using BuildXL.Cache.MemoizationStore.Service;
|
using BuildXL.Cache.MemoizationStore.Service;
|
||||||
using BuildXL.Cache.MemoizationStore.Sessions;
|
using BuildXL.Cache.MemoizationStore.Sessions;
|
||||||
using BuildXL.Cache.MemoizationStore.Stores;
|
using BuildXL.Cache.MemoizationStore.Stores;
|
||||||
using static BuildXL.Utilities.ConfigurationHelper;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
|
using BuildXL.Cache.Host.Service.OutOfProc;
|
||||||
|
using BuildXL.Utilities.ConfigurationHelpers;
|
||||||
|
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service.Internal
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
{
|
{
|
||||||
|
@ -36,6 +40,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
/// <remarks>Marked as public because it is used externally.</remarks>
|
/// <remarks>Marked as public because it is used externally.</remarks>
|
||||||
public class CacheServerFactory
|
public class CacheServerFactory
|
||||||
{
|
{
|
||||||
|
private static readonly Tracer _tracer = new Tracer(nameof(CacheServerFactory));
|
||||||
private readonly IAbsFileSystem _fileSystem;
|
private readonly IAbsFileSystem _fileSystem;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly DistributedCacheServiceArguments _arguments;
|
private readonly DistributedCacheServiceArguments _arguments;
|
||||||
|
@ -50,20 +55,52 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
_fileSystem = new PassThroughFileSystem(_logger);
|
_fileSystem = new PassThroughFileSystem(_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StartupShutdownBase Create()
|
/// <summary>
|
||||||
|
/// Creates a cache server.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Currently it can be one of the following:
|
||||||
|
/// * Launcher that will download configured bits and start them.
|
||||||
|
/// * Out-of-proc launcher that will start the current bits in a separate process.
|
||||||
|
/// * In-proc distributed cache service.
|
||||||
|
/// * In-proc local cache service.
|
||||||
|
/// </remarks>
|
||||||
|
public async Task<StartupShutdownBase> CreateAsync(OperationContext operationContext)
|
||||||
{
|
{
|
||||||
var cacheConfig = _arguments.Configuration;
|
var cacheConfig = _arguments.Configuration;
|
||||||
if (TryCreateLauncherIfSpecified(cacheConfig, out var launcher))
|
|
||||||
|
if (IsLauncherEnabled(cacheConfig))
|
||||||
{
|
{
|
||||||
return launcher;
|
_tracer.Debug(operationContext, $"Creating a launcher.");
|
||||||
|
return await CreateLauncherAsync(cacheConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheConfig.LocalCasSettings = cacheConfig.LocalCasSettings.FilterUnsupportedNamedCaches(_arguments.HostInfo.Capabilities, _logger);
|
|
||||||
|
|
||||||
var distributedSettings = cacheConfig.DistributedContentSettings;
|
var distributedSettings = cacheConfig.DistributedContentSettings;
|
||||||
|
|
||||||
|
if (IsOutOfProcCacheEnabled(cacheConfig))
|
||||||
|
{
|
||||||
|
_tracer.Debug(operationContext, $"Creating an out-of-proc cache service.");
|
||||||
|
var outOfProcCache = await CacheServiceWrapper.CreateAsync(_arguments);
|
||||||
|
|
||||||
|
if (outOfProcCache.Succeeded)
|
||||||
|
{
|
||||||
|
return outOfProcCache.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracing and falling back to the in-proc cache
|
||||||
|
_tracer.Error(operationContext, $"Failed to create out of proc cache: {outOfProcCache}. Using in-proc cache instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_tracer.Debug(operationContext, "Creating an in-proc cache service.");
|
||||||
|
cacheConfig.LocalCasSettings = cacheConfig.LocalCasSettings.FilterUnsupportedNamedCaches(_arguments.HostInfo.Capabilities, _logger);
|
||||||
|
|
||||||
var isLocal = distributedSettings == null || !distributedSettings.IsDistributedContentEnabled;
|
var isLocal = distributedSettings == null || !distributedSettings.IsDistributedContentEnabled;
|
||||||
|
|
||||||
LogManager.Update(distributedSettings.LogManager);
|
if (distributedSettings is not null)
|
||||||
|
{
|
||||||
|
LogManager.Update(distributedSettings.LogManager);
|
||||||
|
}
|
||||||
|
|
||||||
var serviceConfiguration = CreateServiceConfiguration(
|
var serviceConfiguration = CreateServiceConfiguration(
|
||||||
_logger,
|
_logger,
|
||||||
_fileSystem,
|
_fileSystem,
|
||||||
|
@ -93,29 +130,22 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryCreateLauncherIfSpecified(DistributedCacheServiceConfiguration cacheConfig, out DeploymentLauncher launcher)
|
private bool IsLauncherEnabled(DistributedCacheServiceConfiguration cacheConfig) =>
|
||||||
|
cacheConfig.DistributedContentSettings.LauncherSettings != null;
|
||||||
|
|
||||||
|
private bool IsOutOfProcCacheEnabled(DistributedCacheServiceConfiguration cacheConfig) =>
|
||||||
|
cacheConfig.DistributedContentSettings.RunCacheOutOfProc == true;
|
||||||
|
|
||||||
|
private async Task<DeploymentLauncher> CreateLauncherAsync(DistributedCacheServiceConfiguration cacheConfig)
|
||||||
{
|
{
|
||||||
var launcherSettings = cacheConfig.DistributedContentSettings.LauncherSettings;
|
var launcherSettings = cacheConfig.DistributedContentSettings.LauncherSettings;
|
||||||
if (launcherSettings != null)
|
Contract.Assert(launcherSettings is not null);
|
||||||
{
|
|
||||||
var deploymentParams = launcherSettings.DeploymentParameters;
|
|
||||||
deploymentParams.Stamp ??= _arguments.TelemetryFieldsProvider?.Stamp;
|
|
||||||
deploymentParams.Machine ??= Environment.MachineName;
|
|
||||||
deploymentParams.MachineFunction ??= _arguments.TelemetryFieldsProvider?.APMachineFunction;
|
|
||||||
deploymentParams.Ring ??= _arguments.TelemetryFieldsProvider?.Ring;
|
|
||||||
|
|
||||||
deploymentParams.AuthorizationSecret ??= _arguments.Host.GetPlainSecretAsync(deploymentParams.AuthorizationSecretName, _arguments.Cancellation).GetAwaiter().GetResult();
|
var deploymentParams = launcherSettings.DeploymentParameters;
|
||||||
|
deploymentParams.ApplyFromTelemetryProviderIfNeeded(_arguments.TelemetryFieldsProvider);
|
||||||
|
deploymentParams.AuthorizationSecret ??= await _arguments.Host.GetPlainSecretAsync(deploymentParams.AuthorizationSecretName, _arguments.Cancellation);
|
||||||
|
|
||||||
launcher = new DeploymentLauncher(
|
return new DeploymentLauncher(launcherSettings, _fileSystem);
|
||||||
launcherSettings,
|
|
||||||
_fileSystem);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
launcher = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StartupShutdownBase CreateLocalServer(LocalServerConfiguration localServerConfiguration, DistributedContentSettings distributedSettings = null)
|
private StartupShutdownBase CreateLocalServer(LocalServerConfiguration localServerConfiguration, DistributedContentSettings distributedSettings = null)
|
||||||
|
@ -302,18 +332,19 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
|
|
||||||
var localContentServerConfiguration = new LocalServerConfiguration(serviceConfiguration);
|
var localContentServerConfiguration = new LocalServerConfiguration(serviceConfiguration);
|
||||||
|
|
||||||
ApplyIfNotNull(localCasServiceSettings.UnusedSessionTimeoutMinutes, value => localContentServerConfiguration.UnusedSessionTimeout = TimeSpan.FromMinutes(value));
|
localCasServiceSettings.UnusedSessionTimeoutMinutes.ApplyIfNotNull(value => localContentServerConfiguration.UnusedSessionTimeout = TimeSpan.FromMinutes(value));
|
||||||
ApplyIfNotNull(localCasServiceSettings.UnusedSessionHeartbeatTimeoutMinutes, value => localContentServerConfiguration.UnusedSessionHeartbeatTimeout = TimeSpan.FromMinutes(value));
|
localCasServiceSettings.UnusedSessionHeartbeatTimeoutMinutes.ApplyIfNotNull(value => localContentServerConfiguration.UnusedSessionHeartbeatTimeout = TimeSpan.FromMinutes(value));
|
||||||
ApplyIfNotNull(localCasServiceSettings.GrpcCoreServerOptions, value => localContentServerConfiguration.GrpcCoreServerOptions = value);
|
localCasServiceSettings.GrpcCoreServerOptions.ApplyIfNotNull(value => localContentServerConfiguration.GrpcCoreServerOptions = value);
|
||||||
ApplyIfNotNull(localCasServiceSettings.GrpcEnvironmentOptions, value => localContentServerConfiguration.GrpcEnvironmentOptions = value);
|
localCasServiceSettings.GrpcEnvironmentOptions.ApplyIfNotNull(value => localContentServerConfiguration.GrpcEnvironmentOptions = value);
|
||||||
ApplyIfNotNull(localCasServiceSettings.DoNotShutdownSessionsInUse, value => localContentServerConfiguration.DoNotShutdownSessionsInUse = value);
|
localCasServiceSettings.DoNotShutdownSessionsInUse.ApplyIfNotNull(value => localContentServerConfiguration.DoNotShutdownSessionsInUse = value);
|
||||||
|
|
||||||
ApplyIfNotNull(distributedSettings?.UseUnsafeByteStringConstruction, value =>
|
(distributedSettings?.UseUnsafeByteStringConstruction).ApplyIfNotNull(
|
||||||
|
value =>
|
||||||
{
|
{
|
||||||
GrpcExtensions.UnsafeByteStringOptimizations = value;
|
GrpcExtensions.UnsafeByteStringOptimizations = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
ApplyIfNotNull(distributedSettings?.ShutdownEvictionBeforeHibernation, value => localContentServerConfiguration.ShutdownEvictionBeforeHibernation = value);
|
(distributedSettings?.ShutdownEvictionBeforeHibernation).ApplyIfNotNull(value => localContentServerConfiguration.ShutdownEvictionBeforeHibernation = value);
|
||||||
|
|
||||||
return localContentServerConfiguration;
|
return localContentServerConfiguration;
|
||||||
}
|
}
|
||||||
|
@ -364,7 +395,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
logIncrementalStatsCounterNames: distributedSettings?.IncrementalStatisticsCounterNames,
|
logIncrementalStatsCounterNames: distributedSettings?.IncrementalStatisticsCounterNames,
|
||||||
asyncSessionShutdownTimeout: distributedSettings?.AsyncSessionShutdownTimeout);
|
asyncSessionShutdownTimeout: distributedSettings?.AsyncSessionShutdownTimeout);
|
||||||
|
|
||||||
ApplyIfNotNull(distributedSettings?.TraceServiceGrpcOperations, v => result.TraceGrpcOperation = v);
|
distributedSettings?.TraceServiceGrpcOperations.ApplyIfNotNull(v => result.TraceGrpcOperation = v);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@ using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Stores;
|
using BuildXL.Cache.ContentStore.Interfaces.Stores;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
using BuildXL.Cache.ContentStore.Stores;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service.Internal
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
{
|
{
|
||||||
public class ContentStoreFactory
|
public class ContentStoreFactory
|
||||||
|
|
|
@ -8,12 +8,14 @@ using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
using BuildXL.Cache.ContentStore.Utils;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service.Internal
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -22,35 +24,43 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
public class DistributedCacheSecretRetriever
|
public class DistributedCacheSecretRetriever
|
||||||
{
|
{
|
||||||
private readonly DistributedContentSettings _distributedSettings;
|
private readonly DistributedContentSettings _distributedSettings;
|
||||||
|
private readonly AzureBlobStorageLogPublicConfiguration? _loggingConfiguration;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IDistributedCacheServiceHost _host;
|
private readonly IDistributedCacheServiceHost _host;
|
||||||
|
|
||||||
private readonly Lazy<Task<(Dictionary<string, Secret>, string)>> _secrets;
|
private readonly Lazy<Task<Result<RetrievedSecrets>>> _secrets;
|
||||||
|
|
||||||
/// <nodoc />
|
/// <nodoc />
|
||||||
public DistributedCacheSecretRetriever(DistributedCacheServiceArguments arguments)
|
public DistributedCacheSecretRetriever(DistributedCacheServiceArguments arguments)
|
||||||
{
|
{
|
||||||
_distributedSettings = arguments.Configuration.DistributedContentSettings;
|
_distributedSettings = arguments.Configuration.DistributedContentSettings;
|
||||||
|
_loggingConfiguration = arguments.LoggingSettings?.Configuration;
|
||||||
_logger = arguments.Logger;
|
_logger = arguments.Logger;
|
||||||
_host = arguments.Host;
|
_host = arguments.Host;
|
||||||
|
|
||||||
_secrets = new Lazy<Task<(Dictionary<string, Secret>, string)>>(TryGetSecretsAsync);
|
_secrets = new Lazy<Task<Result<RetrievedSecrets>>>(TryGetSecretsAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the secrets. Results will be cached so secrets are only computed the first time this is called.
|
/// Retrieves the secrets. Results will be cached so secrets are only computed the first time this is called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<(Dictionary<string, Secret>, string)> TryRetrieveSecretsAsync() => _secrets.Value;
|
public Task<Result<RetrievedSecrets>> TryRetrieveSecretsAsync() => _secrets.Value;
|
||||||
|
|
||||||
private async Task<(Dictionary<string, Secret>, string errors)> TryGetSecretsAsync()
|
private async Task<Result<RetrievedSecrets>> TryGetSecretsAsync()
|
||||||
{
|
{
|
||||||
var errorBuilder = new StringBuilder();
|
var errorBuilder = new StringBuilder();
|
||||||
|
|
||||||
var result = await impl();
|
var result = await impl();
|
||||||
|
|
||||||
return (result, errorBuilder.ToString());
|
if (result is null)
|
||||||
|
{
|
||||||
|
return Result.FromErrorMessage<RetrievedSecrets>(errorBuilder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
async Task<Dictionary<string, Secret>> impl()
|
return Result.Success(result);
|
||||||
|
|
||||||
|
async Task<RetrievedSecrets?> impl()
|
||||||
{
|
{
|
||||||
_logger.Debug(
|
_logger.Debug(
|
||||||
$"{nameof(_distributedSettings.EventHubSecretName)}: {_distributedSettings.EventHubSecretName}, " +
|
$"{nameof(_distributedSettings.EventHubSecretName)}: {_distributedSettings.EventHubSecretName}, " +
|
||||||
|
@ -78,8 +88,9 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var azureBlobStorageCredentialsKind = _distributedSettings.AzureBlobStorageUseSasTokens ? SecretKind.SasToken : SecretKind.PlainText;
|
retrieveSecretsRequests.AddRange(
|
||||||
retrieveSecretsRequests.AddRange(storageSecretNames.Select(secretName => new RetrieveSecretsRequest(secretName, azureBlobStorageCredentialsKind)));
|
storageSecretNames
|
||||||
|
.Select(tpl => new RetrieveSecretsRequest(tpl.secretName, tpl.useSasTokens ? SecretKind.SasToken : SecretKind.PlainText)));
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_distributedSettings.GlobalRedisSecretName))
|
if (string.IsNullOrEmpty(_distributedSettings.GlobalRedisSecretName))
|
||||||
{
|
{
|
||||||
|
@ -103,6 +114,8 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
|
|
||||||
addOptionalSecret(_distributedSettings.SecondaryGlobalRedisSecretName);
|
addOptionalSecret(_distributedSettings.SecondaryGlobalRedisSecretName);
|
||||||
addOptionalSecret(_distributedSettings.ContentMetadataRedisSecretName);
|
addOptionalSecret(_distributedSettings.ContentMetadataRedisSecretName);
|
||||||
|
|
||||||
|
var azureBlobStorageCredentialsKind = _distributedSettings.AzureBlobStorageUseSasTokens ? SecretKind.SasToken : SecretKind.PlainText;
|
||||||
addOptionalSecret(_distributedSettings.ContentMetadataBlobSecretName, azureBlobStorageCredentialsKind);
|
addOptionalSecret(_distributedSettings.ContentMetadataBlobSecretName, azureBlobStorageCredentialsKind);
|
||||||
|
|
||||||
// Ask the host for credentials
|
// Ask the host for credentials
|
||||||
|
@ -118,7 +131,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
// Validate requests match as expected
|
// Validate requests match as expected
|
||||||
foreach (var request in retrieveSecretsRequests)
|
foreach (var request in retrieveSecretsRequests)
|
||||||
{
|
{
|
||||||
if (secrets.TryGetValue(request.Name, out var secret))
|
if (secrets.Secrets.TryGetValue(request.Name, out var secret))
|
||||||
{
|
{
|
||||||
bool typeMatch = true;
|
bool typeMatch = true;
|
||||||
switch (request.Kind)
|
switch (request.Kind)
|
||||||
|
@ -143,7 +156,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
return secrets;
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool appendIfNull(object value, string propertyName)
|
bool appendIfNull(object? value, string propertyName)
|
||||||
{
|
{
|
||||||
if (value is null)
|
if (value is null)
|
||||||
{
|
{
|
||||||
|
@ -165,17 +178,23 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
deltaBackoff: TimeSpan.FromSeconds(settings.SecretsRetrievalDeltaBackoffSeconds));
|
deltaBackoff: TimeSpan.FromSeconds(settings.SecretsRetrievalDeltaBackoffSeconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetAzureStorageSecretNames(StringBuilder errorBuilder)
|
private List<(string secretName, bool useSasTokens)>? GetAzureStorageSecretNames(StringBuilder errorBuilder)
|
||||||
{
|
{
|
||||||
var secretNames = new List<string>();
|
bool useSasToken = _distributedSettings.AzureBlobStorageUseSasTokens;
|
||||||
|
var secretNames = new List<(string secretName, bool useSasTokens)>();
|
||||||
if (_distributedSettings.AzureStorageSecretName != null && !string.IsNullOrEmpty(_distributedSettings.AzureStorageSecretName))
|
if (_distributedSettings.AzureStorageSecretName != null && !string.IsNullOrEmpty(_distributedSettings.AzureStorageSecretName))
|
||||||
{
|
{
|
||||||
secretNames.Add(_distributedSettings.AzureStorageSecretName);
|
secretNames.Add((_distributedSettings.AzureStorageSecretName, useSasToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_distributedSettings.AzureStorageSecretNames != null && !_distributedSettings.AzureStorageSecretNames.Any(string.IsNullOrEmpty))
|
if (_distributedSettings.AzureStorageSecretNames != null && !_distributedSettings.AzureStorageSecretNames.Any(string.IsNullOrEmpty))
|
||||||
{
|
{
|
||||||
secretNames.AddRange(_distributedSettings.AzureStorageSecretNames);
|
secretNames.AddRange(_distributedSettings.AzureStorageSecretNames.Select(n => (n, useSasToken)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_loggingConfiguration?.SecretName != null)
|
||||||
|
{
|
||||||
|
secretNames.Add((_loggingConfiguration.SecretName, _loggingConfiguration.UseSasTokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secretNames.Count > 0)
|
if (secretNames.Count > 0)
|
||||||
|
|
|
@ -15,7 +15,6 @@ using BuildXL.Cache.ContentStore.Distributed.Redis;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.Sessions;
|
using BuildXL.Cache.ContentStore.Distributed.Sessions;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.Stores;
|
using BuildXL.Cache.ContentStore.Distributed.Stores;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Distributed;
|
using BuildXL.Cache.ContentStore.Interfaces.Distributed;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Extensions;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
|
@ -29,8 +28,6 @@ using BuildXL.Cache.ContentStore.UtilitiesCore;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Cache.MemoizationStore.Distributed.Stores;
|
using BuildXL.Cache.MemoizationStore.Distributed.Stores;
|
||||||
using BuildXL.Cache.MemoizationStore.Interfaces.Stores;
|
using BuildXL.Cache.MemoizationStore.Interfaces.Stores;
|
||||||
using BandwidthConfiguration = BuildXL.Cache.ContentStore.Distributed.BandwidthConfiguration;
|
|
||||||
using static BuildXL.Utilities.ConfigurationHelper;
|
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
using BuildXL.Cache.ContentStore.Utils;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.NuCache.CopyScheduling;
|
using BuildXL.Cache.ContentStore.Distributed.NuCache.CopyScheduling;
|
||||||
using ContentStore.Grpc;
|
using ContentStore.Grpc;
|
||||||
|
@ -41,6 +38,10 @@ using BuildXL.Cache.ContentStore.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.Services;
|
using BuildXL.Cache.ContentStore.Distributed.Services;
|
||||||
|
|
||||||
|
using AbsolutePath = BuildXL.Cache.ContentStore.Interfaces.FileSystem.AbsolutePath;
|
||||||
|
using BandwidthConfiguration = BuildXL.Cache.ContentStore.Distributed.BandwidthConfiguration;
|
||||||
|
using static BuildXL.Utilities.ConfigurationHelper;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service.Internal
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
{
|
{
|
||||||
public sealed class DistributedContentStoreFactory : IDistributedServicesSecrets
|
public sealed class DistributedContentStoreFactory : IDistributedServicesSecrets
|
||||||
|
@ -67,7 +68,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
|
|
||||||
public IReadOnlyList<ResolvedNamedCacheSettings> OrderedResolvedCacheSettings => _orderedResolvedCacheSettings;
|
public IReadOnlyList<ResolvedNamedCacheSettings> OrderedResolvedCacheSettings => _orderedResolvedCacheSettings;
|
||||||
private readonly List<ResolvedNamedCacheSettings> _orderedResolvedCacheSettings;
|
private readonly List<ResolvedNamedCacheSettings> _orderedResolvedCacheSettings;
|
||||||
private readonly Dictionary<string, Secret> _secrets;
|
private readonly RetrievedSecrets _secrets;
|
||||||
|
|
||||||
public RedisContentLocationStoreConfiguration RedisContentLocationStoreConfiguration { get; }
|
public RedisContentLocationStoreConfiguration RedisContentLocationStoreConfiguration { get; }
|
||||||
|
|
||||||
|
@ -82,14 +83,14 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
_fileSystem = arguments.FileSystem;
|
_fileSystem = arguments.FileSystem;
|
||||||
_secretRetriever = new DistributedCacheSecretRetriever(arguments);
|
_secretRetriever = new DistributedCacheSecretRetriever(arguments);
|
||||||
|
|
||||||
(var secrets, var errors) = _secretRetriever.TryRetrieveSecretsAsync().GetAwaiter().GetResult();
|
var secretsResult = _secretRetriever.TryRetrieveSecretsAsync().GetAwaiter().GetResult();
|
||||||
if (secrets == null)
|
if (!secretsResult.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.Error($"Unable to retrieve secrets. {errors}");
|
_logger.Error($"Unable to retrieve secrets. {secretsResult}");
|
||||||
secrets = new Dictionary<string, Secret>();
|
_secrets = new RetrievedSecrets(new Dictionary<string, Secret>());
|
||||||
}
|
}
|
||||||
|
|
||||||
_secrets = secrets;
|
_secrets = secretsResult.Value;
|
||||||
|
|
||||||
_orderedResolvedCacheSettings = ResolveCacheSettingsInPrecedenceOrder(arguments);
|
_orderedResolvedCacheSettings = ResolveCacheSettingsInPrecedenceOrder(arguments);
|
||||||
Contract.Assert(_orderedResolvedCacheSettings.Count != 0);
|
Contract.Assert(_orderedResolvedCacheSettings.Count != 0);
|
||||||
|
@ -294,12 +295,51 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var primaryCacheRoot = OrderedResolvedCacheSettings[0].ResolvedCacheRootPath;
|
||||||
|
|
||||||
|
var configuration = new GlobalCacheServiceConfiguration()
|
||||||
|
{
|
||||||
|
MaxEventParallelism = RedisContentLocationStoreConfiguration.EventStore.MaxEventProcessingConcurrency,
|
||||||
|
MasterLeaseStaleThreshold = RedisContentLocationStoreConfiguration.Checkpoint.MasterLeaseExpiryTime.Multiply(0.5),
|
||||||
|
VolatileEventStorage = new RedisVolatileEventStorageConfiguration()
|
||||||
|
{
|
||||||
|
ConnectionString = (GetRequiredSecret(_distributedSettings.ContentMetadataRedisSecretName) as PlainTextSecret).Secret,
|
||||||
|
KeyPrefix = _distributedSettings.RedisWriteAheadKeyPrefix,
|
||||||
|
MaximumKeyLifetime = _distributedSettings.ContentMetadataRedisMaximumKeyLifetime,
|
||||||
|
},
|
||||||
|
PersistentEventStorage = new BlobEventStorageConfiguration()
|
||||||
|
{
|
||||||
|
Credentials = GetStorageCredentials(new[] { _distributedSettings.ContentMetadataBlobSecretName }).First(),
|
||||||
|
FolderName = "events" + _distributedSettings.KeySpacePrefix,
|
||||||
|
ContainerName = _distributedSettings.ContentMetadataLogBlobContainerName,
|
||||||
|
},
|
||||||
|
CentralStorage = RedisContentLocationStoreConfiguration.CentralStore with
|
||||||
|
{
|
||||||
|
ContainerName = _distributedSettings.ContentMetadataCentralStorageContainerName
|
||||||
|
},
|
||||||
|
EventStream = new ContentMetadataEventStreamConfiguration()
|
||||||
|
{
|
||||||
|
BatchWriteAheadWrites = _distributedSettings.ContentMetadataBatchVolatileWrites,
|
||||||
|
ShutdownTimeout = _distributedSettings.ContentMetadataShutdownTimeout,
|
||||||
|
LogBlockRefreshInterval = _distributedSettings.ContentMetadataPersistInterval
|
||||||
|
},
|
||||||
|
Checkpoint = RedisContentLocationStoreConfiguration.Checkpoint with
|
||||||
|
{
|
||||||
|
WorkingDirectory = primaryCacheRoot / "cmschkpt"
|
||||||
|
},
|
||||||
|
ClusterManagement = new ClusterManagementConfiguration()
|
||||||
|
{
|
||||||
|
MachineExpiryInterval = RedisContentLocationStoreConfiguration.MachineActiveToExpiredInterval,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CentralStreamStorage centralStreamStorage = configuration.CentralStorage.CreateCentralStorage();
|
||||||
|
|
||||||
if (_distributedSettings.IsMasterEligible)
|
if (_distributedSettings.IsMasterEligible)
|
||||||
{
|
{
|
||||||
var service = Services.GlobalCacheService.Instance;
|
var service = Services.GlobalCacheService.Instance;
|
||||||
yield return new ProtobufNetGrpcServiceEndpoint<IGlobalCacheService, GlobalCacheService>(nameof(GlobalCacheService), service);
|
yield return new ProtobufNetGrpcServiceEndpoint<IGlobalCacheService, GlobalCacheService>(nameof(GlobalCacheService), service);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IGrpcServiceEndpoint[] GetAdditionalEndpoints()
|
public IGrpcServiceEndpoint[] GetAdditionalEndpoints()
|
||||||
|
@ -567,7 +607,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
AbsolutePath localCacheRoot,
|
AbsolutePath localCacheRoot,
|
||||||
RocksDbContentLocationDatabaseConfiguration dbConfig)
|
RocksDbContentLocationDatabaseConfiguration dbConfig)
|
||||||
{
|
{
|
||||||
if (_secrets.Count == 0)
|
if (_secrets.Secrets.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -823,7 +863,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
|
|
||||||
public Secret GetRequiredSecret(string secretName)
|
public Secret GetRequiredSecret(string secretName)
|
||||||
{
|
{
|
||||||
if (!_secrets.TryGetValue(secretName, out var value))
|
if (!_secrets.Secrets.TryGetValue(secretName, out var value))
|
||||||
{
|
{
|
||||||
throw new KeyNotFoundException($"Missing secret: {secretName}");
|
throw new KeyNotFoundException($"Missing secret: {secretName}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,13 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.ContractsLight;
|
using System.Diagnostics.ContractsLight;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Extensions;
|
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Stores;
|
using BuildXL.Cache.ContentStore.Interfaces.Stores;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
using BuildXL.Cache.ContentStore.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
using BuildXL.Cache.ContentStore.UtilitiesCore;
|
using BuildXL.Cache.ContentStore.UtilitiesCore;
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
protected override Tracer Tracer { get; } = new Tracer(nameof(MultiLevelContentStore));
|
protected override Tracer Tracer { get; } = new Tracer(nameof(MultiLevelContentStore));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MultiLevelReadOnlyContentSession"/> class.
|
/// Initializes a new instance of the <see cref="MultiLevelReadOnlyContentSession{TSession}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiLevelReadOnlyContentSession(
|
public MultiLevelReadOnlyContentSession(
|
||||||
string name,
|
string name,
|
||||||
|
|
|
@ -6,15 +6,12 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildXL.Cache.ContentStore.Distributed.Sessions;
|
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
using BuildXL.Cache.ContentStore.Hashing;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
using BuildXL.Cache.ContentStore.Interfaces.Tracing;
|
||||||
using BuildXL.Cache.ContentStore.Sessions.Internal;
|
using BuildXL.Cache.ContentStore.Sessions.Internal;
|
||||||
using BuildXL.Cache.ContentStore.Tracing;
|
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service.Internal
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether to use all sessions rather than only the primary for session read operations
|
/// Indicates whether to use all sessions rather than only the primary for session read operations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryAllSesssions { get; }
|
public bool TryAllSessions { get; }
|
||||||
|
|
||||||
private ContentStoreTracer StoreTracer { get; } = new ContentStoreTracer(nameof(MultiplexedContentStore));
|
private ContentStoreTracer StoreTracer { get; } = new ContentStoreTracer(nameof(MultiplexedContentStore));
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
DrivesWithContentStore = drivesWithContentStore;
|
DrivesWithContentStore = drivesWithContentStore;
|
||||||
PreferredCacheDrive = preferredCacheDrive;
|
PreferredCacheDrive = preferredCacheDrive;
|
||||||
PreferredContentStore = drivesWithContentStore[preferredCacheDrive];
|
PreferredContentStore = drivesWithContentStore[preferredCacheDrive];
|
||||||
TryAllSesssions = tryAllSessions;
|
TryAllSessions = tryAllSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -303,7 +303,7 @@ namespace BuildXL.Cache.Host.Service.Internal
|
||||||
yield return session;
|
yield return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Store.TryAllSesssions)
|
if (!Store.TryAllSessions)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using BuildXL.Cache.ContentStore.Distributed;
|
using BuildXL.Cache.ContentStore.Distributed;
|
||||||
using BuildXL.Cache.ContentStore.Hashing;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Time;
|
|
||||||
using BuildXL.Cache.ContentStore.Stores;
|
|
||||||
using BuildXL.Cache.ContentStore.Utils;
|
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Service.Internal
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Final settings object used for intializing a distributed cache instance
|
/// Final settings object used for initializing a distributed cache instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ResolvedNamedCacheSettings
|
public class ResolvedNamedCacheSettings
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.ContractsLight;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Service.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A helper type used for serializing <see cref="RetrievedSecrets"/> to and from a string.
|
||||||
|
/// </summary>
|
||||||
|
internal static class RetrievedSecretsSerializer
|
||||||
|
{
|
||||||
|
public const string SerializedSecretsKeyName = "SerializedSecretsKey";
|
||||||
|
|
||||||
|
public static string Serialize(RetrievedSecrets secrets)
|
||||||
|
{
|
||||||
|
var secretsList = secrets.Secrets
|
||||||
|
.Select(kvp => SecretData.FromSecret(kvp.Key, kvp.Value))
|
||||||
|
.OrderBy(s => s.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return JsonSerializer.Serialize(secretsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<RetrievedSecrets> Deserialize(string content)
|
||||||
|
{
|
||||||
|
Contract.Requires(!string.IsNullOrEmpty(content));
|
||||||
|
|
||||||
|
List<SecretData> secrets = JsonSerializer.Deserialize<List<SecretData>>(content);
|
||||||
|
return Result.Success(
|
||||||
|
new RetrievedSecrets(
|
||||||
|
secrets.ToDictionary(s => s.Name, s => s.ToSecret())));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SecretData
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public SecretKind Kind { get; set; }
|
||||||
|
|
||||||
|
public string SecretOrToken { get; set; }
|
||||||
|
|
||||||
|
public string StorageAccount { get; set; }
|
||||||
|
|
||||||
|
public string ResourcePath { get; set; }
|
||||||
|
|
||||||
|
public Secret ToSecret()
|
||||||
|
=> Kind switch
|
||||||
|
{
|
||||||
|
SecretKind.PlainText => new PlainTextSecret(SecretOrToken),
|
||||||
|
SecretKind.SasToken => new UpdatingSasToken(new SasToken(SecretOrToken, StorageAccount, ResourcePath)),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(Kind))
|
||||||
|
};
|
||||||
|
|
||||||
|
public static SecretData FromSecret(string name, Secret secret)
|
||||||
|
=> secret switch
|
||||||
|
{
|
||||||
|
PlainTextSecret plainTextSecret
|
||||||
|
=> new SecretData { Name = name, Kind = SecretKind.PlainText, SecretOrToken = plainTextSecret.Secret },
|
||||||
|
|
||||||
|
UpdatingSasToken updatingSasToken
|
||||||
|
=> new SecretData
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Kind = SecretKind.SasToken,
|
||||||
|
SecretOrToken = updatingSasToken.Token.Token,
|
||||||
|
ResourcePath = updatingSasToken.Token.ResourcePath,
|
||||||
|
StorageAccount = updatingSasToken.Token.StorageAccount
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(secret))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ using System.Diagnostics.ContractsLight;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using BuildXL.Cache.ContentStore.FileSystem;
|
using BuildXL.Cache.ContentStore.FileSystem;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System.Diagnostics.ContractsLight;
|
|
||||||
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
using BuildXL.Cache.ContentStore.Interfaces.Logging;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Cache.Host.Configuration;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Type to signal the host which kind of secret is expected to be returned
|
/// Type to signal the host which kind of secret is expected to be returned
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct RetrieveSecretsRequest
|
public readonly struct RetrieveSecretsRequest
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
new RetrieveSecretsRequest(secretName, SecretKind.PlainText)
|
new RetrieveSecretsRequest(secretName, SecretKind.PlainText)
|
||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
return ((PlainTextSecret)secrets[secretName]).Secret;
|
return ((PlainTextSecret)secrets.Secrets[secretName]).Secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -42,7 +42,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
new RetrieveSecretsRequest(secretName, SecretKind.SasToken)
|
new RetrieveSecretsRequest(secretName, SecretKind.SasToken)
|
||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
return new AzureBlobStorageCredentials((UpdatingSasToken)secrets[secretName]);
|
return new AzureBlobStorageCredentials((UpdatingSasToken)secrets.Secrets[secretName]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,7 @@ namespace BuildXL.Cache.Host.Service
|
||||||
new RetrieveSecretsRequest(secretName, SecretKind.PlainText)
|
new RetrieveSecretsRequest(secretName, SecretKind.PlainText)
|
||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
return new AzureBlobStorageCredentials((PlainTextSecret)secrets[secretName]);
|
return new AzureBlobStorageCredentials((PlainTextSecret)secrets.Secrets[secretName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,14 @@ using System.Text;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Results;
|
||||||
|
using BuildXL.Cache.Host.Configuration;
|
||||||
using BuildXL.Cache.Host.Service;
|
using BuildXL.Cache.Host.Service;
|
||||||
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
using BuildXL.Cache.ContentStore.Tracing.Internal;
|
||||||
using BuildXL.Cache.ContentStore.Service;
|
using BuildXL.Cache.ContentStore.Service;
|
||||||
using BuildXL.Utilities.CLI;
|
using BuildXL.Utilities.CLI;
|
||||||
using BuildXL.Utilities;
|
using BuildXL.Utilities;
|
||||||
using BuildXL.Cache.Host.Configuration;
|
using BuildXL.Utilities.Tasks;
|
||||||
|
|
||||||
namespace BuildXL.Cache.Host.Test
|
namespace BuildXL.Cache.Host.Test
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BuildXL.Cache.ContentStore.Interfaces.Secrets;
|
||||||
|
using BuildXL.Cache.ContentStore.InterfacesTest.Results;
|
||||||
|
using BuildXL.Cache.Host.Service;
|
||||||
|
using BuildXL.Cache.Host.Service.Internal;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace BuildXL.Cache.Host.Test;
|
||||||
|
|
||||||
|
public class RetrievedSecretsSerializerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TestSerialization()
|
||||||
|
{
|
||||||
|
var secretsMap = new Dictionary<string, Secret>
|
||||||
|
{
|
||||||
|
["cbcache-test-redis-dm_s1"] =
|
||||||
|
new PlainTextSecret("Fake secret that is quite long to emulate the size of the serialized entry."),
|
||||||
|
["cbcache-test-redis-secondary-dm_s1"] =
|
||||||
|
new PlainTextSecret("Fake secret that is quite long to emulate the size of the serialized entry."),
|
||||||
|
["cbcache-test-event-hub-dm_s1"] =
|
||||||
|
new PlainTextSecret("Fake secret that is quite long to emulate the size of the serialized entry."),
|
||||||
|
["cbcacheteststorage-dm_s1-sas"] =
|
||||||
|
new UpdatingSasToken(new SasToken("token_name", "storage_account", "resource_path")),
|
||||||
|
["ContentMetadataBlobSecretName-dm_s1"] = new PlainTextSecret(
|
||||||
|
"Fake secret that is quite long to emulate the size of the serialized entry.")
|
||||||
|
};
|
||||||
|
var secrets = new RetrievedSecrets(secretsMap);
|
||||||
|
|
||||||
|
var text = RetrievedSecretsSerializer.Serialize(secrets);
|
||||||
|
|
||||||
|
var deserializedSecretsMap = RetrievedSecretsSerializer.Deserialize(text).ShouldBeSuccess().Value.Secrets;
|
||||||
|
|
||||||
|
Assert.Equal(secretsMap.Count, deserializedSecretsMap.Count);
|
||||||
|
|
||||||
|
foreach (var kvp in secretsMap)
|
||||||
|
{
|
||||||
|
Assert.Equal(kvp.Value, deserializedSecretsMap[kvp.Key]);
|
||||||
|
Assert.Equal(kvp.Value, deserializedSecretsMap[kvp.Key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BuildXL.Utilities.ConfigurationHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A special class that defines some methods from <see cref="ConfigurationHelper"/> as extension methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <code>using static</code> does not allow using methods defined as extension methods.
|
||||||
|
/// It means that the code should decide if to use such helpers as extension methods or via 'using static',
|
||||||
|
/// defining a separate class solves this ambiguity.
|
||||||
|
/// This type is moved intentionally into a sub-namespace of 'BuildXL.Utilities' to name conflicts that may occur
|
||||||
|
/// if the client code have 'using BuildXL.Utilities'.
|
||||||
|
/// </remarks>
|
||||||
|
public static class ConfigurationHelperExtensions
|
||||||
|
{
|
||||||
|
/// <nodoc />
|
||||||
|
public static void ApplyIfNotNull<T>(this T value, Action<T> apply)
|
||||||
|
where T : class => ConfigurationHelper.ApplyIfNotNull(value, apply);
|
||||||
|
|
||||||
|
/// <nodoc />
|
||||||
|
public static void ApplyIfNotNull<T>(this T? value, Action<T> apply)
|
||||||
|
where T : struct
|
||||||
|
=> ConfigurationHelper.ApplyIfNotNull(value, apply);
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче