Skip to content

Commit

Permalink
Start Context Cache support
Browse files Browse the repository at this point in the history
  • Loading branch information
mathewc committed Feb 12, 2020
1 parent 1ed2a54 commit bd350f3
Show file tree
Hide file tree
Showing 24 changed files with 971 additions and 379 deletions.
13 changes: 13 additions & 0 deletions WebJobs.Script.sln
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpTrigger", "HttpTrigger"
sample\PowerShell\HttpTrigger\run.ps1 = sample\PowerShell\HttpTrigger\run.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Python", "Python", "{0AE3CE25-4CD9-4769-AE58-399FC59CF70F}"
ProjectSection(SolutionItems) = preProject
sample\Python\host.json = sample\Python\host.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpTrigger", "HttpTrigger", "{BA45A727-34B7-484F-9B93-B1755AF09A2A}"
ProjectSection(SolutionItems) = preProject
sample\Python\HttpTrigger\__init__.py = sample\Python\HttpTrigger\__init__.py
sample\Python\HttpTrigger\function.json = sample\Python\HttpTrigger\function.json
EndProjectSection
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
test\WebJobs.Script.Tests.Shared\WebJobs.Script.Tests.Shared.projitems*{35c9ccb7-d8b6-4161-bb0d-bcfa7c6dcffb}*SharedItemsImports = 13
Expand Down Expand Up @@ -386,6 +397,8 @@ Global
{120DC6B3-B4B0-4CA6-A1B6-13177EAE325B} = {908055D8-096D-49BA-850D-C96F676C1561}
{FA3EB27D-D1C1-4AE0-A928-CF3882D929CD} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
{EA8288BA-CB4D-4B9C-ADF8-F4B7C41466EF} = {FA3EB27D-D1C1-4AE0-A928-CF3882D929CD}
{0AE3CE25-4CD9-4769-AE58-399FC59CF70F} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
{BA45A727-34B7-484F-9B93-B1755AF09A2A} = {0AE3CE25-4CD9-4769-AE58-399FC59CF70F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85400884-5FFD-4C27-A571-58CB3C8CAAC5}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,50 @@ public class LinuxContainerInitializationHostService : IHostedService
private readonly IEnvironment _environment;
private readonly IInstanceManager _instanceManager;
private readonly ILogger _logger;
private readonly StartupContextProvider _startupContextProvider;
private CancellationToken _cancellationToken;

public LinuxContainerInitializationHostService(IEnvironment environment, IInstanceManager instanceManager, ILogger<LinuxContainerInitializationHostService> logger)
public LinuxContainerInitializationHostService(IEnvironment environment, IInstanceManager instanceManager, ILogger<LinuxContainerInitializationHostService> logger, StartupContextProvider startupContextProvider)
{
_environment = environment;
_instanceManager = instanceManager;
_logger = logger;
_startupContextProvider = startupContextProvider;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Initializing LinuxContainerInitializationService.");
_cancellationToken = cancellationToken;

// The service should be registered in IsLinuxContainerEnvironment only. But do additional check here.
// The service should be registered in Linux Consumption only, but do additional check here.
if (_environment.IsLinuxConsumption())
{
await ApplyContextIfPresent();
await ApplyStartContextIfPresent();
}
}

private async Task ApplyContextIfPresent()
private async Task ApplyStartContextIfPresent()
{
var startContext = await GetStartContextOrNullAsync();

if (!string.IsNullOrEmpty(startContext))
{
_logger.LogInformation("Applying host context");

var encryptedAssignmentContext = JsonConvert.DeserializeObject<EncryptedHostAssignmentContext>(startContext);
var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);

bool success = _instanceManager.StartAssignment(assignmentContext, false);
_logger.LogInformation($"StartAssignment invoked (Success={success})");
}
else
{
_logger.LogInformation("No host context specified. Waiting for host assignment");
}
}

private async Task<string> GetStartContextOrNullAsync()
{
var startContext = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerStartContext);

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

if (!string.IsNullOrEmpty(startContext))
{
_logger.LogInformation("Applying host context");

var encryptedAssignmentContext = JsonConvert.DeserializeObject<EncryptedHostAssignmentContext>(startContext);
var containerKey = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey);
var assignmentContext = encryptedAssignmentContext.Decrypt(containerKey);
if (_instanceManager.StartAssignment(assignmentContext, false))
{
_logger.LogInformation("Start assign HostAssignmentContext success");
}
else
{
_logger.LogError("Start assign HostAssignmentContext failed");
}
}
else
{
_logger.LogInformation("No host context specified. Waiting for host assignment");
}
return startContext;
}

private async Task<string> GetAssignmentContextFromSasUri(string sasUri)
Expand Down
46 changes: 27 additions & 19 deletions src/WebJobs.Script.WebHost/Controllers/InstanceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ public class InstanceController : Controller
private readonly IEnvironment _environment;
private readonly IInstanceManager _instanceManager;
private readonly ILogger _logger;
private readonly StartupContextProvider _startupContextProvider;

public InstanceController(IEnvironment environment, IInstanceManager instanceManager, ILoggerFactory loggerFactory)
public InstanceController(IEnvironment environment, IInstanceManager instanceManager, ILoggerFactory loggerFactory, StartupContextProvider startupContextProvider)
{
_environment = environment;
_instanceManager = instanceManager;
_logger = loggerFactory.CreateLogger<InstanceController>();
_startupContextProvider = startupContextProvider;
}

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

// before starting the assignment we want to perform as much
// up front validation on the context as possible
string error = await _instanceManager.ValidateContext(assignmentContext, encryptedAssignmentContext.IsWarmup);
if (error != null)
bool succeeded = false;
if (!encryptedAssignmentContext.IsWarmup)
{
return StatusCode(StatusCodes.Status400BadRequest, error);
}
var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);

// before starting the assignment we want to perform as much
// up front validation on the context as possible
string error = await _instanceManager.ValidateContext(assignmentContext, encryptedAssignmentContext.IsWarmup);
if (error != null)
{
return StatusCode(StatusCodes.Status400BadRequest, error);
}

// Wait for Sidecar specialization to complete before returning ok.
// This shouldn't take too long so ok to do this sequentially.
error = await _instanceManager.SpecializeMSISidecar(assignmentContext, encryptedAssignmentContext.IsWarmup);
if (error != null)
{
return StatusCode(StatusCodes.Status500InternalServerError, error);
}

// Wait for Sidecar specialization to complete before returning ok.
// This shouldn't take too long so ok to do this sequentially.
error = await _instanceManager.SpecializeMSISidecar(assignmentContext, encryptedAssignmentContext.IsWarmup);
if (error != null)
succeeded = _instanceManager.StartAssignment(assignmentContext, encryptedAssignmentContext.IsWarmup);
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, error);
succeeded = true;
}

var result = _instanceManager.StartAssignment(assignmentContext, encryptedAssignmentContext.IsWarmup);

return result || encryptedAssignmentContext.IsWarmup
return succeeded
? Accepted()
: StatusCode(StatusCodes.Status409Conflict, "Instance already assigned");
}
Expand Down
34 changes: 18 additions & 16 deletions src/WebJobs.Script.WebHost/Management/FunctionsSyncManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Models;
using Microsoft.Azure.WebJobs.Script.WebHost.Extensions;
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -301,31 +302,32 @@ public async Task<SyncTriggersPayload> GetSyncTriggersPayload()
string.Compare(secretsStorageType, "files", StringComparison.OrdinalIgnoreCase) == 0 ||
string.Compare(secretsStorageType, "blob", StringComparison.OrdinalIgnoreCase) == 0)
{
JObject secrets = new JObject();
result.Add("secrets", secrets);
var functionAppSecrets = new FunctionAppSecrets();

// add host secrets
var hostSecretsInfo = await _secretManagerProvider.Current.GetHostSecretsAsync();
var hostSecrets = new JObject();
hostSecrets.Add("master", hostSecretsInfo.MasterKey);
hostSecrets.Add("function", JObject.FromObject(hostSecretsInfo.FunctionKeys));
hostSecrets.Add("system", JObject.FromObject(hostSecretsInfo.SystemKeys));
secrets.Add("host", hostSecrets);
functionAppSecrets.Host = new FunctionAppSecrets.HostSecrets
{
Master = hostSecretsInfo.MasterKey,
Function = hostSecretsInfo.FunctionKeys,
System = hostSecretsInfo.SystemKeys
};

// add function secrets
var functionSecrets = new JArray();
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);
foreach (var functionName in httpFunctions)
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();
functionAppSecrets.Function = new FunctionAppSecrets.FunctionSecrets[httpFunctions.Length];
for (int i = 0; i < httpFunctions.Length; i++)
{
var currSecrets = await _secretManagerProvider.Current.GetFunctionSecretsAsync(functionName);
var currElement = new JObject()
var currFunctionName = httpFunctions[i];
var currSecrets = await _secretManagerProvider.Current.GetFunctionSecretsAsync(currFunctionName);
functionAppSecrets.Function[i] = new FunctionAppSecrets.FunctionSecrets
{
{ "name", functionName },
{ "secrets", JObject.FromObject(currSecrets) }
Name = currFunctionName,
Secrets = currSecrets
};
functionSecrets.Add(currElement);
}
secrets.Add("function", functionSecrets);

result.Add("secrets", JObject.FromObject(functionAppSecrets));
}
else
{
Expand Down
1 change: 1 addition & 0 deletions src/WebJobs.Script.WebHost/Management/InstanceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public async Task<string> ValidateContext(HostAssignmentContext assignmentContex
{
return null;
}

_logger.LogInformation($"Validating host assignment context (SiteId: {assignmentContext.SiteId}, SiteName: '{assignmentContext.SiteName}')");
RunFromPackageContext pkgContext = assignmentContext.GetRunFromPkgContext();
_logger.LogInformation($"Will be using {pkgContext.EnvironmentVariableName} app setting as zip url");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@ public FunctionInvocationMiddleware(RequestDelegate next)

public async Task Invoke(HttpContext context)
{
// TODO: DI Need to make sure downstream services are getting what they need
// that includes proxies.
//if (scriptHost != null)
//{
// // flow required context through the request pipeline
// // downstream middleware and filters rely on this
// context.Items[ScriptConstants.AzureFunctionsHostKey] = scriptHost;
//}

if (_next != null)
{
await _next(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
using Newtonsoft.Json;

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

[JsonProperty("isWarmup")]
public bool IsWarmup { get; set; }

public static EncryptedHostAssignmentContext Create(HostAssignmentContext context, string key)
{
string json = JsonConvert.SerializeObject(context);
var encryptionKey = Convert.FromBase64String(key);
string encrypted = SimpleWebTokenHelper.Encrypt(json, encryptionKey);

return new EncryptedHostAssignmentContext { EncryptedContext = encrypted };
}

public HostAssignmentContext Decrypt(string key)
{
var decrypted = SimpleWebTokenHelper.Decrypt(key.ToKeyBytes(), EncryptedContext);
return JsonConvert.DeserializeObject<HostAssignmentContext>(decrypted);
}
}
}
42 changes: 42 additions & 0 deletions src/WebJobs.Script.WebHost/Models/FunctionAppSecrets.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
using Newtonsoft.Json;

namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
{
/// <summary>
/// Secrets cache format used by both <see cref="FunctionsSyncManager"/> and <see cref="StartupContextProvider"/>.
/// </summary>
public class FunctionAppSecrets
{
[JsonProperty("host")]
public HostSecrets Host { get; set; }

[JsonProperty("function")]
public FunctionSecrets[] Function { get; set; }

public class FunctionSecrets
{
[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("secrets")]
public IDictionary<string, string> Secrets { get; set; }
}

public class HostSecrets
{
[JsonProperty("master")]
public string Master { get; set; }

[JsonProperty("function")]
public IDictionary<string, string> Function { get; set; }

[JsonProperty("system")]
public IDictionary<string, string> System { get; set; }
}
}
}
4 changes: 4 additions & 0 deletions src/WebJobs.Script.WebHost/Models/HostAssignmentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

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

[JsonProperty("Secrets")]
public FunctionAppSecrets Secrets { get; set; }

public long? PackageContentLength { get; set; }

public string AzureFilesConnectionString
Expand Down
Loading

0 comments on commit bd350f3

Please sign in to comment.