Skip to content

Commit bd350f3

Browse files
committed
Start Context Cache support
1 parent 1ed2a54 commit bd350f3

24 files changed

+971
-379
lines changed

WebJobs.Script.sln

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpTrigger", "HttpTrigger"
297297
sample\PowerShell\HttpTrigger\run.ps1 = sample\PowerShell\HttpTrigger\run.ps1
298298
EndProjectSection
299299
EndProject
300+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Python", "Python", "{0AE3CE25-4CD9-4769-AE58-399FC59CF70F}"
301+
ProjectSection(SolutionItems) = preProject
302+
sample\Python\host.json = sample\Python\host.json
303+
EndProjectSection
304+
EndProject
305+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpTrigger", "HttpTrigger", "{BA45A727-34B7-484F-9B93-B1755AF09A2A}"
306+
ProjectSection(SolutionItems) = preProject
307+
sample\Python\HttpTrigger\__init__.py = sample\Python\HttpTrigger\__init__.py
308+
sample\Python\HttpTrigger\function.json = sample\Python\HttpTrigger\function.json
309+
EndProjectSection
310+
EndProject
300311
Global
301312
GlobalSection(SharedMSBuildProjectFiles) = preSolution
302313
test\WebJobs.Script.Tests.Shared\WebJobs.Script.Tests.Shared.projitems*{35c9ccb7-d8b6-4161-bb0d-bcfa7c6dcffb}*SharedItemsImports = 13
@@ -386,6 +397,8 @@ Global
386397
{120DC6B3-B4B0-4CA6-A1B6-13177EAE325B} = {908055D8-096D-49BA-850D-C96F676C1561}
387398
{FA3EB27D-D1C1-4AE0-A928-CF3882D929CD} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
388399
{EA8288BA-CB4D-4B9C-ADF8-F4B7C41466EF} = {FA3EB27D-D1C1-4AE0-A928-CF3882D929CD}
400+
{0AE3CE25-4CD9-4769-AE58-399FC59CF70F} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
401+
{BA45A727-34B7-484F-9B93-B1755AF09A2A} = {0AE3CE25-4CD9-4769-AE58-399FC59CF70F}
389402
EndGlobalSection
390403
GlobalSection(ExtensibilityGlobals) = postSolution
391404
SolutionGuid = {85400884-5FFD-4C27-A571-58CB3C8CAAC5}

src/WebJobs.Script.WebHost/ContainerManagement/LinuxContainerInitializationHostService.cs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,50 @@ public class LinuxContainerInitializationHostService : IHostedService
1919
private readonly IEnvironment _environment;
2020
private readonly IInstanceManager _instanceManager;
2121
private readonly ILogger _logger;
22+
private readonly StartupContextProvider _startupContextProvider;
2223
private CancellationToken _cancellationToken;
2324

24-
public LinuxContainerInitializationHostService(IEnvironment environment, IInstanceManager instanceManager, ILogger<LinuxContainerInitializationHostService> logger)
25+
public LinuxContainerInitializationHostService(IEnvironment environment, IInstanceManager instanceManager, ILogger<LinuxContainerInitializationHostService> logger, StartupContextProvider startupContextProvider)
2526
{
2627
_environment = environment;
2728
_instanceManager = instanceManager;
2829
_logger = logger;
30+
_startupContextProvider = startupContextProvider;
2931
}
3032

3133
public async Task StartAsync(CancellationToken cancellationToken)
3234
{
3335
_logger.LogInformation("Initializing LinuxContainerInitializationService.");
3436
_cancellationToken = cancellationToken;
3537

36-
// The service should be registered in IsLinuxContainerEnvironment only. But do additional check here.
38+
// The service should be registered in Linux Consumption only, but do additional check here.
3739
if (_environment.IsLinuxConsumption())
3840
{
39-
await ApplyContextIfPresent();
41+
await ApplyStartContextIfPresent();
4042
}
4143
}
4244

43-
private async Task ApplyContextIfPresent()
45+
private async Task ApplyStartContextIfPresent()
46+
{
47+
var startContext = await GetStartContextOrNullAsync();
48+
49+
if (!string.IsNullOrEmpty(startContext))
50+
{
51+
_logger.LogInformation("Applying host context");
52+
53+
var encryptedAssignmentContext = JsonConvert.DeserializeObject<EncryptedHostAssignmentContext>(startContext);
54+
var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);
55+
56+
bool success = _instanceManager.StartAssignment(assignmentContext, false);
57+
_logger.LogInformation($"StartAssignment invoked (Success={success})");
58+
}
59+
else
60+
{
61+
_logger.LogInformation("No host context specified. Waiting for host assignment");
62+
}
63+
}
64+
65+
private async Task<string> GetStartContextOrNullAsync()
4466
{
4567
var startContext = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerStartContext);
4668

@@ -61,26 +83,7 @@ private async Task ApplyContextIfPresent()
6183
_logger.LogInformation("Host context specified via CONTAINER_START_CONTEXT");
6284
}
6385

64-
if (!string.IsNullOrEmpty(startContext))
65-
{
66-
_logger.LogInformation("Applying host context");
67-
68-
var encryptedAssignmentContext = JsonConvert.DeserializeObject<EncryptedHostAssignmentContext>(startContext);
69-
var containerKey = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey);
70-
var assignmentContext = encryptedAssignmentContext.Decrypt(containerKey);
71-
if (_instanceManager.StartAssignment(assignmentContext, false))
72-
{
73-
_logger.LogInformation("Start assign HostAssignmentContext success");
74-
}
75-
else
76-
{
77-
_logger.LogError("Start assign HostAssignmentContext failed");
78-
}
79-
}
80-
else
81-
{
82-
_logger.LogInformation("No host context specified. Waiting for host assignment");
83-
}
86+
return startContext;
8487
}
8588

8689
private async Task<string> GetAssignmentContextFromSasUri(string sasUri)

src/WebJobs.Script.WebHost/Controllers/InstanceController.cs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ public class InstanceController : Controller
2222
private readonly IEnvironment _environment;
2323
private readonly IInstanceManager _instanceManager;
2424
private readonly ILogger _logger;
25+
private readonly StartupContextProvider _startupContextProvider;
2526

26-
public InstanceController(IEnvironment environment, IInstanceManager instanceManager, ILoggerFactory loggerFactory)
27+
public InstanceController(IEnvironment environment, IInstanceManager instanceManager, ILoggerFactory loggerFactory, StartupContextProvider startupContextProvider)
2728
{
2829
_environment = environment;
2930
_instanceManager = instanceManager;
3031
_logger = loggerFactory.CreateLogger<InstanceController>();
32+
_startupContextProvider = startupContextProvider;
3133
}
3234

3335
[HttpPost]
@@ -36,30 +38,36 @@ public InstanceController(IEnvironment environment, IInstanceManager instanceMan
3638
public async Task<IActionResult> Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
3739
{
3840
_logger.LogDebug($"Starting container assignment for host : {Request?.Host}. ContextLength is: {encryptedAssignmentContext.EncryptedContext?.Length}");
39-
var containerKey = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey);
40-
var assignmentContext = encryptedAssignmentContext.IsWarmup
41-
? null
42-
: encryptedAssignmentContext.Decrypt(containerKey);
4341

44-
// before starting the assignment we want to perform as much
45-
// up front validation on the context as possible
46-
string error = await _instanceManager.ValidateContext(assignmentContext, encryptedAssignmentContext.IsWarmup);
47-
if (error != null)
42+
bool succeeded = false;
43+
if (!encryptedAssignmentContext.IsWarmup)
4844
{
49-
return StatusCode(StatusCodes.Status400BadRequest, error);
50-
}
45+
var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);
46+
47+
// before starting the assignment we want to perform as much
48+
// up front validation on the context as possible
49+
string error = await _instanceManager.ValidateContext(assignmentContext, encryptedAssignmentContext.IsWarmup);
50+
if (error != null)
51+
{
52+
return StatusCode(StatusCodes.Status400BadRequest, error);
53+
}
54+
55+
// Wait for Sidecar specialization to complete before returning ok.
56+
// This shouldn't take too long so ok to do this sequentially.
57+
error = await _instanceManager.SpecializeMSISidecar(assignmentContext, encryptedAssignmentContext.IsWarmup);
58+
if (error != null)
59+
{
60+
return StatusCode(StatusCodes.Status500InternalServerError, error);
61+
}
5162

52-
// Wait for Sidecar specialization to complete before returning ok.
53-
// This shouldn't take too long so ok to do this sequentially.
54-
error = await _instanceManager.SpecializeMSISidecar(assignmentContext, encryptedAssignmentContext.IsWarmup);
55-
if (error != null)
63+
succeeded = _instanceManager.StartAssignment(assignmentContext, encryptedAssignmentContext.IsWarmup);
64+
}
65+
else
5666
{
57-
return StatusCode(StatusCodes.Status500InternalServerError, error);
67+
succeeded = true;
5868
}
5969

60-
var result = _instanceManager.StartAssignment(assignmentContext, encryptedAssignmentContext.IsWarmup);
61-
62-
return result || encryptedAssignmentContext.IsWarmup
70+
return succeeded
6371
? Accepted()
6472
: StatusCode(StatusCodes.Status409Conflict, "Instance already assigned");
6573
}

src/WebJobs.Script.WebHost/Management/FunctionsSyncManager.cs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.Azure.WebJobs.Script.Description;
1616
using Microsoft.Azure.WebJobs.Script.Models;
1717
using Microsoft.Azure.WebJobs.Script.WebHost.Extensions;
18+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
1819
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
1920
using Microsoft.Extensions.Configuration;
2021
using Microsoft.Extensions.Logging;
@@ -301,31 +302,32 @@ public async Task<SyncTriggersPayload> GetSyncTriggersPayload()
301302
string.Compare(secretsStorageType, "files", StringComparison.OrdinalIgnoreCase) == 0 ||
302303
string.Compare(secretsStorageType, "blob", StringComparison.OrdinalIgnoreCase) == 0)
303304
{
304-
JObject secrets = new JObject();
305-
result.Add("secrets", secrets);
305+
var functionAppSecrets = new FunctionAppSecrets();
306306

307307
// add host secrets
308308
var hostSecretsInfo = await _secretManagerProvider.Current.GetHostSecretsAsync();
309-
var hostSecrets = new JObject();
310-
hostSecrets.Add("master", hostSecretsInfo.MasterKey);
311-
hostSecrets.Add("function", JObject.FromObject(hostSecretsInfo.FunctionKeys));
312-
hostSecrets.Add("system", JObject.FromObject(hostSecretsInfo.SystemKeys));
313-
secrets.Add("host", hostSecrets);
309+
functionAppSecrets.Host = new FunctionAppSecrets.HostSecrets
310+
{
311+
Master = hostSecretsInfo.MasterKey,
312+
Function = hostSecretsInfo.FunctionKeys,
313+
System = hostSecretsInfo.SystemKeys
314+
};
314315

315316
// add function secrets
316-
var functionSecrets = new JArray();
317-
var httpFunctions = functionsMetadata.Where(p => !p.IsProxy && p.InputBindings.Any(q => q.IsTrigger && string.Compare(q.Type, "httptrigger", StringComparison.OrdinalIgnoreCase) == 0)).Select(p => p.Name);
318-
foreach (var functionName in httpFunctions)
317+
var httpFunctions = functionsMetadata.Where(p => !p.IsProxy && p.InputBindings.Any(q => q.IsTrigger && string.Compare(q.Type, "httptrigger", StringComparison.OrdinalIgnoreCase) == 0)).Select(p => p.Name).ToArray();
318+
functionAppSecrets.Function = new FunctionAppSecrets.FunctionSecrets[httpFunctions.Length];
319+
for (int i = 0; i < httpFunctions.Length; i++)
319320
{
320-
var currSecrets = await _secretManagerProvider.Current.GetFunctionSecretsAsync(functionName);
321-
var currElement = new JObject()
321+
var currFunctionName = httpFunctions[i];
322+
var currSecrets = await _secretManagerProvider.Current.GetFunctionSecretsAsync(currFunctionName);
323+
functionAppSecrets.Function[i] = new FunctionAppSecrets.FunctionSecrets
322324
{
323-
{ "name", functionName },
324-
{ "secrets", JObject.FromObject(currSecrets) }
325+
Name = currFunctionName,
326+
Secrets = currSecrets
325327
};
326-
functionSecrets.Add(currElement);
327328
}
328-
secrets.Add("function", functionSecrets);
329+
330+
result.Add("secrets", JObject.FromObject(functionAppSecrets));
329331
}
330332
else
331333
{

src/WebJobs.Script.WebHost/Management/InstanceManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public async Task<string> ValidateContext(HostAssignmentContext assignmentContex
139139
{
140140
return null;
141141
}
142+
142143
_logger.LogInformation($"Validating host assignment context (SiteId: {assignmentContext.SiteId}, SiteName: '{assignmentContext.SiteName}')");
143144
RunFromPackageContext pkgContext = assignmentContext.GetRunFromPkgContext();
144145
_logger.LogInformation($"Will be using {pkgContext.EnvironmentVariableName} app setting as zip url");

src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,6 @@ public FunctionInvocationMiddleware(RequestDelegate next)
3838

3939
public async Task Invoke(HttpContext context)
4040
{
41-
// TODO: DI Need to make sure downstream services are getting what they need
42-
// that includes proxies.
43-
//if (scriptHost != null)
44-
//{
45-
// // flow required context through the request pipeline
46-
// // downstream middleware and filters rely on this
47-
// context.Items[ScriptConstants.AzureFunctionsHostKey] = scriptHost;
48-
//}
49-
5041
if (_next != null)
5142
{
5243
await _next(context);

src/WebJobs.Script.WebHost/Models/EncryptedHostAssignmentContext.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
65
using Newtonsoft.Json;
76

87
namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
@@ -14,20 +13,5 @@ public class EncryptedHostAssignmentContext
1413

1514
[JsonProperty("isWarmup")]
1615
public bool IsWarmup { get; set; }
17-
18-
public static EncryptedHostAssignmentContext Create(HostAssignmentContext context, string key)
19-
{
20-
string json = JsonConvert.SerializeObject(context);
21-
var encryptionKey = Convert.FromBase64String(key);
22-
string encrypted = SimpleWebTokenHelper.Encrypt(json, encryptionKey);
23-
24-
return new EncryptedHostAssignmentContext { EncryptedContext = encrypted };
25-
}
26-
27-
public HostAssignmentContext Decrypt(string key)
28-
{
29-
var decrypted = SimpleWebTokenHelper.Decrypt(key.ToKeyBytes(), EncryptedContext);
30-
return JsonConvert.DeserializeObject<HostAssignmentContext>(decrypted);
31-
}
3216
}
3317
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.Collections.Generic;
5+
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
6+
using Newtonsoft.Json;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
9+
{
10+
/// <summary>
11+
/// Secrets cache format used by both <see cref="FunctionsSyncManager"/> and <see cref="StartupContextProvider"/>.
12+
/// </summary>
13+
public class FunctionAppSecrets
14+
{
15+
[JsonProperty("host")]
16+
public HostSecrets Host { get; set; }
17+
18+
[JsonProperty("function")]
19+
public FunctionSecrets[] Function { get; set; }
20+
21+
public class FunctionSecrets
22+
{
23+
[JsonProperty("name")]
24+
public string Name { get; set; }
25+
26+
[JsonProperty("secrets")]
27+
public IDictionary<string, string> Secrets { get; set; }
28+
}
29+
30+
public class HostSecrets
31+
{
32+
[JsonProperty("master")]
33+
public string Master { get; set; }
34+
35+
[JsonProperty("function")]
36+
public IDictionary<string, string> Function { get; set; }
37+
38+
[JsonProperty("system")]
39+
public IDictionary<string, string> System { get; set; }
40+
}
41+
}
42+
}

src/WebJobs.Script.WebHost/Models/HostAssignmentContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
89

910
namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
1011
{
@@ -31,6 +32,9 @@ public class HostAssignmentContext
3132
[JsonProperty("EasyAuthSpecializationPayload")]
3233
public EasyAuthSettings EasyAuthSettings { get; set; }
3334

35+
[JsonProperty("Secrets")]
36+
public FunctionAppSecrets Secrets { get; set; }
37+
3438
public long? PackageContentLength { get; set; }
3539

3640
public string AzureFilesConnectionString

0 commit comments

Comments
 (0)