3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics ;
6
7
using System . IO ;
7
8
using System . IO . Compression ;
8
9
using System . Net . Http ;
9
10
using System . Threading . Tasks ;
11
+ using Microsoft . Azure . WebJobs . Script . Diagnostics ;
10
12
using Microsoft . Azure . WebJobs . Script . WebHost . Configuration ;
11
13
using Microsoft . Azure . WebJobs . Script . WebHost . Models ;
12
14
using Microsoft . Extensions . Logging ;
@@ -20,17 +22,19 @@ public class InstanceManager : IInstanceManager
20
22
private static HostAssignmentContext _assignmentContext ;
21
23
22
24
private readonly ILogger _logger ;
25
+ private readonly IMetricsLogger _metricsLogger ;
23
26
private readonly IEnvironment _environment ;
24
27
private readonly IOptionsFactory < ScriptApplicationHostOptions > _optionsFactory ;
25
28
private readonly HttpClient _client ;
26
29
private readonly IScriptWebHostEnvironment _webHostEnvironment ;
27
30
28
31
public InstanceManager ( IOptionsFactory < ScriptApplicationHostOptions > optionsFactory , HttpClient client , IScriptWebHostEnvironment webHostEnvironment ,
29
- IEnvironment environment , ILogger < InstanceManager > logger )
32
+ IEnvironment environment , ILogger < InstanceManager > logger , IMetricsLogger metricsLogger )
30
33
{
31
34
_client = client ?? throw new ArgumentNullException ( nameof ( client ) ) ;
32
35
_webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException ( nameof ( webHostEnvironment ) ) ;
33
36
_logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
37
+ _metricsLogger = metricsLogger ;
34
38
_environment = environment ?? throw new ArgumentNullException ( nameof ( environment ) ) ;
35
39
_optionsFactory = optionsFactory ?? throw new ArgumentNullException ( nameof ( optionsFactory ) ) ;
36
40
}
@@ -80,21 +84,40 @@ public async Task<string> ValidateContext(HostAssignmentContext assignmentContex
80
84
{
81
85
_logger . LogInformation ( $ "Validating host assignment context (SiteId: { assignmentContext . SiteId } , SiteName: '{ assignmentContext . SiteName } ')") ;
82
86
83
- var zipUrl = assignmentContext . ZipUrl ;
84
- if ( ! string . IsNullOrEmpty ( zipUrl ) )
87
+ string error = null ;
88
+ HttpResponseMessage response = null ;
89
+ try
85
90
{
86
- // make sure the zip uri is valid and accessible
87
- var request = new HttpRequestMessage ( HttpMethod . Head , zipUrl ) ;
88
- var response = await _client . SendAsync ( request ) ;
89
- if ( ! response . IsSuccessStatusCode )
91
+ var zipUrl = assignmentContext . ZipUrl ;
92
+ if ( ! string . IsNullOrEmpty ( zipUrl ) )
90
93
{
91
- string error = $ "Invalid zip url specified (StatusCode: { response . StatusCode } )";
92
- _logger . LogError ( error ) ;
93
- return error ;
94
+ // make sure the zip uri is valid and accessible
95
+ await Utility . InvokeWithRetriesAsync ( async ( ) =>
96
+ {
97
+ try
98
+ {
99
+ using ( _metricsLogger . LatencyEvent ( MetricEventNames . LinuxContainerSpecializationZipHead ) )
100
+ {
101
+ var request = new HttpRequestMessage ( HttpMethod . Head , zipUrl ) ;
102
+ response = await _client . SendAsync ( request ) ;
103
+ response . EnsureSuccessStatusCode ( ) ;
104
+ }
105
+ }
106
+ catch ( Exception e )
107
+ {
108
+ _logger . LogError ( e , $ "{ MetricEventNames . LinuxContainerSpecializationZipHead } failed") ;
109
+ throw ;
110
+ }
111
+ } , maxRetries : 2 , retryInterval : TimeSpan . FromSeconds ( 0.3 ) ) ; // Keep this less than ~1s total
94
112
}
95
113
}
114
+ catch ( Exception e )
115
+ {
116
+ error = $ "Invalid zip url specified (StatusCode: { response ? . StatusCode } )";
117
+ _logger . LogError ( e , "ValidateContext failed" ) ;
118
+ }
96
119
97
- return null ;
120
+ return error ;
98
121
}
99
122
100
123
private async Task Assign ( HostAssignmentContext assignmentContext )
@@ -140,32 +163,55 @@ private async Task ApplyContext(HostAssignmentContext assignmentContext)
140
163
var filePath = Path . GetTempFileName ( ) ;
141
164
await DownloadAsync ( zipUri , filePath ) ;
142
165
143
- _logger . LogInformation ( $ "Extracting files to '{ options . ScriptPath } '") ;
144
- ZipFile . ExtractToDirectory ( filePath , options . ScriptPath , overwriteFiles : true ) ;
145
- _logger . LogInformation ( $ "Zip extraction complete") ;
166
+ using ( _metricsLogger . LatencyEvent ( MetricEventNames . LinuxContainerSpecializationZipExtract ) )
167
+ {
168
+ _logger . LogInformation ( $ "Extracting files to '{ options . ScriptPath } '") ;
169
+ ZipFile . ExtractToDirectory ( filePath , options . ScriptPath , overwriteFiles : true ) ;
170
+ _logger . LogInformation ( $ "Zip extraction complete") ;
171
+ }
146
172
}
147
173
}
148
174
149
175
private async Task DownloadAsync ( Uri zipUri , string filePath )
150
176
{
151
- var zipPath = $ "{ zipUri . Authority } { zipUri . AbsolutePath } ";
152
- _logger . LogInformation ( $ "Downloading zip contents from '{ zipPath } ' to temp file '{ filePath } '") ;
177
+ string cleanedUrl ;
178
+ Utility . TryCleanUrl ( zipUri . AbsoluteUri , out cleanedUrl ) ;
179
+
180
+ _logger . LogInformation ( $ "Downloading zip contents from '{ cleanedUrl } ' to temp file '{ filePath } '") ;
181
+
182
+ HttpResponseMessage response = null ;
153
183
154
- var response = await _client . GetAsync ( zipUri ) ;
155
- if ( ! response . IsSuccessStatusCode )
184
+ await Utility . InvokeWithRetriesAsync ( async ( ) =>
156
185
{
157
- string error = $ "Error downloading zip content { zipPath } ";
158
- _logger . LogError ( error ) ;
159
- throw new InvalidDataException ( error ) ;
160
- }
186
+ try
187
+ {
188
+ using ( _metricsLogger . LatencyEvent ( MetricEventNames . LinuxContainerSpecializationZipDownload ) )
189
+ {
190
+ var request = new HttpRequestMessage ( HttpMethod . Get , zipUri ) ;
191
+ response = await _client . SendAsync ( request ) ;
192
+ response . EnsureSuccessStatusCode ( ) ;
193
+ }
194
+ }
195
+ catch ( Exception e )
196
+ {
197
+ string error = $ "Error downloading zip content { cleanedUrl } ";
198
+ _logger . LogError ( e , error ) ;
199
+ throw ;
200
+ }
161
201
162
- using ( var content = await response . Content . ReadAsStreamAsync ( ) )
163
- using ( var stream = new FileStream ( filePath , FileMode . Create , FileAccess . Write , FileShare . None , bufferSize : 4096 , useAsync : true ) )
202
+ _logger . LogInformation ( $ "{ response . Content . Headers . ContentLength } bytes downloaded") ;
203
+ } , 2 , TimeSpan . FromSeconds ( 0.5 ) ) ;
204
+
205
+ using ( _metricsLogger . LatencyEvent ( MetricEventNames . LinuxContainerSpecializationZipWrite ) )
164
206
{
165
- await content . CopyToAsync ( stream ) ;
166
- }
207
+ using ( var content = await response . Content . ReadAsStreamAsync ( ) )
208
+ using ( var stream = new FileStream ( filePath , FileMode . Create , FileAccess . Write , FileShare . None , bufferSize : 4096 , useAsync : true ) )
209
+ {
210
+ await content . CopyToAsync ( stream ) ;
211
+ }
167
212
168
- _logger . LogInformation ( $ "{ response . Content . Headers . ContentLength } bytes downloaded") ;
213
+ _logger . LogInformation ( $ "{ response . Content . Headers . ContentLength } bytes written") ;
214
+ }
169
215
}
170
216
171
217
public IDictionary < string , string > GetInstanceInfo ( )
0 commit comments