11
11
using System . Linq ;
12
12
using System . Threading ;
13
13
using System . Threading . Tasks ;
14
+ using Azure . Storage . Blobs ;
14
15
using Microsoft . AspNetCore . Hosting ;
16
+ using Microsoft . Azure . WebJobs . Host . Storage ;
15
17
using Microsoft . Azure . WebJobs . Script . WebHost ;
16
18
using Microsoft . Azure . WebJobs . Script . Workers . Rpc ;
17
19
using Microsoft . Extensions . DependencyInjection ;
18
20
using Microsoft . WebJobs . Script . Tests ;
19
21
using Xunit ;
22
+ using static Microsoft . Azure . WebJobs . Script . HostIdValidator ;
20
23
21
24
namespace Microsoft . Azure . WebJobs . Script . Tests
22
25
{
@@ -28,6 +31,8 @@ public class StandbyManagerE2ETests_Windows : StandbyManagerE2ETestBase
28
31
29
32
public StandbyManagerE2ETests_Windows ( )
30
33
{
34
+ Utility . ColdStartDelayMS = 1000 ;
35
+
31
36
_settings = new Dictionary < string , string > ( )
32
37
{
33
38
{ EnvironmentSettingNames . AzureWebsitePlaceholderMode , "1" } ,
@@ -67,7 +72,7 @@ public async Task ZipPackageFailure_DetectedOnSpecialization()
67
72
Assert . True ( environment . IsContainerReady ( ) ) ;
68
73
69
74
// 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 ( ) ;
71
76
await TestHelpers . RunWithTimeoutAsync ( ( ) => applicationLifetime . ApplicationStopping . WaitHandle . WaitOneAsync ( ) , TimeSpan . FromSeconds ( 30 ) ) ;
72
77
73
78
// ensure the host was specialized and the expected error was logged
@@ -168,6 +173,105 @@ await TestHelpers.Await(() =>
168
173
Assert . NotSame ( GetCachedTimeZoneInfo ( ) , _originalTimeZoneInfoCache ) ;
169
174
}
170
175
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
+
171
275
[ Fact ( Skip = "https://github.com/Azure/azure-functions-host/issues/7805" ) ]
172
276
public async Task InitializeAsync_WithSpecializedSite_SkipsWarmupFunctionsAndLogs ( )
173
277
{
0 commit comments