Skip to content

Commit 901d8b6

Browse files
TonewallTony Choi
andauthored
InstanceManager for Legion Runtime (#8634)
* Removed mounting/unzipping app content and runfrompackage validations * Removed unnecessary methods * removed methods * Unit tests for LegionInstanceManager * dependency injection * resolving comments * resolving comment * dependency injection * resolving comments * synchronous assign * removing duplicate services * removed script * created a base abstract class to eliminate code duplication * changing linuxinstancemanager to iinstancemanager * iinstancemanager * resolving comments * fixing tests * adding todo * Adding ernv setting from rebase * remove env name * env * rebase * using instance fields * nit * instance fields * websitenodefaultversion constant * constant ordering Co-authored-by: Tony Choi <[email protected]>
1 parent ba112b7 commit 901d8b6

File tree

9 files changed

+532
-169
lines changed

9 files changed

+532
-169
lines changed

src/WebJobs.Script.WebHost/Management/InstanceManager.cs renamed to src/WebJobs.Script.WebHost/Management/AtlasInstanceManager.cs

Lines changed: 14 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@
1919

2020
namespace Microsoft.Azure.WebJobs.Script.WebHost.Management
2121
{
22-
public class InstanceManager : IInstanceManager
22+
public class AtlasInstanceManager : LinuxInstanceManager
2323
{
24-
private static readonly object _assignmentLock = new object();
25-
private static HostAssignmentContext _assignmentContext;
24+
private readonly object _assignmentLock = new object();
2625

2726
private readonly ILogger _logger;
2827
private readonly IMetricsLogger _metricsLogger;
@@ -34,9 +33,10 @@ public class InstanceManager : IInstanceManager
3433
private readonly HttpClient _client;
3534
private readonly IScriptWebHostEnvironment _webHostEnvironment;
3635

37-
public InstanceManager(IOptionsFactory<ScriptApplicationHostOptions> optionsFactory, IHttpClientFactory httpClientFactory, IScriptWebHostEnvironment webHostEnvironment,
38-
IEnvironment environment, ILogger<InstanceManager> logger, IMetricsLogger metricsLogger, IMeshServiceClient meshServiceClient, IRunFromPackageHandler runFromPackageHandler,
39-
IPackageDownloadHandler packageDownloadHandler)
36+
public AtlasInstanceManager(IOptionsFactory<ScriptApplicationHostOptions> optionsFactory, IHttpClientFactory httpClientFactory, IScriptWebHostEnvironment webHostEnvironment,
37+
IEnvironment environment, ILogger<AtlasInstanceManager> logger, IMetricsLogger metricsLogger, IMeshServiceClient meshServiceClient, IRunFromPackageHandler runFromPackageHandler,
38+
IPackageDownloadHandler packageDownloadHandler) : base(httpClientFactory, webHostEnvironment,
39+
environment, logger, metricsLogger, meshServiceClient)
4040
{
4141
_client = httpClientFactory?.CreateClient() ?? throw new ArgumentNullException(nameof(httpClientFactory));
4242
_webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
@@ -49,7 +49,7 @@ public InstanceManager(IOptionsFactory<ScriptApplicationHostOptions> optionsFact
4949
_optionsFactory = optionsFactory ?? throw new ArgumentNullException(nameof(optionsFactory));
5050
}
5151

52-
public async Task<string> SpecializeMSISidecar(HostAssignmentContext context)
52+
public override async Task<string> SpecializeMSISidecar(HostAssignmentContext context)
5353
{
5454
// No cold start optimization needed for side car scenarios
5555
if (context.IsWarmupRequest)
@@ -103,65 +103,7 @@ await _meshServiceClient.NotifyHealthEvent(ContainerHealthEventType.Fatal, this.
103103
return null;
104104
}
105105

106-
public bool StartAssignment(HostAssignmentContext context)
107-
{
108-
if (!_webHostEnvironment.InStandbyMode)
109-
{
110-
// This is only true when specializing pinned containers.
111-
if (!context.Environment.TryGetValue(EnvironmentSettingNames.ContainerStartContext, out string startContext))
112-
{
113-
_logger.LogError("Assign called while host is not in placeholder mode and start context is not present.");
114-
return false;
115-
}
116-
}
117-
118-
if (_environment.IsContainerReady())
119-
{
120-
_logger.LogError("Assign called while container is marked as specialized.");
121-
return false;
122-
}
123-
124-
if (context.IsWarmupRequest)
125-
{
126-
// Based on profiling download code jit-ing holds up cold start.
127-
// Pre-jit to avoid paying the cost later.
128-
Task.Run(async () => await _packageDownloadHandler.Download(context.GetRunFromPkgContext()));
129-
return true;
130-
}
131-
else if (_assignmentContext == null)
132-
{
133-
lock (_assignmentLock)
134-
{
135-
if (_assignmentContext != null)
136-
{
137-
return _assignmentContext.Equals(context);
138-
}
139-
_assignmentContext = context;
140-
}
141-
142-
_logger.LogInformation($"Starting Assignment. Cloud Name: {_environment.GetCloudName()}");
143-
144-
// set a flag which will cause any incoming http requests to buffer
145-
// until specialization is complete
146-
// the host is guaranteed not to receive any requests until AFTER assign
147-
// has been initiated, so setting this flag here is sufficient to ensure
148-
// that any subsequent incoming requests while the assign is in progress
149-
// will be delayed until complete
150-
_webHostEnvironment.DelayRequests();
151-
152-
// start the specialization process in the background
153-
Task.Run(async () => await Assign(context));
154-
155-
return true;
156-
}
157-
else
158-
{
159-
// No lock needed here since _assignmentContext is not null when we are here
160-
return _assignmentContext.Equals(context);
161-
}
162-
}
163-
164-
public async Task<string> ValidateContext(HostAssignmentContext assignmentContext)
106+
public override async Task<string> ValidateContext(HostAssignmentContext assignmentContext)
165107
{
166108
_logger.LogInformation($"Validating host assignment context (SiteId: {assignmentContext.SiteId}, SiteName: '{assignmentContext.SiteName}'. IsWarmup: '{assignmentContext.IsWarmupRequest}')");
167109
RunFromPackageContext pkgContext = assignmentContext.GetRunFromPkgContext();
@@ -214,6 +156,11 @@ public async Task<string> ValidateContext(HostAssignmentContext assignmentContex
214156
}
215157
}
216158

159+
protected override async Task<string> DownloadWarmupAsync(RunFromPackageContext context)
160+
{
161+
return await _packageDownloadHandler.Download(context);
162+
}
163+
217164
private async Task<(string Error, long? ContentLength)> ValidateBlobPackageContext(RunFromPackageContext context)
218165
{
219166
string blobUri = context.Url;
@@ -277,38 +224,8 @@ private async Task<string> ValidateAzureFilesContext(string connectionString, st
277224
}
278225
}
279226

280-
private async Task Assign(HostAssignmentContext assignmentContext)
227+
protected override async Task ApplyContextAsync(HostAssignmentContext assignmentContext)
281228
{
282-
try
283-
{
284-
// first make all environment and file system changes required for
285-
// the host to be specialized
286-
await ApplyContext(assignmentContext);
287-
}
288-
catch (Exception ex)
289-
{
290-
_logger.LogError(ex, "Assign failed");
291-
await _meshServiceClient.NotifyHealthEvent(ContainerHealthEventType.Fatal, GetType(), "Assign failed");
292-
throw;
293-
}
294-
finally
295-
{
296-
// all assignment settings/files have been applied so we can flip
297-
// the switch now on specialization
298-
// even if there are failures applying context above, we want to
299-
// leave placeholder mode
300-
_logger.LogInformation("Triggering specialization");
301-
_webHostEnvironment.FlagAsSpecializedAndReady();
302-
303-
_webHostEnvironment.ResumeRequests();
304-
}
305-
}
306-
307-
private async Task ApplyContext(HostAssignmentContext assignmentContext)
308-
{
309-
_logger.LogInformation($"Applying {assignmentContext.Environment.Count} app setting(s)");
310-
assignmentContext.ApplyAppSettings(_environment, _logger);
311-
312229
// We need to get the non-PlaceholderMode script Path so we can unzip to the correct location.
313230
// This asks the factory to skip the PlaceholderMode check when configuring options.
314231
var options = _optionsFactory.Create(ScriptApplicationHostOptionsSetup.SkipPlaceholder);
@@ -447,20 +364,5 @@ await Utility.InvokeWithRetriesAsync(async () =>
447364
return false;
448365
}
449366
}
450-
451-
public IDictionary<string, string> GetInstanceInfo()
452-
{
453-
return new Dictionary<string, string>
454-
{
455-
{ "FUNCTIONS_EXTENSION_VERSION", ScriptHost.Version },
456-
{ "WEBSITE_NODE_DEFAULT_VERSION", "8.5.0" }
457-
};
458-
}
459-
460-
// for testing
461-
internal static void Reset()
462-
{
463-
_assignmentContext = null;
464-
}
465367
}
466368
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
11+
using Microsoft.Azure.WebJobs.Script.WebHost.Management.LinuxSpecialization;
12+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
using Newtonsoft.Json;
16+
17+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Management
18+
{
19+
public class LegionInstanceManager : LinuxInstanceManager
20+
{
21+
public LegionInstanceManager(IHttpClientFactory httpClientFactory, IScriptWebHostEnvironment webHostEnvironment,
22+
IEnvironment environment, ILogger<LegionInstanceManager> logger, IMetricsLogger metricsLogger, IMeshServiceClient meshServiceClient) : base(httpClientFactory, webHostEnvironment,
23+
environment, logger, metricsLogger, meshServiceClient) { }
24+
25+
public override Task<string> SpecializeMSISidecar(HostAssignmentContext context)
26+
{
27+
// Legion will take care of MSI Specialization
28+
return Task.FromResult<string>(null);
29+
}
30+
31+
protected override Task<string> DownloadWarmupAsync(RunFromPackageContext context)
32+
{
33+
return Task.FromResult<string>(null);
34+
}
35+
36+
public override Task<string> ValidateContext(HostAssignmentContext assignmentContext)
37+
{
38+
// Don't need to validate RunFromPackageContext in Legion
39+
return Task.FromResult<string>(null);
40+
}
41+
42+
protected override Task<string> ApplyContextAsync(HostAssignmentContext assignmentContext)
43+
{
44+
return Task.FromResult<string>(null);
45+
}
46+
}
47+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using Microsoft.Azure.Storage;
11+
using Microsoft.Azure.Storage.File;
12+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
13+
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
14+
using Microsoft.Azure.WebJobs.Script.WebHost.Management.LinuxSpecialization;
15+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
16+
using Microsoft.Extensions.Logging;
17+
using Microsoft.Extensions.Options;
18+
using Newtonsoft.Json;
19+
20+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Management
21+
{
22+
public abstract class LinuxInstanceManager : IInstanceManager
23+
{
24+
private const string WebsiteNodeDefaultVersion = "8.5.0";
25+
26+
private readonly object _assignmentLock = new object();
27+
private readonly ILogger _logger;
28+
private readonly IMeshServiceClient _meshServiceClient;
29+
private readonly IEnvironment _environment;
30+
private readonly HttpClient _client;
31+
private readonly IScriptWebHostEnvironment _webHostEnvironment;
32+
33+
private HostAssignmentContext _assignmentContext;
34+
35+
public LinuxInstanceManager(IHttpClientFactory httpClientFactory, IScriptWebHostEnvironment webHostEnvironment,
36+
IEnvironment environment, ILogger<LinuxInstanceManager> logger, IMetricsLogger metricsLogger, IMeshServiceClient meshServiceClient)
37+
{
38+
_client = httpClientFactory?.CreateClient() ?? throw new ArgumentNullException(nameof(httpClientFactory));
39+
_webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
40+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
41+
_meshServiceClient = meshServiceClient;
42+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
43+
}
44+
45+
public abstract Task<string> SpecializeMSISidecar(HostAssignmentContext context);
46+
47+
public bool StartAssignment(HostAssignmentContext context)
48+
{
49+
if (!_webHostEnvironment.InStandbyMode)
50+
{
51+
// This is only true when specializing pinned containers.
52+
if (!context.Environment.TryGetValue(EnvironmentSettingNames.ContainerStartContext, out string startContext))
53+
{
54+
_logger.LogError("Assign called while host is not in placeholder mode and start context is not present.");
55+
return false;
56+
}
57+
}
58+
59+
if (_environment.IsContainerReady())
60+
{
61+
_logger.LogError("Assign called while container is marked as specialized.");
62+
return false;
63+
}
64+
65+
if (context.IsWarmupRequest)
66+
{
67+
// Based on profiling download code jit-ing holds up cold start.
68+
// Pre-jit to avoid paying the cost later.
69+
Task.Run(async () => await DownloadWarmupAsync(context.GetRunFromPkgContext()));
70+
return true;
71+
}
72+
else if (_assignmentContext == null)
73+
{
74+
lock (_assignmentLock)
75+
{
76+
if (_assignmentContext != null)
77+
{
78+
return _assignmentContext.Equals(context);
79+
}
80+
_assignmentContext = context;
81+
}
82+
83+
_logger.LogInformation($"Starting Assignment. Cloud Name: {_environment.GetCloudName()}");
84+
85+
// set a flag which will cause any incoming http requests to buffer
86+
// until specialization is complete
87+
// the host is guaranteed not to receive any requests until AFTER assign
88+
// has been initiated, so setting this flag here is sufficient to ensure
89+
// that any subsequent incoming requests while the assign is in progress
90+
// will be delayed until complete
91+
_webHostEnvironment.DelayRequests();
92+
93+
// start the specialization process in the background
94+
Task.Run(async () => await AssignAsync(context));
95+
96+
return true;
97+
}
98+
else
99+
{
100+
// No lock needed here since _assignmentContext is not null when we are here
101+
return _assignmentContext.Equals(context);
102+
}
103+
}
104+
105+
public abstract Task<string> ValidateContext(HostAssignmentContext assignmentContext);
106+
107+
private async Task AssignAsync(HostAssignmentContext assignmentContext)
108+
{
109+
try
110+
{
111+
// first make all environment and file system changes required for
112+
// the host to be specialized
113+
_logger.LogInformation("Applying {environmentCount} app setting(s)", assignmentContext.Environment.Count);
114+
assignmentContext.ApplyAppSettings(_environment, _logger);
115+
await ApplyContextAsync(assignmentContext);
116+
}
117+
catch (Exception ex)
118+
{
119+
_logger.LogError(ex, "Assign failed");
120+
await _meshServiceClient.NotifyHealthEvent(ContainerHealthEventType.Fatal, GetType(), "Assign failed");
121+
throw;
122+
}
123+
finally
124+
{
125+
// all assignment settings/files have been applied so we can flip
126+
// the switch now on specialization
127+
// even if there are failures applying context above, we want to
128+
// leave placeholder mode
129+
_logger.LogInformation("Triggering specialization");
130+
_webHostEnvironment.FlagAsSpecializedAndReady();
131+
132+
_webHostEnvironment.ResumeRequests();
133+
}
134+
}
135+
136+
protected abstract Task ApplyContextAsync(HostAssignmentContext assignmentContext);
137+
138+
protected abstract Task<string> DownloadWarmupAsync(RunFromPackageContext context);
139+
140+
public IDictionary<string, string> GetInstanceInfo()
141+
{
142+
return new Dictionary<string, string>
143+
{
144+
{ EnvironmentSettingNames.FunctionsExtensionVersion, ScriptHost.Version },
145+
{ EnvironmentSettingNames.WebsiteNodeDefaultVersion, WebsiteNodeDefaultVersion }
146+
};
147+
}
148+
149+
// for testing
150+
internal void Reset()
151+
{
152+
_assignmentContext = null;
153+
}
154+
}
155+
}

0 commit comments

Comments
 (0)