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
@@ -136,6 +141,105 @@ await TestHelpers.Await(() =>
136
141
Assert . NotSame ( GetCachedTimeZoneInfo ( ) , _originalTimeZoneInfoCache ) ;
137
142
}
138
143
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
+
139
243
[ Fact ( Skip = "https://github.com/Azure/azure-functions-host/issues/7805" ) ]
140
244
public async Task InitializeAsync_WithSpecializedSite_SkipsWarmupFunctionsAndLogs ( )
141
245
{
0 commit comments