diff --git a/src/Runner.Listener/JobDispatcher.cs b/src/Runner.Listener/JobDispatcher.cs index 97422ee47..6d003d026 100644 --- a/src/Runner.Listener/JobDispatcher.cs +++ b/src/Runner.Listener/JobDispatcher.cs @@ -36,7 +36,10 @@ namespace GitHub.Runner.Listener { private readonly Lazy> _localRunJobResult = new Lazy>(); private int _poolId; - RunnerSettings _runnerSetting; + + IConfigurationStore _configurationStore; + + RunnerSettings _runnerSettings; private static readonly string _workerProcessName = $"Runner.Worker{IOUtil.ExeExtension}"; // this is not thread-safe @@ -54,9 +57,9 @@ namespace GitHub.Runner.Listener base.Initialize(hostContext); // get pool id from config - var configurationStore = hostContext.GetService(); - _runnerSetting = configurationStore.GetSettings(); - _poolId = _runnerSetting.PoolId; + _configurationStore = hostContext.GetService(); + _runnerSettings = _configurationStore.GetSettings(); + _poolId = _runnerSettings.PoolId; int channelTimeoutSeconds; if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT") ?? string.Empty, out channelTimeoutSeconds)) @@ -661,13 +664,15 @@ namespace GitHub.Runner.Listener try { request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token); - Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}"); if (!firstJobRequestRenewed.Task.IsCompleted) { // fire first renew succeed event. firstJobRequestRenewed.TrySetResult(0); + + // Update settings if the runner name has been changed server-side + UpdateAgentNameIfNeeded(request.ReservedAgent?.Name); } if (encounteringError > 0) @@ -767,6 +772,27 @@ namespace GitHub.Runner.Listener } } + private void UpdateAgentNameIfNeeded(string agentName) + { + var isNewAgentName = !string.Equals(_runnerSettings.AgentName, agentName, StringComparison.Ordinal); + if (!isNewAgentName || string.IsNullOrEmpty(agentName)) + { + return; + } + + _runnerSettings.AgentName = agentName; + try + { + _configurationStore.SaveSettings(_runnerSettings); + } + catch (Exception ex) + { + Trace.Error("Cannot update the settings file:"); + Trace.Error(ex); + } + + } + // Best effort upload any logs for this job. private async Task TryUploadUnfinishedLogs(Pipelines.AgentJobRequestMessage message) { diff --git a/src/Runner.Worker/JobRunner.cs b/src/Runner.Worker/JobRunner.cs index 7f67ed03b..43c5f5f82 100644 --- a/src/Runner.Worker/JobRunner.cs +++ b/src/Runner.Worker/JobRunner.cs @@ -106,6 +106,9 @@ namespace GitHub.Runner.Worker jobContext.SetRunnerContext("os", VarUtil.OS); + var runnerSettings = HostContext.GetService().GetSettings(); + jobContext.SetRunnerContext("name", runnerSettings.AgentName); + string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools); Directory.CreateDirectory(toolsDirectory); jobContext.SetRunnerContext("tool_cache", toolsDirectory); diff --git a/src/Test/L0/Listener/JobDispatcherL0.cs b/src/Test/L0/Listener/JobDispatcherL0.cs index a8062b206..f06005f13 100644 --- a/src/Test/L0/Listener/JobDispatcherL0.cs +++ b/src/Test/L0/Listener/JobDispatcherL0.cs @@ -264,6 +264,170 @@ namespace GitHub.Runner.Common.Tests.Listener } } + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Runner")] + public async void RenewJobRequestNewAgentNameUpdatesSettings() + { + //Arrange + using (var hc = new TestHostContext(this)) + { + var count = 0; + var oldName = "OldName"; + var newName = "NewName"; + var oldSettings = new RunnerSettings { AgentName = oldName }; + var reservedAgent = new TaskAgentReference { Name = newName }; + + var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions)); + TaskCompletionSource firstJobRequestRenewed = new TaskCompletionSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + var request = new Mock(); + request.Object.ReservedAgent = reservedAgent; + PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + Assert.NotNull(lockUntilProperty); + lockUntilProperty.SetValue(request.Object, DateTime.UtcNow.AddMinutes(5)); + hc.SetSingleton(_runnerServer.Object); + hc.SetSingleton(_configurationStore.Object); + _configurationStore.Setup(x => x.GetSettings()).Returns(oldSettings); + _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(() => + { + count++; + if (count < 5) + { + return Task.FromResult(request.Object); + } + else if (count == 5 || count == 6 || count == 7) + { + throw new TimeoutException(""); + } + else + { + cancellationTokenSource.Cancel(); + return Task.FromResult(request.Object); + } + }); + + var jobDispatcher = new JobDispatcher(); + jobDispatcher.Initialize(hc); + + // Act + await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token); + + // Assert + _configurationStore.Verify(x => x.SaveSettings(It.Is(settings => settings.AgentName == newName)), Times.Once); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Runner")] + public async void RenewJobRequestSameAgentNameIgnored() + { + //Arrange + using (var hc = new TestHostContext(this)) + { + var count = 0; + var oldName = "OldName"; + var newName = "OldName"; + var oldSettings = new RunnerSettings { AgentName = oldName }; + var reservedAgent = new TaskAgentReference { Name = newName }; + + var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions)); + TaskCompletionSource firstJobRequestRenewed = new TaskCompletionSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + var request = new Mock(); + request.Object.ReservedAgent = reservedAgent; + PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + Assert.NotNull(lockUntilProperty); + lockUntilProperty.SetValue(request.Object, DateTime.UtcNow.AddMinutes(5)); + hc.SetSingleton(_runnerServer.Object); + hc.SetSingleton(_configurationStore.Object); + _configurationStore.Setup(x => x.GetSettings()).Returns(oldSettings); + _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(() => + { + count++; + if (count < 5) + { + return Task.FromResult(request.Object); + } + else if (count == 5 || count == 6 || count == 7) + { + throw new TimeoutException(""); + } + else + { + cancellationTokenSource.Cancel(); + return Task.FromResult(request.Object); + } + }); + var jobDispatcher = new JobDispatcher(); + jobDispatcher.Initialize(hc); + + // Act + await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token); + + // Assert + _configurationStore.Verify(x => x.SaveSettings(It.IsAny()), Times.Never); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Runner")] + public async void RenewJobRequestNullAgentNameIgnored() + { + //Arrange + using (var hc = new TestHostContext(this)) + { + var count = 0; + var oldName = "OldName"; + var oldSettings = new RunnerSettings { AgentName = oldName }; + + var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions)); + TaskCompletionSource firstJobRequestRenewed = new TaskCompletionSource(); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + var request = new Mock(); + PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + Assert.NotNull(lockUntilProperty); + lockUntilProperty.SetValue(request.Object, DateTime.UtcNow.AddMinutes(5)); + hc.SetSingleton(_runnerServer.Object); + hc.SetSingleton(_configurationStore.Object); + _configurationStore.Setup(x => x.GetSettings()).Returns(oldSettings); + _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(() => + { + count++; + if (count < 5) + { + return Task.FromResult(request.Object); + } + else if (count == 5 || count == 6 || count == 7) + { + throw new TimeoutException(""); + } + else + { + cancellationTokenSource.Cancel(); + return Task.FromResult(request.Object); + } + }); + + var jobDispatcher = new JobDispatcher(); + jobDispatcher.Initialize(hc); + + // Act + await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token); + + // Assert + _configurationStore.Verify(x => x.SaveSettings(It.IsAny()), Times.Never); + } + } + [Fact] [Trait("Level", "L0")] [Trait("Category", "Runner")]