Skip to content

DO NOT REVIEW - Agent CDN - Added Warning in Initialize Phase when new Agent CDN is not reachable #5205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Agent.Sdk/Knob/AgentKnobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -786,5 +786,13 @@ public class AgentKnobs
"Timeout for channel communication between agent listener and worker processes.",
new EnvironmentKnobSource("PIPELINE_ARTIFACT_ASSOCIATE_TIMEOUT"),
new BuiltInDefaultKnobSource("900")); // 15 * 60 - Setting the timeout to 15 minutes to account for slowness from azure storage and retries.

public static readonly Knob AgentCDNConnectivityFailWarning = new Knob(
nameof(AgentCDNConnectivityFailWarning),
"Show warning message when the Agent CDN Endpoint (download.agent.dev.azure.com) is not reachable. ",
new RuntimeKnobSource("AGENT_CDN_CONNECTIVITY_FAIL_WARNING"),
new EnvironmentKnobSource("AGENT_CDN_CONNECTIVITY_FAIL_WARNING"),
new PipelineFeatureSource("AgentCDNConnectivityFailWarning"),
new BuiltInDefaultKnobSource("false"));
}
}
43 changes: 43 additions & 0 deletions src/Agent.Sdk/Util/PlatformUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
using Newtonsoft.Json;
using System.ServiceProcess;
using Agent.Sdk.Util;
using System.Net.Http;
using System.Net;

namespace Agent.Sdk
{
Expand Down Expand Up @@ -438,6 +440,47 @@ public static bool DetectAzureVM()
}
return isAzureVM;
}

// The URL of the agent package hosted on Azure CDN
private const string _agentPackageUri = "https://download.agent.dev.azure.com/agent/4.252.0/vsts-agent-win-x64-4.252.0.zip";

#nullable enable
/// <summary>
/// Checks if the agent CDN endpoint is accessible by sending an HTTP HEAD request.
/// </summary>
/// <param name="webProxy">
/// Optional <see cref="IWebProxy"/> to route the request through a proxy. If null, the system default proxy settings are used.
/// </param>
/// <remarks>
/// - Returns <c>true</c> if the endpoint responds with a successful (2xx) status code.
/// - Returns <c>false</c> if the endpoint responds with a non-success status code (4xx, 5xx).
/// - Throws exceptions (e.g., timeout, DNS failure) if the request cannot be completed.
/// - Uses a 5-second timeout to avoid hanging.
/// - All HTTP resources are properly disposed after the request completes.
/// </remarks>
/// <returns><c>true</c> if the endpoint is reachable and returns success; otherwise, <c>false</c>.</returns>
public static async Task<bool> IsAgentCdnAccessibleAsync(IWebProxy? webProxy = null)
{
// Configure the HttpClientHandler with the proxy if provided
using HttpClientHandler handler = new()
{
Proxy = webProxy,
UseProxy = webProxy is not null
};
handler.CheckCertificateRevocationList = true; // Check for certificate revocation
using HttpClient httpClient = new(handler);

// Construct a HEAD request to avoid downloading the full file
using HttpRequestMessage request = new(HttpMethod.Head, _agentPackageUri);

// Apply a 5-second timeout to prevent hanging
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(5));

// Send the request and return whether the response status indicates success
HttpResponseMessage response = await httpClient.SendAsync(request, cts.Token);
return response.IsSuccessStatusCode;
}
#nullable disable
}

#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
Expand Down
52 changes: 52 additions & 0 deletions src/Agent.Worker/JobExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Agent.Sdk.Knob;
using Newtonsoft.Json;
using Microsoft.VisualStudio.Services.Agent.Worker.Telemetry;
using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;

namespace Microsoft.VisualStudio.Services.Agent.Worker
{
Expand Down Expand Up @@ -230,6 +231,32 @@ public async Task<List<IStep>> InitializeJob(IExecutionContext jobContext, Pipel
}
}

// Check if the Agent CDN is accessible
if (AgentKnobs.AgentCDNConnectivityFailWarning.GetValue(context).AsBoolean())
{
try
{
Trace.Verbose("Checking if the Agent CDN Endpoint (download.agent.dev.azure.com) is reachable");
bool isAgentCDNAccessible = await PlatformUtil.IsAgentCdnAccessibleAsync(agentWebProxy.WebProxy);
if (isAgentCDNAccessible)
{
context.Output("Agent CDN is accessible.");
}
else
{
context.Warning(StringUtil.Loc("AgentCdnAccessFailWarning"));
}
PublishAgentCDNAccessStatusTelemetry(context, isAgentCDNAccessible);
}
catch (Exception ex)
{
// Handles network-level or unexpected exceptions (DNS failure, timeout, etc.)
context.Warning(StringUtil.Loc("AgentCdnAccessFailWarning"));
PublishAgentCDNAccessStatusTelemetry(context, false);
Trace.Error($"Exception when attempting a HEAD request to Agent CDN: {ex}");
}
}

if (PlatformUtil.RunningOnWindows)
{
// This is for internal testing and is not publicly supported. This will be removed from the agent at a later time.
Expand Down Expand Up @@ -755,6 +782,31 @@ private void PublishKnobsInfo(IExecutionContext jobContext)
PublishTelemetry(jobContext, telemetryData, "KnobsStatus");
}

private void PublishAgentCDNAccessStatusTelemetry(IExecutionContext context, bool isAgentCDNAccessible)
{
try
{
var telemetryData = new Dictionary<string, string>
{
["JobId"] = context?.Variables?.System_JobId?.ToString() ?? string.Empty,
["isAgentCDNAccessible"] = isAgentCDNAccessible.ToString()
};

var cmd = new Command("telemetry", "publish")
{
Data = JsonConvert.SerializeObject(telemetryData)
};
cmd.Properties["area"] = "PipelinesTasks";
cmd.Properties["feature"] = "CDNConnectivityCheck";

PublishTelemetry(context, telemetryData, "AgentCDNAccessStatus");
}
catch (Exception ex)
{
Trace.Verbose($"Ignoring exception during 'AgentCDNAccessStatus' telemetry publish: '{ex.Message}'");
}
}

private void PublishTelemetry(IExecutionContext context, Dictionary<string, string> telemetryData, string feature)
{
try
Expand Down
1 change: 1 addition & 0 deletions src/Misc/layoutbin/en-US/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"AddEnvironmentVMResourceTags": "Environment Virtual Machine resource tags? (Y/N)",
"AgentAddedSuccessfully": "Successfully added the agent",
"AgentAlreadyInsideContainer": "Container feature is not supported when agent is already running inside container. Please reference documentation (https://go.microsoft.com/fwlink/?linkid=875268)",
"AgentCdnAccessFailWarning": "Action Required: Azure Pipelines Agent cannot reach the new CDN URL. Allowlist 'download.agent.dev.azure.com' now to prevent pipeline failures. Details: https://devblogs.microsoft.com/devops/cdn-domain-url-change-for-agents-in-pipelines/",
"AgentDoesNotSupportContainerFeatureRhel6": "Agent does not support the container feature on Red Hat Enterprise Linux 6 or CentOS 6.",
"AgentDowngrade": "Downgrading agent to a lower version. This is usually due to a rollback of the currently published agent for a bug fix. To disable this behavior, set environment variable AZP_AGENT_DOWNGRADE_DISABLED=true before launching your agent.",
"AgentExit": "Agent will exit shortly for update, should back online within 10 seconds.",
Expand Down
Loading