Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: [Functions][Kubernetes]SyncTrigger issue #208

13 changes: 13 additions & 0 deletions Kudu.Core/Functions/KedaFunctionTriggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ public static IEnumerable<ScaleTrigger> GetFunctionTriggers(IEnumerable<JObject>
return CreateScaleTriggers(triggerBindings, hostJsonText, appSettings);
}

public static IEnumerable<ScaleTrigger> GetFunctionTriggersFromSyncTriggerPayload(string synctriggerPayload,
string hostJsonText, IDictionary<string, string> appSettings)
{
return CreateScaleTriggers(ParseSyncTriggerPayload(synctriggerPayload), hostJsonText, appSettings);
}

internal static IEnumerable<ScaleTrigger> CreateScaleTriggers(IEnumerable<FunctionTrigger> triggerBindings, string hostJsonText, IDictionary<string, string> appSettings)
{

Expand All @@ -138,6 +144,13 @@ bool IsDurable(FunctionTrigger function) =>
return kedaScaleTriggers;
}

internal static IEnumerable<FunctionTrigger> ParseSyncTriggerPayload(string payload)
{
var payloadJson = JObject.Parse(payload);
var triggers = (JArray)payloadJson["triggers"];
return triggers.Select(o => o.ToObject<JObject>())
.Select(o => new FunctionTrigger(o["functionName"].ToString(), o, o["type"].ToString()));
}

internal static IEnumerable<FunctionTrigger> ParseFunctionJson(string functionName, JObject functionJson)
{
Expand Down
10 changes: 6 additions & 4 deletions Kudu.Core/Functions/SyncTriggerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public async Task<string> SyncTriggers(string functionTriggersPayload)
using (_tracer.Step("SyncTriggerHandler.SyncTrigger()"))
{
var scaleTriggersContent = GetScaleTriggers(functionTriggersPayload);
Console.WriteLine("******* Getting ScaleTriggers ***");
if (!string.IsNullOrEmpty(scaleTriggersContent.Item2))
{
return scaleTriggersContent.Item2;
Expand All @@ -47,6 +48,7 @@ public async Task<string> SyncTriggers(string functionTriggersPayload)
};

await Task.Run(() => K8SEDeploymentHelper.UpdateFunctionAppTriggers(appName, scaleTriggers, buildMetadata));
Console.WriteLine("***** Finish the Updating App Triggers ***** ");
}

return null;
Expand All @@ -62,10 +64,9 @@ public Tuple<IEnumerable<ScaleTrigger>, string> GetScaleTriggers(string function
return new Tuple<IEnumerable<ScaleTrigger>, string>(null, "Function trigger payload is null or empty.");
}

var triggersJson = JArray.Parse(functionTriggersPayload).Select(o => o.ToObject<JObject>());

// TODO: https://github.com/Azure/azure-functions-host/issues/7288 should change how we parse hostJsonText here.
scaleTriggers = KedaFunctionTriggerProvider.GetFunctionTriggers(triggersJson, string.Empty, _appSettings);
scaleTriggers =
KedaFunctionTriggerProvider.GetFunctionTriggersFromSyncTriggerPayload(functionTriggersPayload,
string.Empty, _appSettings);
if (!scaleTriggers.Any())
{
return new Tuple<IEnumerable<ScaleTrigger>, string>(null, "No triggers in the payload");
Expand All @@ -78,5 +79,6 @@ public Tuple<IEnumerable<ScaleTrigger>, string> GetScaleTriggers(string function

return new Tuple<IEnumerable<ScaleTrigger>, string>(scaleTriggers, null); ;
}

}
}
29 changes: 22 additions & 7 deletions Kudu.Core/Kube/SyncTriggerAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,27 @@ public async static Task<bool> AuthenticateCaller(Dictionary<string, IEnumerable
{
return false;
}

Console.WriteLine("**** SyncTriggerAuthenticator Started ***");
//If there's no encryption key in the header return true
if (!headers.TryGetValue(SiteTokenHeaderName, out IEnumerable<string> siteTokenHeaderValue))
{
return false;
}

Console.WriteLine("**** SyncTriggerAuthenticator finish TryGetValue ***");
//Auth header value is null or empty return false
var funcAppAuthToken = siteTokenHeaderValue.FirstOrDefault();
if (string.IsNullOrEmpty(funcAppAuthToken))
{
return false;
}

Console.WriteLine("**** SyncTriggerAuthenticator funcAppAuthToken ***");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these Console.WriteLine needed? Can they use a logger of some sort?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @ahmelsayed This line is just for temporary debugging, However, I'd like to use Logger.
I found ITracer is injected, so that I tried to use it, However, it doesn't show anything on the console log. Do you know why it doesn't show the console log? Do we need to configure for it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to ILogger ? If yes, you need to explicitly add console logging provider - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed on the standup, I remove the Console logs.
I'll update the logs for production on this issue. #202

//If there's no app name or app namespace in the header return false
if (!headers.TryGetValue(FuncAppNameHeaderKey, out IEnumerable<string> funcAppNameHeaderValue)
|| !headers.TryGetValue(FuncAppNamespaceHeaderKey, out IEnumerable<string> funcAppNamespaceHeaderValue))
{
return false;
}

Console.WriteLine("**** SyncTriggerAuthenticator TryGetValue ***");
var funcAppName = funcAppNameHeaderValue.FirstOrDefault();
var funcAppNamespace = funcAppNamespaceHeaderValue.FirstOrDefault();
if (string.IsNullOrEmpty(funcAppName) || string.IsNullOrEmpty(funcAppNamespace))
Expand All @@ -51,23 +51,38 @@ public async static Task<bool> AuthenticateCaller(Dictionary<string, IEnumerable

//If the encryption key secret is null or empty in the Kubernetes - return false
var secretProvider = new SecretProvider();
var encryptionKeySecretContent = await secretProvider.GetSecretContent(funcAppName + "-encryptionkey".ToLower(), funcAppNamespace);
var encryptionKeySecretContent = await secretProvider.GetSecretContent(funcAppName + "-secrets".ToLower(), funcAppNamespace);
if (string.IsNullOrEmpty(encryptionKeySecretContent))
{
return false;
}

Console.WriteLine("**** SyncTriggerAuthenticator GetSecretContent done ***");
var encryptionSecretJObject = JObject.Parse(encryptionKeySecretContent);
var functionEncryptionKey = Base64Decode((string)encryptionSecretJObject["data"][FuncAppEncryptionKeyName]);
if (string.IsNullOrEmpty(functionEncryptionKey))
{
return false;
}

var decryptedToken = Decrypt(Encoding.UTF8.GetBytes(functionEncryptionKey), funcAppAuthToken);
var decryptedToken = Decrypt(GetKeyBytes(functionEncryptionKey), funcAppAuthToken);
Console.WriteLine("**** SyncTriggerAuthenticator Decrypt done ***");
return ValidateToken(decryptedToken);
}

public static byte[] GetKeyBytes(string hexOrBase64)
{
// only support 32 bytes (256 bits) key length
if (hexOrBase64.Length == 64)
{
return Enumerable.Range(0, hexOrBase64.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hexOrBase64.Substring(x, 2), 16))
.ToArray();
}

return Convert.FromBase64String(hexOrBase64);
}

private static bool ValidateToken(string token)
{
if (string.IsNullOrEmpty(token))
Expand Down
2 changes: 2 additions & 0 deletions Kudu.Services/Function/FunctionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public FunctionController(ITracer tracer,
[HttpPut]
public async Task<IActionResult> SyncTrigger()
{
Console.WriteLine("******* Sync Trigger is executing**** ");
_tracer.Trace("Tracer: Sync Trigger Started...");
try
{
var headers = Request.Headers.ToDictionary(a => a.Key, a => a.Value.AsEnumerable());
Expand Down
67 changes: 67 additions & 0 deletions Kudu.Tests/Core/Function/KedaFunctionTriggersProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,73 @@ public void PopulateMetadataDictionary_KedaV2_OnlyKedaSupportedTriggers()
Assert.Equal(0, triggers.Count());
}

[Fact]
public void ConversionFromSyncTriggerPayloadToFunctionTrigger()
{
var inputPayload = @"
{
""triggers"": [
{
""name"": ""myQueueItem"",
""type"": ""queueTrigger"",
""direction"": ""in"",
""queueName"": ""js-queue-items"",
""connection"": ""AzureWebJobsStorage"",
""functionName"": ""QueueTrigger""
}
],
""functions"": [
{
""name"": ""QueueTrigger"",
""script_root_path_href"": ""https://tsushiququecon1901.limaarcncsenv19--k53aemd.northcentralusstage.k4apps.io/admin/vfs/home/site/wwwroot/QueueTrigger/"",
""script_href"": ""https://tsushiququecon1901.limaarcncsenv19--k53aemd.northcentralusstage.k4apps.io/admin/vfs/home/site/wwwroot/QueueTrigger/index.js"",
""config_href"": ""https://tsushiququecon1901.limaarcncsenv19--k53aemd.northcentralusstage.k4apps.io/admin/vfs/home/site/wwwroot/QueueTrigger/function.json"",
""test_data_href"": ""https://tsushiququecon1901.limaarcncsenv19--k53aemd.northcentralusstage.k4apps.io/admin/vfs/tmp/FunctionsData/QueueTrigger.dat"",
""href"": ""https://tsushiququecon1901.limaarcncsenv19--k53aemd.northcentralusstage.k4apps.io/admin/functions/QueueTrigger"",
""invoke_url_template"": null,
""language"": ""node"",
""config"": {
""bindings"": [
{
""name"": ""myQueueItem"",
""type"": ""queueTrigger"",
""direction"": ""in"",
""queueName"": ""js-queue-items"",
""connection"": ""AzureWebJobsStorage""
}
]
},
""files"": null,
""test_data"": """",
""isDisabled"": false,
""isDirect"": false,
""isProxy"": false
}
]
}
";
var expectedJObjectString = @"
{
""name"": ""myQueueItem"",
""type"": ""queueTrigger"",
""direction"": ""in"",
""queueName"": ""js-queue-items"",
""connection"": ""AzureWebJobsStorage"",
""functionName"": ""QueueTrigger""
}
";

var expectedJObject = JObject.Parse(expectedJObjectString);
IEnumerable<KedaFunctionTriggerProvider.FunctionTrigger> results = KedaFunctionTriggerProvider.ParseSyncTriggerPayload(inputPayload);
var actual = results.FirstOrDefault();
Assert.NotNull(actual);
Assert.Equal("QueueTrigger", actual.FunctionName);
Assert.Equal("myQueueItem", actual.Binding["name"]);
Assert.Equal("js-queue-items", actual.Binding["queueName"]);
Assert.Equal("in", actual.Binding["direction"]);
Assert.Equal("AzureWebJobsStorage", actual.Binding["connection"]);
}

[Fact]
public void UpdateFunctionTriggerBindingExpression_Replace_Expression()
{
Expand Down
3 changes: 2 additions & 1 deletion Kudu.Tests/Core/Function/SyncTriggerHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using Kudu.Core.Functions;
using System.Linq;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Kudu.Tests.Core.Function
{
public class SyncTriggerHandlerTests
{
[Theory]
[InlineData("[{\"type\":\"httpTrigger\",\"methods\":[\"get\",\"post\"],\"authLevel\":\"function\",\"name\":\"req\",\"functionName\":\"Function1 - Oct152020 - 1\"},{\"type\":\"queueTrigger\",\"connection\":\"AzureWebJobsStorage\",\"queueName\":\"myqueue - 1\",\"name\":\"myQueueItem\",\"functionName\":\"Function2\"}]")]
[InlineData("{\"triggers\":[{\"type\":\"httpTrigger\",\"methods\":[\"get\",\"post\"],\"authLevel\":\"function\",\"name\":\"req\",\"functionName\":\"Function1 - Oct152020 - 1\"},{\"type\":\"queueTrigger\",\"connection\":\"AzureWebJobsStorage\",\"queueName\":\"myqueue - 1\",\"name\":\"myQueueItem\",\"functionName\":\"Function2\"}], \"functions\":[]}")]
[InlineData("Invalid json")]
[InlineData(null)]
public void GetScaleTriggersTest(string functionTriggerPayload)
Expand Down