Skip to content

Commit 4c5dad6

Browse files
authored
For node20 fallback on host, check for glibc instead of OS version check (#4524)
* wip * ci * wip * wip * ci * fix * add telemetry for container "NeedsNode16Redirect" reduce telemetry spam for host * fix * fix: only override container node when task targets node20 * use correct warning for containers * update wording * update telemetry * only emit HostNode20to16Fallback once per ExecutionContext * minor
1 parent aaf9fcd commit 4c5dad6

File tree

7 files changed

+263
-140
lines changed

7 files changed

+263
-140
lines changed

src/Agent.Sdk/ContainerInfo.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,9 @@ public ContainerInfo(Pipelines.ContainerResource container, Boolean isJobContain
9090
public string CurrentUserId { get; set; }
9191
public string CurrentGroupName { get; set; }
9292
public string CurrentGroupId { get; set; }
93-
public bool NeedsNode16Redirect { get; set; }
94-
9593
public bool IsJobContainer { get; set; }
9694
public bool MapDockerSocket { get; set; }
95+
public bool NeedsNode16Redirect { get; set; }
9796
public PlatformUtil.OS ImageOS
9897
{
9998
get

src/Agent.Sdk/Util/PlatformUtil.cs

-45
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,6 @@ public static bool RunningOnRHEL6
102102
}
103103
}
104104

105-
public static bool RunningOnRHEL7
106-
{
107-
get
108-
{
109-
if (!(detectedRHEL7 is null))
110-
{
111-
return (bool)detectedRHEL7;
112-
}
113-
114-
DetectRHEL7();
115-
116-
return (bool)detectedRHEL7;
117-
}
118-
}
119-
120105
public static string GetSystemId()
121106
{
122107
return PlatformUtil.HostOS switch
@@ -167,34 +152,6 @@ private static void DetectRHEL6()
167152
}
168153
}
169154

170-
private static void DetectRHEL7()
171-
{
172-
lock (detectedRHEL7lock)
173-
{
174-
if (!RunningOnLinux || !File.Exists("/etc/redhat-release"))
175-
{
176-
detectedRHEL7 = false;
177-
}
178-
else
179-
{
180-
detectedRHEL7 = false;
181-
try
182-
{
183-
string redhatVersion = File.ReadAllText("/etc/redhat-release");
184-
if (redhatVersion.StartsWith("CentOS release 7.")
185-
|| redhatVersion.StartsWith("Red Hat Enterprise Linux Server release 7."))
186-
{
187-
detectedRHEL7 = true;
188-
}
189-
}
190-
catch (IOException)
191-
{
192-
// IOException indicates we couldn't read that file; probably not RHEL7
193-
}
194-
}
195-
}
196-
}
197-
198155
private static string GetLinuxId()
199156
{
200157
if (RunningOnLinux && File.Exists("/etc/os-release"))
@@ -314,8 +271,6 @@ private static string GetWindowsVersion()
314271

315272
private static bool? detectedRHEL6 = null;
316273
private static object detectedRHEL6lock = new object();
317-
private static bool? detectedRHEL7 = null;
318-
private static object detectedRHEL7lock = new object();
319274

320275
public static Architecture HostArchitecture
321276
{

src/Agent.Worker/ContainerOperationProvider.cs

+38-31
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.VisualStudio.Services.Agent.Util;
2020
using Microsoft.VisualStudio.Services.Agent.Worker.Container;
2121
using Microsoft.VisualStudio.Services.Agent.Worker.Handlers;
22+
using Microsoft.VisualStudio.Services.Agent.Worker.Telemetry;
2223
using Microsoft.VisualStudio.Services.Common;
2324
using Microsoft.Win32;
2425
using Newtonsoft.Json;
@@ -767,49 +768,37 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta
767768
}
768769
}
769770

770-
bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(executionContext).AsBoolean();
771-
772-
if(!useNode20InUnsupportedSystem)
771+
if (PlatformUtil.RunningOnLinux)
773772
{
774-
var node20 = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node20_1Folder, "bin", $"node{IOUtil.ExeExtension}"));
773+
bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(executionContext).AsBoolean();
775774

776-
string node20TestCmd = $"bash -c \"{node20} -v\"";
777-
List<string> nodeInfo = await DockerExec(executionContext, container.ContainerId, node20TestCmd, noExceptionOnError: true);
778-
if (nodeInfo.Count > 0)
775+
if (!useNode20InUnsupportedSystem)
779776
{
780-
foreach(var nodeInfoLine in nodeInfo)
777+
var node20 = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node20_1Folder, "bin", $"node{IOUtil.ExeExtension}"));
778+
779+
string node20TestCmd = $"bash -c \"{node20} -v\"";
780+
List<string> nodeVersionOutput = await DockerExec(executionContext, container.ContainerId, node20TestCmd, noExceptionOnError: true);
781+
782+
container.NeedsNode16Redirect = WorkerUtilities.IsCommandResultGlibcError(executionContext, nodeVersionOutput, out string nodeInfoLine);
783+
784+
if (container.NeedsNode16Redirect)
781785
{
782-
// detect example error from node 20 attempting to run on Ubuntu18:
783-
// /__a/externals/node20/bin/node: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.27' not found (required by /__a/externals/node20/bin/node)
784-
// /__a/externals/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by /__a/externals/node20/bin/node)
785-
// /__a/externals/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /__a/externals/node20/bin/node)
786-
if(nodeInfoLine.Contains("version `GLIBC_2.28' not found")
787-
|| nodeInfoLine.Contains("version `GLIBC_2.25' not found")
788-
|| nodeInfoLine.Contains("version `GLIBC_2.27' not found"))
789-
{
790-
executionContext.Debug($"GLIBC error found executing node -v; setting NeedsNode16Redirect: {nodeInfoLine}");
791-
executionContext.Warning($"The container operating system doesn't support Node20. Using Node16 instead. " +
792-
"Please upgrade the operating system of the container to ensure compatibility with Node20 tasks: " +
793-
"https://github.com/nodesource/distributions");
794-
795-
container.NeedsNode16Redirect = true;
796-
}
786+
PublishTelemetry(
787+
executionContext,
788+
new Dictionary<string, string>
789+
{
790+
{ "ContainerNode20to16Fallback", container.NeedsNode16Redirect.ToString() }
791+
}
792+
);
797793
}
798794
}
795+
799796
}
800797

801798
if (!string.IsNullOrEmpty(containerUserName))
802799
{
803800
container.CurrentUserName = containerUserName;
804801
}
805-
806-
if(!useNode20InUnsupportedSystem)
807-
{
808-
if(container.NeedsNode16Redirect)
809-
{
810-
container.CustomNodePath = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node16Folder, "bin", $"node{IOUtil.ExeExtension}"));
811-
}
812-
}
813802
}
814803
}
815804
}
@@ -1039,5 +1028,23 @@ private static void ThrowIfWrongWindowsVersion(IExecutionContext executionContex
10391028
throw new ArgumentOutOfRangeException(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ReleaseId");
10401029
}
10411030
}
1031+
1032+
private void PublishTelemetry(
1033+
IExecutionContext executionContext,
1034+
object telemetryData,
1035+
string feature = nameof(ContainerOperationProvider)
1036+
)
1037+
{
1038+
var cmd = new Command("telemetry", "publish")
1039+
{
1040+
Data = JsonConvert.SerializeObject(telemetryData, Formatting.None)
1041+
};
1042+
cmd.Properties.Add("area", "PipelinesTasks");
1043+
cmd.Properties.Add("feature", feature);
1044+
1045+
var publishTelemetryCmd = new TelemetryCommandExtension();
1046+
publishTelemetryCmd.Initialize(HostContext);
1047+
publishTelemetryCmd.ProcessCommand(executionContext, cmd);
1048+
}
10421049
}
10431050
}

src/Agent.Worker/ExecutionContext.cs

+44
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
using Microsoft.TeamFoundation.DistributedTask.WebApi;
1717
using Pipelines = Microsoft.TeamFoundation.DistributedTask.Pipelines;
1818
using Microsoft.VisualStudio.Services.Agent.Util;
19+
using Microsoft.VisualStudio.Services.Agent.Worker.Telemetry;
20+
using Newtonsoft.Json;
1921

2022
namespace Microsoft.VisualStudio.Services.Agent.Worker
2123
{
@@ -86,6 +88,7 @@ public interface IExecutionContext : IAgentService, IKnobValueContext
8688
/// </summary>
8789
/// <returns></returns>
8890
void CancelForceTaskCompletion();
91+
void EmitHostNode20FallbackTelemetry(bool node20ResultsInGlibCErrorHost);
8992
}
9093

9194
public sealed class ExecutionContext : AgentService, IExecutionContext, IDisposable
@@ -118,6 +121,7 @@ public sealed class ExecutionContext : AgentService, IExecutionContext, IDisposa
118121
private string _buildLogsFile;
119122
private FileStream _buildLogsData;
120123
private StreamWriter _buildLogsWriter;
124+
private bool emittedHostNode20FallbackTelemetry = false;
121125

122126
// only job level ExecutionContext will track throttling delay.
123127
private long _totalThrottlingDelayInMilliseconds = 0;
@@ -889,6 +893,46 @@ public void ReInitializeForceCompleted()
889893
this._forceCompleteCancellationTokenSource = new CancellationTokenSource();
890894
}
891895

896+
public void EmitHostNode20FallbackTelemetry(bool node20ResultsInGlibCErrorHost)
897+
{
898+
if (!emittedHostNode20FallbackTelemetry)
899+
{
900+
PublishTelemetry(new Dictionary<string, string>
901+
{
902+
{ "HostNode20to16Fallback", node20ResultsInGlibCErrorHost.ToString() }
903+
});
904+
905+
emittedHostNode20FallbackTelemetry = true;
906+
}
907+
}
908+
909+
// This overload is to handle specific types some other way.
910+
private void PublishTelemetry<T>(
911+
Dictionary<string, T> telemetryData,
912+
string feature = "TaskHandler"
913+
)
914+
{
915+
// JsonConvert.SerializeObject always converts to base object.
916+
PublishTelemetry((object)telemetryData, feature);
917+
}
918+
919+
private void PublishTelemetry(
920+
object telemetryData,
921+
string feature = "TaskHandler"
922+
)
923+
{
924+
var cmd = new Command("telemetry", "publish")
925+
{
926+
Data = JsonConvert.SerializeObject(telemetryData, Formatting.None)
927+
};
928+
cmd.Properties.Add("area", "PipelinesTasks");
929+
cmd.Properties.Add("feature", feature);
930+
931+
var publishTelemetryCmd = new TelemetryCommandExtension();
932+
publishTelemetryCmd.Initialize(HostContext);
933+
publishTelemetryCmd.ProcessCommand(this, cmd);
934+
}
935+
892936
public void Dispose()
893937
{
894938
_cancellationTokenSource?.Dispose();

0 commit comments

Comments
 (0)