Skip to content

Commit 64bbcaf

Browse files
authored
Skip HostIdValidator checks in placehlder mode (#10789) (#10801)
1 parent 4a65df8 commit 64bbcaf

File tree

4 files changed

+121
-11
lines changed

4 files changed

+121
-11
lines changed

src/WebJobs.Script/Host/ScriptHostIdProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public Task<string> GetHostIdAsync(CancellationToken cancellationToken)
3737
// if the user hasn't configured an explicit ID, we generate the default ID.
3838
HostIdResult result = GetDefaultHostId(_environment, _options.CurrentValue);
3939
hostId = result.HostId;
40-
if (result.IsTruncated && !result.IsLocal)
40+
if (result.IsTruncated && !result.IsLocal && !_options.CurrentValue.IsStandbyConfiguration)
4141
{
4242
_hostIdValidator.ScheduleValidation(hostId);
4343
}

test/WebJobs.Script.Tests.Integration/Host/StandbyManager/StandbyManagerE2ETestBase.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class StandbyManagerE2ETestBase : IDisposable
3636
protected readonly object _originalTimeZoneInfoCache = GetCachedTimeZoneInfo();
3737
protected TestMetricsLogger _metricsLogger;
3838

39+
private const string TestSiteName = "test-site-name";
40+
3941
public StandbyManagerE2ETestBase()
4042
{
4143
_testRootPath = Path.Combine(Path.GetTempPath(), "StandbyManagerTests");
@@ -44,7 +46,7 @@ public StandbyManagerE2ETestBase()
4446
StandbyManager.ResetChangeToken();
4547
}
4648

47-
protected async Task<IWebHostBuilder> CreateWebHostBuilderAsync(string testDirName, IEnvironment environment)
49+
protected async Task<IWebHostBuilder> CreateWebHostBuilderAsync(string testDirName, IEnvironment environment, string websiteSiteName = TestSiteName)
4850
{
4951
var httpConfig = new HttpConfiguration();
5052
var uniqueTestRootPath = Path.Combine(_testRootPath, testDirName, Guid.NewGuid().ToString());
@@ -63,7 +65,7 @@ protected async Task<IWebHostBuilder> CreateWebHostBuilderAsync(string testDirNa
6365
// if the test is mocking App Service environment, we need
6466
// to also set the HOME and WEBSITE_SITE_NAME variables
6567
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHomePath, uniqueTestRootPath);
66-
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName, "test-host-name");
68+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName, websiteSiteName);
6769
}
6870

6971
var webHostBuilder = Program.CreateWebHostBuilder()
@@ -113,9 +115,9 @@ protected async Task<IWebHostBuilder> CreateWebHostBuilderAsync(string testDirNa
113115
return webHostBuilder;
114116
}
115117

116-
protected async Task InitializeTestHostAsync(string testDirName, IEnvironment environment)
118+
protected async Task InitializeTestHostAsync(string testDirName, IEnvironment environment, string websiteSiteName = TestSiteName)
117119
{
118-
var webHostBuilder = await CreateWebHostBuilderAsync(testDirName, environment);
120+
var webHostBuilder = await CreateWebHostBuilderAsync(testDirName, environment, websiteSiteName);
119121
_httpServer = new TestServer(webHostBuilder);
120122
_httpClient = _httpServer.CreateClient();
121123
_httpClient.BaseAddress = new Uri("https://localhost/");

test/WebJobs.Script.Tests.Integration/Host/StandbyManager/StandbyManagerE2ETests_Windows.cs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
using System.Linq;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14+
using Azure.Storage.Blobs;
1415
using Microsoft.AspNetCore.Hosting;
16+
using Microsoft.Azure.WebJobs.Host.Storage;
1517
using Microsoft.Azure.WebJobs.Script.WebHost;
1618
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
1719
using Microsoft.Extensions.DependencyInjection;
1820
using Microsoft.WebJobs.Script.Tests;
1921
using Xunit;
22+
using static Microsoft.Azure.WebJobs.Script.HostIdValidator;
2023

2124
namespace Microsoft.Azure.WebJobs.Script.Tests
2225
{
@@ -28,6 +31,8 @@ public class StandbyManagerE2ETests_Windows : StandbyManagerE2ETestBase
2831

2932
public StandbyManagerE2ETests_Windows()
3033
{
34+
Utility.ColdStartDelayMS = 1000;
35+
3136
_settings = new Dictionary<string, string>()
3237
{
3338
{ EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1" },
@@ -67,7 +72,7 @@ public async Task ZipPackageFailure_DetectedOnSpecialization()
6772
Assert.True(environment.IsContainerReady());
6873

6974
// wait for shutdown to be triggered
70-
var applicationLifetime = host.Services.GetServices<IApplicationLifetime>().Single();
75+
var applicationLifetime = host.Services.GetServices<Microsoft.AspNetCore.Hosting.IApplicationLifetime>().Single();
7176
await TestHelpers.RunWithTimeoutAsync(() => applicationLifetime.ApplicationStopping.WaitHandle.WaitOneAsync(), TimeSpan.FromSeconds(30));
7277

7378
// ensure the host was specialized and the expected error was logged
@@ -136,6 +141,105 @@ await TestHelpers.Await(() =>
136141
Assert.NotSame(GetCachedTimeZoneInfo(), _originalTimeZoneInfoCache);
137142
}
138143

144+
[Fact]
145+
public async Task StandbyModeE2E_Dotnet_HostIdValidator_DoesNotRunInPlaceholderMode()
146+
{
147+
_settings.Add(EnvironmentSettingNames.AzureWebsiteInstanceId, Guid.NewGuid().ToString());
148+
149+
string siteName = "areallylongnamethatexceedsthemaxhostidlength";
150+
string expectedHostId = siteName.Substring(0, 32);
151+
string expectedHostName = $"{siteName}.azurewebsites.net";
152+
153+
string placeholderSiteName = "functionsv4x86inproc8placeholdertemplatesite";
154+
string placeholderHostId = placeholderSiteName.Substring(0, 32);
155+
string placeholderHostName = $"{placeholderSiteName}.azurewebsites.net";
156+
157+
var environment = new TestEnvironment(_settings);
158+
await InitializeTestHostAsync("Windows", environment, placeholderSiteName);
159+
160+
// Directly configure a bad placeholder mode host ID record.
161+
// Before the fix for this bug, such records were being generated
162+
// as part of a specialization race condition.
163+
var serviceProvider = _httpServer.Host.Services;
164+
var blobStorageProvider = serviceProvider.GetService<IAzureBlobStorageProvider>();
165+
Assert.True(blobStorageProvider.TryCreateHostingBlobContainerClient(out var blobContainerClient));
166+
var hostIdInfo = new HostIdValidator.HostIdInfo { Hostname = expectedHostName };
167+
string blobPath = string.Format(BlobPathFormat, placeholderHostId);
168+
BlobClient blobClient = blobContainerClient.GetBlobClient(blobPath);
169+
BinaryData data = BinaryData.FromObjectAsJson(hostIdInfo);
170+
await blobClient.UploadAsync(data, overwrite: true);
171+
172+
await VerifyWarmupSucceeds();
173+
await VerifyWarmupSucceeds(restart: true);
174+
175+
// before specialization, the hostname will contain the placeholder site name
176+
var hostNameProvider = serviceProvider.GetService<HostNameProvider>();
177+
Assert.Equal(placeholderHostName, hostNameProvider.Value);
178+
179+
// allow time for any scheduled host ID check to happen
180+
await Task.Delay(Utility.ColdStartDelayMS);
181+
182+
// now specialize the host
183+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "0");
184+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteContainerReady, "1");
185+
environment.SetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeSettingName, "dotnet");
186+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName, siteName);
187+
188+
Assert.False(environment.IsPlaceholderModeEnabled());
189+
Assert.True(environment.IsContainerReady());
190+
191+
// give time for the specialization to happen
192+
string[] logLines = null;
193+
LogMessage[] errors = null;
194+
await TestHelpers.Await(() =>
195+
{
196+
errors = _loggerProvider.GetAllLogMessages().Where(p => p.Level == Microsoft.Extensions.Logging.LogLevel.Error && p.EventId.Id != 302).ToArray();
197+
if (errors.Any())
198+
{
199+
// one or more unexpected errors
200+
return true;
201+
}
202+
203+
// wait for the trace indicating that the host has been specialized
204+
logLines = _loggerProvider.GetAllLogMessages().Where(p => p.FormattedMessage != null).Select(p => p.FormattedMessage).ToArray();
205+
206+
return logLines.Contains("Generating 0 job function(s)") && logLines.Contains("Stopping JobHost");
207+
}, userMessageCallback: () => string.Join(Environment.NewLine, _loggerProvider.GetAllLogMessages().Select(p => $"[{p.Timestamp.ToString("HH:mm:ss.fff")}] {p.FormattedMessage}")));
208+
209+
Assert.Empty(errors);
210+
211+
var logs = _loggerProvider.GetAllLogMessages().ToArray();
212+
213+
// after specialization, the hostname changes
214+
Assert.Equal(expectedHostName, hostNameProvider.Value);
215+
216+
// verify the rest of the expected logs
217+
logLines = logs.Where(p => p.FormattedMessage != null).Select(p => p.FormattedMessage).ToArray();
218+
Assert.True(logLines.Count(p => p.Contains("Stopping JobHost")) >= 1);
219+
Assert.Equal(1, logLines.Count(p => p.Contains("Creating StandbyMode placeholder function directory")));
220+
Assert.Equal(1, logLines.Count(p => p.Contains("StandbyMode placeholder function directory created")));
221+
Assert.Equal(2, logLines.Count(p => p.Contains("Host is in standby mode")));
222+
Assert.Equal(2, logLines.Count(p => p.Contains("Executed 'Functions.WarmUp' (Succeeded")));
223+
Assert.Equal(1, logLines.Count(p => p.Contains("Starting host specialization")));
224+
Assert.Equal(1, logLines.Count(p => p.Contains("Starting language worker channel specialization")));
225+
Assert.Equal(6, logLines.Count(p => p.Contains($"Loading functions metadata")));
226+
Assert.Equal(4, logLines.Count(p => p.Contains($"1 functions loaded")));
227+
Assert.Equal(2, logLines.Count(p => p.Contains($"0 functions loaded")));
228+
Assert.Contains("Generating 0 job function(s)", logLines);
229+
230+
// we expect to see host startup logs for both the placeholder site as well as the
231+
// specialized site
232+
Assert.Equal(2, logLines.Count(p => p.Contains($"Starting Host (HostId={placeholderHostId}")));
233+
Assert.Equal(1, logLines.Count(p => p.Contains($"Starting Host (HostId={expectedHostId}")));
234+
235+
// allow time for any scheduled host ID check to happen
236+
await Task.Delay(Utility.ColdStartDelayMS);
237+
238+
// Don't expect any errors (other than bundle download errors)
239+
errors = _loggerProvider.GetAllLogMessages().Where(p => p.Level == Microsoft.Extensions.Logging.LogLevel.Error && p.EventId.Id != 302).ToArray();
240+
Assert.Empty(errors);
241+
}
242+
139243
[Fact(Skip = "https://github.com/Azure/azure-functions-host/issues/7805")]
140244
public async Task InitializeAsync_WithSpecializedSite_SkipsWarmupFunctionsAndLogs()
141245
{

test/WebJobs.Script.Tests/ScriptHostIdProviderTests.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ public class ScriptHostIdProviderTests
2323
private readonly Mock<IConfiguration> _mockConfiguration;
2424
private readonly HostIdValidator _hostIdValidator;
2525
private readonly TestEnvironment _environment;
26+
private readonly ScriptApplicationHostOptions _scriptApplicationHostOptions;
2627

2728
public ScriptHostIdProviderTests()
2829
{
29-
var options = new ScriptApplicationHostOptions();
30+
_scriptApplicationHostOptions = new ScriptApplicationHostOptions();
3031
_environment = new TestEnvironment();
3132

3233
_mockConfiguration = new Mock<IConfiguration>(MockBehavior.Strict);
@@ -39,7 +40,7 @@ public ScriptHostIdProviderTests()
3940
var mockStorageProvider = new Mock<IAzureBlobStorageProvider>(MockBehavior.Strict);
4041
var mockApplicationLifetime = new Mock<IApplicationLifetime>(MockBehavior.Strict);
4142
_hostIdValidator = new HostIdValidator(_environment, mockStorageProvider.Object, mockApplicationLifetime.Object, hostNameProvider, logger);
42-
_provider = new ScriptHostIdProvider(_mockConfiguration.Object, _environment, new TestOptionsMonitor<ScriptApplicationHostOptions>(options), _hostIdValidator);
43+
_provider = new ScriptHostIdProvider(_mockConfiguration.Object, _environment, new TestOptionsMonitor<ScriptApplicationHostOptions>(_scriptApplicationHostOptions), _hostIdValidator);
4344
}
4445

4546
[Fact]
@@ -53,10 +54,13 @@ public async Task GetHostIdAsync_WithConfigurationHostId_ReturnsConfigurationHos
5354
}
5455

5556
[Theory]
56-
[InlineData("TEST-FUNCTIONS--", "123123", "test-functions", false)]
57-
[InlineData("TEST-FUNCTIONS-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "123123", "test-functions-xxxxxxxxxxxxxxxxx", true)]
58-
public async Task GetHostIdAsync_AzureHost_ReturnsExpectedResult(string siteName, string azureWebsiteInstanceId, string expected, bool validationExpected)
57+
[InlineData("TEST-FUNCTIONS--", "123123", "test-functions", false, false)]
58+
[InlineData("TEST-FUNCTIONS-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "123123", "test-functions-xxxxxxxxxxxxxxxxx", true, false)]
59+
[InlineData("functionsv4x86inproc8placeholdertemplatesite", "123123", "functionsv4x86inproc8placeholder", false, true)]
60+
public async Task GetHostIdAsync_AzureHost_ReturnsExpectedResult(string siteName, string azureWebsiteInstanceId, string expected, bool validationExpected, bool inPlaceholderMode)
5961
{
62+
_scriptApplicationHostOptions.IsStandbyConfiguration = inPlaceholderMode;
63+
6064
_mockConfiguration.SetupGet(p => p[ConfigurationSectionNames.HostIdPath]).Returns((string)null);
6165

6266
_environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteInstanceId, azureWebsiteInstanceId);

0 commit comments

Comments
 (0)