diff --git a/src/Agent.Plugins/Artifact/PipelineArtifactConstants.cs b/src/Agent.Plugins/Artifact/PipelineArtifactConstants.cs index f0cd3908ce..f1ff1c79ec 100644 --- a/src/Agent.Plugins/Artifact/PipelineArtifactConstants.cs +++ b/src/Agent.Plugins/Artifact/PipelineArtifactConstants.cs @@ -3,7 +3,6 @@ namespace Agent.Plugins { - // Use PipelineArtifactContants.cs from ADO, once the latest libs are available. public class PipelineArtifactConstants { public const string AzurePipelinesAgent = "AzurePipelinesAgent"; @@ -19,6 +18,5 @@ public class PipelineArtifactConstants public const string FileShareArtifact = "filepath"; public const string CustomPropertiesPrefix = "user-"; public const string HashType = "HashType"; - public const string DomainId = "DomainId"; } } \ No newline at end of file diff --git a/src/Agent.Plugins/Artifact/PipelineArtifactProvider.cs b/src/Agent.Plugins/Artifact/PipelineArtifactProvider.cs index e2fd7667b8..cfe0bc5741 100644 --- a/src/Agent.Plugins/Artifact/PipelineArtifactProvider.cs +++ b/src/Agent.Plugins/Artifact/PipelineArtifactProvider.cs @@ -15,7 +15,6 @@ using Microsoft.VisualStudio.Services.WebApi; using Microsoft.VisualStudio.Services.Content.Common; using Microsoft.VisualStudio.Services.BlobStore.Common; -using Microsoft.VisualStudio.Services.BlobStore.Common.Telemetry; namespace Agent.Plugins { @@ -38,19 +37,12 @@ public async Task DownloadSingleArtifactAsync( CancellationToken cancellationToken, AgentTaskPluginExecutionContext context) { - // if properties doesn't have it, use the default domain for backward compatibility - IDomainId domainId = WellKnownDomainIds.DefaultDomainId; - if(buildArtifact.Resource.Properties.TryGetValue(PipelineArtifactConstants.DomainId, out string domainIdString)) - { - domainId = DomainIdFactory.Create(domainIdString); - } - var (dedupManifestClient, clientTelemetry) = await DedupManifestArtifactClientFactory.Instance.CreateDedupManifestClientAsync( this.context.IsSystemDebugTrue(), (str) => this.context.Output(str), this.connection, DedupManifestArtifactClientFactory.Instance.GetDedupStoreClientMaxParallelism(context), - domainId, + WellKnownDomainIds.DefaultDomainId, Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts.Client.PipelineArtifact, context, cancellationToken); @@ -93,81 +85,49 @@ public async Task DownloadMultipleArtifactsAsync( CancellationToken cancellationToken, AgentTaskPluginExecutionContext context) { - // create clients and group artifacts for each domain: - Dictionary ArtifactDictionary)> dedupManifestClients = - new(); - - foreach(var buildArtifact in buildArtifacts) - { - // if properties doesn't have it, use the default domain for backward compatibility - IDomainId domainId = WellKnownDomainIds.DefaultDomainId; - if(buildArtifact.Resource.Properties.TryGetValue(PipelineArtifactConstants.DomainId, out string domainIdString)) - { - domainId = DomainIdFactory.Create(domainIdString); - } - - // Have we already created the clients for this domain? - if(dedupManifestClients.ContainsKey(domainId)) { - // Clients already created for this domain, Just add the artifact to the list: - dedupManifestClients[domainId].ArtifactDictionary.Add(buildArtifact.Name, DedupIdentifier.Create(buildArtifact.Resource.Data)); - } - else - { - // create the clients: - var (dedupManifestClient, clientTelemetry) = await DedupManifestArtifactClientFactory.Instance.CreateDedupManifestClientAsync( - this.context.IsSystemDebugTrue(), - (str) => this.context.Output(str), - this.connection, - DedupManifestArtifactClientFactory.Instance.GetDedupStoreClientMaxParallelism(context), - domainId, - Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts.Client.PipelineArtifact, - context, - cancellationToken); - - // and create the artifact dictionary with the current artifact - var artifactDictionary = new Dictionary - { - { buildArtifact.Name, DedupIdentifier.Create(buildArtifact.Resource.Data) } - }; - - dedupManifestClients.Add(domainId, (dedupManifestClient, clientTelemetry, artifactDictionary)); - } - } + var (dedupManifestClient, clientTelemetry) = await DedupManifestArtifactClientFactory.Instance.CreateDedupManifestClientAsync( + this.context.IsSystemDebugTrue(), + (str) => this.context.Output(str), + this.connection, + DedupManifestArtifactClientFactory.Instance.GetDedupStoreClientMaxParallelism(context), + WellKnownDomainIds.DefaultDomainId, + Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts.Client.PipelineArtifact, + context, + cancellationToken); - foreach(var clientInfo in dedupManifestClients.Values) + using (clientTelemetry) { - using (clientInfo.Telemetry) - { - // 2) download to the target path - var options = DownloadDedupManifestArtifactOptions.CreateWithMultiManifestIds( - clientInfo.ArtifactDictionary, - downloadParameters.TargetDirectory, - proxyUri: null, - minimatchPatterns: downloadParameters.MinimatchFilters, - minimatchFilterWithArtifactName: downloadParameters.MinimatchFilterWithArtifactName); - - PipelineArtifactActionRecord downloadRecord = clientInfo.Telemetry.CreateRecord((level, uri, type) => - new PipelineArtifactActionRecord(level, uri, type, nameof(DownloadMultipleArtifactsAsync), this.context)); + var artifactNameAndManifestIds = buildArtifacts.ToDictionary( + keySelector: (a) => a.Name, // keys should be unique, if not something is really wrong + elementSelector: (a) => DedupIdentifier.Create(a.Resource.Data)); + // 2) download to the target path + var options = DownloadDedupManifestArtifactOptions.CreateWithMultiManifestIds( + artifactNameAndManifestIds, + downloadParameters.TargetDirectory, + proxyUri: null, + minimatchPatterns: downloadParameters.MinimatchFilters, + minimatchFilterWithArtifactName: downloadParameters.MinimatchFilterWithArtifactName); - await clientInfo.Telemetry.MeasureActionAsync( - record: downloadRecord, - actionAsync: async () => - { - await AsyncHttpRetryHelper.InvokeVoidAsync( - async () => - { - await clientInfo.Client.DownloadAsync(options, cancellationToken); - }, - maxRetries: 3, - tracer: tracer, - canRetryDelegate: e => true, - context: nameof(DownloadMultipleArtifactsAsync), - cancellationToken: cancellationToken, - continueOnCapturedContext: false); - }); - // Send results to CustomerIntelligence - this.context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineArtifact, record: downloadRecord); - } + PipelineArtifactActionRecord downloadRecord = clientTelemetry.CreateRecord((level, uri, type) => + new PipelineArtifactActionRecord(level, uri, type, nameof(DownloadMultipleArtifactsAsync), this.context)); + await clientTelemetry.MeasureActionAsync( + record: downloadRecord, + actionAsync: async () => + { + await AsyncHttpRetryHelper.InvokeVoidAsync( + async () => + { + await dedupManifestClient.DownloadAsync(options, cancellationToken); + }, + maxRetries: 3, + tracer: tracer, + canRetryDelegate: e => true, + context: nameof(DownloadMultipleArtifactsAsync), + cancellationToken: cancellationToken, + continueOnCapturedContext: false); + }); + // Send results to CustomerIntelligence + this.context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineArtifact, record: downloadRecord); } } } diff --git a/src/Agent.Plugins/Artifact/PipelineArtifactServer.cs b/src/Agent.Plugins/Artifact/PipelineArtifactServer.cs index 9183bec208..89c0e2e0ea 100644 --- a/src/Agent.Plugins/Artifact/PipelineArtifactServer.cs +++ b/src/Agent.Plugins/Artifact/PipelineArtifactServer.cs @@ -41,26 +41,15 @@ internal async Task UploadAsync( IDictionary properties, CancellationToken cancellationToken) { - // Get the client settings, if any. - var tracer = DedupManifestArtifactClientFactory.CreateArtifactsTracer(verbose: false, (str) => context.Output(str)); VssConnection connection = context.VssConnection; - var clientSettings = await DedupManifestArtifactClientFactory.GetClientSettingsAsync( - connection, - Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts.Client.PipelineArtifact, - tracer, - cancellationToken); - - // Get the default domain to use: - IDomainId domainId = DedupManifestArtifactClientFactory.GetDefaultDomainId(clientSettings, tracer); - - var (dedupManifestClient, clientTelemetry) = DedupManifestArtifactClientFactory.Instance - .CreateDedupManifestClient( + var (dedupManifestClient, clientTelemetry) = await DedupManifestArtifactClientFactory.Instance + .CreateDedupManifestClientAsync( context.IsSystemDebugTrue(), (str) => context.Output(str), connection, DedupManifestArtifactClientFactory.Instance.GetDedupStoreClientMaxParallelism(context), - domainId, - clientSettings, + WellKnownDomainIds.DefaultDomainId, + Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts.Client.PipelineArtifact, context, cancellationToken); @@ -95,8 +84,7 @@ internal async Task UploadAsync( { PipelineArtifactConstants.RootId, result.RootId.ValueString }, { PipelineArtifactConstants.ProofNodes, StringUtil.ConvertToJson(result.ProofNodes.ToArray()) }, { PipelineArtifactConstants.ArtifactSize, result.ContentSize.ToString() }, - { PipelineArtifactConstants.HashType, dedupManifestClient.HashType.Serialize() }, - { PipelineArtifactConstants.DomainId, domainId.Serialize() } + { PipelineArtifactConstants.HashType, dedupManifestClient.HashType.Serialize() } }; BuildArtifact buildArtifact = await AsyncHttpRetryHelper.InvokeAsync( @@ -152,11 +140,22 @@ internal async Task DownloadAsync( CancellationToken cancellationToken) { VssConnection connection = context.VssConnection; - PipelineArtifactProvider provider = new PipelineArtifactProvider(context, connection, tracer); + var (dedupManifestClient, clientTelemetry) = await DedupManifestArtifactClientFactory.Instance + .CreateDedupManifestClientAsync( + context.IsSystemDebugTrue(), + (str) => context.Output(str), + connection, + DedupManifestArtifactClientFactory.Instance.GetDedupStoreClientMaxParallelism(context), + WellKnownDomainIds.DefaultDomainId, + Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts.Client.PipelineArtifact, + context, + cancellationToken); BuildServer buildServer = new(connection); + using (clientTelemetry) // download all pipeline artifacts if artifact name is missing + { if (downloadOptions == DownloadOptions.MultiDownload) { List artifacts; @@ -188,7 +187,40 @@ internal async Task DownloadAsync( else { context.Output(StringUtil.Loc("DownloadingMultiplePipelineArtifacts", pipelineArtifacts.Count())); - await provider.DownloadMultipleArtifactsAsync(downloadParameters,artifacts, cancellationToken, context); + + var artifactNameAndManifestIds = pipelineArtifacts.ToDictionary( + keySelector: (a) => a.Name, // keys should be unique, if not something is really wrong + elementSelector: (a) => DedupIdentifier.Create(a.Resource.Data)); + // 2) download to the target path + var options = DownloadDedupManifestArtifactOptions.CreateWithMultiManifestIds( + artifactNameAndManifestIds, + downloadParameters.TargetDirectory, + proxyUri: null, + minimatchPatterns: downloadParameters.MinimatchFilters, + minimatchFilterWithArtifactName: downloadParameters.MinimatchFilterWithArtifactName, + customMinimatchOptions: downloadParameters.CustomMinimatchOptions); + + PipelineArtifactActionRecord downloadRecord = clientTelemetry.CreateRecord((level, uri, type) => + new PipelineArtifactActionRecord(level, uri, type, nameof(DownloadAsync), context)); + await clientTelemetry.MeasureActionAsync( + record: downloadRecord, + actionAsync: async () => + { + await AsyncHttpRetryHelper.InvokeVoidAsync( + async () => + { + await dedupManifestClient.DownloadAsync(options, cancellationToken); + }, + maxRetries: 3, + tracer: tracer, + canRetryDelegate: e => true, + context: nameof(DownloadAsync), + cancellationToken: cancellationToken, + continueOnCapturedContext: false); + }); + + // Send results to CustomerIntelligence + context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineArtifact, record: downloadRecord); } } else if (downloadOptions == DownloadOptions.SingleDownload) @@ -214,12 +246,42 @@ internal async Task DownloadAsync( { throw new InvalidOperationException($"Invalid {nameof(downloadParameters.ProjectRetrievalOptions)}!"); } - await provider.DownloadSingleArtifactAsync(downloadParameters, buildArtifact, cancellationToken, context); + + var manifestId = DedupIdentifier.Create(buildArtifact.Resource.Data); + var options = DownloadDedupManifestArtifactOptions.CreateWithManifestId( + manifestId, + downloadParameters.TargetDirectory, + proxyUri: null, + minimatchPatterns: downloadParameters.MinimatchFilters, + customMinimatchOptions: downloadParameters.CustomMinimatchOptions); + + PipelineArtifactActionRecord downloadRecord = clientTelemetry.CreateRecord((level, uri, type) => + new PipelineArtifactActionRecord(level, uri, type, nameof(DownloadAsync), context)); + await clientTelemetry.MeasureActionAsync( + record: downloadRecord, + actionAsync: async () => + { + await AsyncHttpRetryHelper.InvokeVoidAsync( + async () => + { + await dedupManifestClient.DownloadAsync(options, cancellationToken); + }, + maxRetries: 3, + tracer: tracer, + canRetryDelegate: e => true, + context: nameof(DownloadAsync), + cancellationToken: cancellationToken, + continueOnCapturedContext: false); + }); + + // Send results to CustomerIntelligence + context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineArtifact, record: downloadRecord); } else { throw new InvalidOperationException($"Invalid {nameof(downloadOptions)}!"); } + } } // Download for version 2. This decision was made because version 1 is sealed and we didn't want to break any existing customers. diff --git a/src/Microsoft.VisualStudio.Services.Agent/Blob/DedupManifestArtifactClientFactory.cs b/src/Microsoft.VisualStudio.Services.Agent/Blob/DedupManifestArtifactClientFactory.cs index cf7cfde0c4..a5acd170f5 100644 --- a/src/Microsoft.VisualStudio.Services.Agent/Blob/DedupManifestArtifactClientFactory.cs +++ b/src/Microsoft.VisualStudio.Services.Agent/Blob/DedupManifestArtifactClientFactory.cs @@ -31,19 +31,6 @@ public interface IDedupManifestArtifactClientFactory /// use the system default. /// Cancellation token used for both creating clients and verifying client conneciton. /// Tuple of the client and the telemtery client - (DedupManifestArtifactClient client, BlobStoreClientTelemetry telemetry) CreateDedupManifestClient( - bool verbose, - Action traceOutput, - VssConnection connection, - int maxParallelism, - IDomainId domainId, - ClientSettingsInfo clientSettings, - AgentTaskPluginExecutionContext context, - CancellationToken cancellationToken); - - /// - /// Creates a DedupManifestArtifactClient client and retrieves any client settings from the server - /// Task<(DedupManifestArtifactClient client, BlobStoreClientTelemetry telemetry)> CreateDedupManifestClientAsync( bool verbose, Action traceOutput, @@ -81,9 +68,6 @@ public interface IDedupManifestArtifactClientFactory public class DedupManifestArtifactClientFactory : IDedupManifestArtifactClientFactory { - // NOTE: this should be set to ClientSettingsConstants.DefaultDomainId when the latest update from Azure Devops is added. - private static string DefaultDomainIdKey = "DefaultDomainId"; - // Old default for hosted agents was 16*2 cores = 32. // In my tests of a node_modules folder, this 32x parallelism was consistently around 47 seconds. // At 192x it was around 16 seconds and 256x was no faster. @@ -97,9 +81,6 @@ private DedupManifestArtifactClientFactory() { } - /// - /// Creates a DedupManifestArtifactClient client and retrieves any client settings from the server - /// public async Task<(DedupManifestArtifactClient client, BlobStoreClientTelemetry telemetry)> CreateDedupManifestClientAsync( bool verbose, Action traceOutput, @@ -109,33 +90,6 @@ private DedupManifestArtifactClientFactory() BlobStore.WebApi.Contracts.Client client, AgentTaskPluginExecutionContext context, CancellationToken cancellationToken) - { - var clientSettings = await GetClientSettingsAsync( - connection, - client, - CreateArtifactsTracer(verbose, traceOutput), - cancellationToken); - - return CreateDedupManifestClient( - context.IsSystemDebugTrue(), - (str) => context.Output(str), - connection, - DedupManifestArtifactClientFactory.Instance.GetDedupStoreClientMaxParallelism(context), - domainId, - clientSettings, - context, - cancellationToken); - } - - public (DedupManifestArtifactClient client, BlobStoreClientTelemetry telemetry) CreateDedupManifestClient( - bool verbose, - Action traceOutput, - VssConnection connection, - int maxParallelism, - IDomainId domainId, - ClientSettingsInfo clientSettings, - AgentTaskPluginExecutionContext context, - CancellationToken cancellationToken) { const int maxRetries = 5; var tracer = CreateArtifactsTracer(verbose, traceOutput); @@ -143,23 +97,16 @@ private DedupManifestArtifactClientFactory() { maxParallelism = DefaultDedupStoreClientMaxParallelism; } - traceOutput($"Max dedup parallelism: {maxParallelism}"); - traceOutput($"DomainId: {domainId}"); - ArtifactHttpClientFactory factory = new ArtifactHttpClientFactory( - connection.Credentials, - connection.Settings.SendTimeout, - tracer, - cancellationToken); - - var helper = new HttpRetryHelper(maxRetries,e => true); - - IDedupStoreHttpClient dedupStoreHttpClient = helper.Invoke( - () => + IDedupStoreHttpClient dedupStoreHttpClient = await AsyncHttpRetryHelper.InvokeAsync( + async () => { - // since our call below is hidden, check if we are cancelled and throw if we are... - cancellationToken.ThrowIfCancellationRequested(); + ArtifactHttpClientFactory factory = new ArtifactHttpClientFactory( + connection.Credentials, + connection.Settings.SendTimeout, + tracer, + cancellationToken); IDedupStoreHttpClient dedupHttpclient; // this is actually a hidden network call to the location service: @@ -173,17 +120,24 @@ private DedupManifestArtifactClientFactory() dedupHttpclient = new DomainHttpClientWrapper(domainId, domainClient); } + this.HashType ??= await GetClientHashTypeAsync(factory, connection, client, tracer, context, cancellationToken); + return dedupHttpclient; - }); + }, + maxRetries: maxRetries, + tracer: tracer, + canRetryDelegate: e => true, + context: nameof(CreateDedupManifestClientAsync), + cancellationToken: cancellationToken, + continueOnCapturedContext: false); var telemetry = new BlobStoreClientTelemetry(tracer, dedupStoreHttpClient.BaseAddress); - this.HashType = GetClientHashType(clientSettings, context, tracer); + traceOutput($"Hashtype: {this.HashType.Value}"); if (this.HashType == BuildXL.Cache.ContentStore.Hashing.HashType.Dedup1024K) { dedupStoreHttpClient.RecommendedChunkCountPerCall = 10; // This is to workaround IIS limit - https://learn.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/requestlimits/ } - traceOutput($"Hashtype: {this.HashType.Value}"); var dedupClient = new DedupStoreClientWithDataport(dedupStoreHttpClient, new DedupStoreClientContext(maxParallelism), this.HashType.Value); return (new DedupManifestArtifactClient(telemetry, dedupClient, tracer), telemetry); @@ -285,75 +239,35 @@ public static IAppTraceSource CreateArtifactsTracer(bool verbose, Action includeSeverityLevel: verbose); } - /// - /// Get the client settings for the given client. - /// - /// This should only be called once per client type. This is intended to fail fast so it has no retries. - public static async Task GetClientSettingsAsync( + private static async Task GetClientHashTypeAsync( + ArtifactHttpClientFactory factory, VssConnection connection, BlobStore.WebApi.Contracts.Client client, IAppTraceSource tracer, + AgentTaskPluginExecutionContext context, CancellationToken cancellationToken) - { - try - { - ArtifactHttpClientFactory factory = new( - connection.Credentials, - connection.Settings.SendTimeout, - tracer, - cancellationToken); - - var blobUri = connection.GetClient().BaseAddress; - var clientSettingsHttpClient = factory.CreateVssHttpClient(blobUri); - return await clientSettingsHttpClient.GetSettingsAsync(client, userState: null, cancellationToken); - } - catch (Exception exception) - { - // Use info cause we don't want to fail builds with warnings as errors... - tracer.Info($"Error while retrieving client Settings for {client}. Exception: {exception}. Falling back to defaults."); - } - return null; - } - - public static IDomainId GetDefaultDomainId(ClientSettingsInfo clientSettings, IAppTraceSource tracer) - { - IDomainId domainId = WellKnownDomainIds.DefaultDomainId; - if (clientSettings != null && clientSettings.Properties.ContainsKey(DefaultDomainIdKey)) - { - try - { - domainId = DomainIdFactory.Create(clientSettings.Properties[DefaultDomainIdKey]); - } - catch (Exception exception) - { - tracer.Info($"Error converting the domain id '{clientSettings.Properties[DefaultDomainIdKey]}': {exception.Message}. Falling back to default."); - } - } - - return domainId; - } - - private static HashType GetClientHashType(ClientSettingsInfo clientSettings, AgentTaskPluginExecutionContext context, IAppTraceSource tracer) { HashType hashType = ChunkerHelper.DefaultChunkHashType; - // Note: 9/6/2023 Remove the below check in couple of months. if (AgentKnobs.AgentEnablePipelineArtifactLargeChunkSize.GetValue(context).AsBoolean()) { - if (clientSettings != null && clientSettings.Properties.ContainsKey(ClientSettingsConstants.ChunkSize)) + try { - try + var blobUri = connection.GetClient().BaseAddress; + var clientSettingsHttpClient = factory.CreateVssHttpClient(blobUri); + ClientSettingsInfo clientSettings = await clientSettingsHttpClient.GetSettingsAsync(client, cancellationToken); + if (clientSettings != null && clientSettings.Properties.ContainsKey(ClientSettingsConstants.ChunkSize)) { HashTypeExtensions.Deserialize(clientSettings.Properties[ClientSettingsConstants.ChunkSize], out hashType); } - catch (Exception exception) - { - tracer.Info($"Error converting the chunk size '{clientSettings.Properties[ClientSettingsConstants.ChunkSize]}': {exception.Message}. Falling back to default."); - } + } + catch (Exception exception) + { + tracer.Warn($"Error while retrieving hash type for {client}. Exception: {exception}"); } } return ChunkerHelper.IsHashTypeChunk(hashType) ? hashType : ChunkerHelper.DefaultChunkHashType; } - } + } } \ No newline at end of file diff --git a/src/Test/L0/Plugin/TestFileShareProvider/MockDedupManifestArtifactClientFactory.cs b/src/Test/L0/Plugin/TestFileShareProvider/MockDedupManifestArtifactClientFactory.cs index e00124ed09..5f3eed4f70 100644 --- a/src/Test/L0/Plugin/TestFileShareProvider/MockDedupManifestArtifactClientFactory.cs +++ b/src/Test/L0/Plugin/TestFileShareProvider/MockDedupManifestArtifactClientFactory.cs @@ -7,7 +7,6 @@ using Agent.Sdk; using Microsoft.VisualStudio.Services.Agent.Blob; using Microsoft.VisualStudio.Services.BlobStore.WebApi; -using Microsoft.VisualStudio.Services.BlobStore.WebApi.Contracts; using Microsoft.VisualStudio.Services.Content.Common.Tracing; using Microsoft.VisualStudio.Services.WebApi; using Microsoft.VisualStudio.Services.BlobStore.Common.Telemetry; @@ -37,22 +36,6 @@ public class MockDedupManifestArtifactClientFactory : IDedupManifestArtifactClie telemetrySender))); } - public (DedupManifestArtifactClient client, BlobStoreClientTelemetry telemetry) CreateDedupManifestClient( - bool verbose, - Action traceOutput, - VssConnection connection, - int maxParallelism, - IDomainId domainId, - ClientSettingsInfo clientSettings, - AgentTaskPluginExecutionContext context, - CancellationToken cancellationToken) - { - telemetrySender = new TestTelemetrySender(); - return (client: (DedupManifestArtifactClient)null, telemetry: new BlobStoreClientTelemetry( - NoopAppTraceSource.Instance, - baseAddress, - telemetrySender)); - } public Task<(DedupStoreClient client, BlobStoreClientTelemetryTfs telemetry)> CreateDedupClientAsync(bool verbose, Action traceOutput, VssConnection connection, int maxParallelism, CancellationToken cancellationToken) {