Skip to content

Commit 30cafb8

Browse files
authored
Skip HostIdValidator checks in placehlder mode (#10789)
1 parent e57d37f commit 30cafb8

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
@@ -168,6 +173,105 @@ await TestHelpers.Await(() =>
168173
Assert.NotSame(GetCachedTimeZoneInfo(), _originalTimeZoneInfoCache);
169174
}
170175

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

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)