Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

#nullable enable

using System;
using System.Collections.Generic;

namespace Microsoft.Azure.WebJobs.Script.AppCapabilities
{
public sealed class AppCapabilitiesOptions
{
private readonly Dictionary<string, string> _capabilities;

public AppCapabilitiesOptions()
Copy link
Member Author

Choose a reason for hiding this comment

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

Similar setup to

{
_capabilities = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

public Dictionary<string, string> Capabilities => _capabilities;
}
}
2 changes: 1 addition & 1 deletion src/WebJobs.Script.Abstractions/Directory.Version.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.0.4</VersionPrefix>
<VersionSuffix>preview</VersionSuffix>
<VersionSuffix>preview2</VersionSuffix>
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: remove test change

</PropertyGroup>
</Project>
32 changes: 32 additions & 0 deletions src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Google.Protobuf.WellKnownTypes;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Azure.WebJobs.Script.AppCapabilities;
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
Expand Down Expand Up @@ -93,6 +94,7 @@ internal partial class GrpcWorkerChannel : IRpcWorkerChannel, IDisposable
private System.Timers.Timer _timer;
private bool _functionMetadataRequestSent = false;
private IOptions<ScriptJobHostOptions> _scriptHostOptions;
private IAppCapabilitiesProvider _appCapabilitiesProvider;

internal GrpcWorkerChannel(
string workerId,
Expand All @@ -108,6 +110,7 @@ internal GrpcWorkerChannel(
ISharedMemoryManager sharedMemoryManager,
IOptions<WorkerConcurrencyOptions> workerConcurrencyOptions,
IOptions<FunctionsHostingConfigOptions> hostingConfigOptions,
IAppCapabilitiesProvider appCapabilitiesProvider,
IHttpProxyService httpProxyService)
{
_workerId = workerId;
Expand All @@ -127,6 +130,7 @@ internal GrpcWorkerChannel(

_httpProxyService = httpProxyService;
_workerCapabilities = new GrpcCapabilities(_workerChannelLogger);
_appCapabilitiesProvider = appCapabilitiesProvider;

if (!_eventManager.TryGetGrpcChannels(workerId, out var inbound, out var outbound))
{
Expand Down Expand Up @@ -535,6 +539,7 @@ internal void WorkerInitResponse(GrpcEvent initEvent)
_state = _state | RpcWorkerChannelState.Initialized;

ApplyCapabilities(_initMessage.Capabilities);
RegisterAppCapabilities(_initMessage.AppCapabilities);

_workerInitTask.TrySetResult(true);
}
Expand Down Expand Up @@ -610,6 +615,33 @@ internal void ApplyCapabilities(IDictionary<string, string> capabilities, GrpcCa
}
}

/// <summary>
/// Registers app capabilities from the worker init response into the AppCapabilitiesProvider.
/// </summary>
/// <param name="appCapabilities">The app capabilities map from the worker.</param>
private void RegisterAppCapabilities(MapField<string, string> appCapabilities)
{
if (appCapabilities is null || appCapabilities.Count == 0)
{
return;
}

try
{
_workerChannelLogger.LogDebug("Registering {count} app capabilities from worker '{workerId}')",
appCapabilities.Count, _workerId);

foreach (var capability in appCapabilities)
{
_appCapabilitiesProvider.SetCapability(capability.Key, capability.Value);
}
}
catch (Exception ex)
{
_workerChannelLogger.LogWarning(ex, "Failed to register app capabilities from worker '{workerId}'", _workerId);
}
}

public void SetupFunctionInvocationBuffers(IEnumerable<FunctionMetadata> functions)
{
_functions = functions;
Expand Down
12 changes: 9 additions & 3 deletions src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.WebJobs.Script.AppCapabilities;
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Eventing;
Expand All @@ -28,11 +29,13 @@ internal class GrpcWorkerChannelFactory : IRpcWorkerChannelFactory
private readonly ISharedMemoryManager _sharedMemoryManager = null;
private readonly IOptions<WorkerConcurrencyOptions> _workerConcurrencyOptions;
private readonly IOptions<FunctionsHostingConfigOptions> _hostingConfigOptions;
private readonly IAppCapabilitiesProvider _appCapabilityProvider;
private readonly IHttpProxyService _httpProxyService;

public GrpcWorkerChannelFactory(IScriptEventManager eventManager, IScriptHostManager hostManager, IEnvironment environment, ILoggerFactory loggerFactory,
IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IRpcWorkerProcessFactory rpcWorkerProcessManager, ISharedMemoryManager sharedMemoryManager,
IOptions<WorkerConcurrencyOptions> workerConcurrencyOptions, IOptions<FunctionsHostingConfigOptions> hostingConfigOptions, IHttpProxyService httpProxyService)
IOptions<WorkerConcurrencyOptions> workerConcurrencyOptions, IOptions<FunctionsHostingConfigOptions> hostingConfigOptions,
IAppCapabilitiesProvider appCapabilityProvider, IHttpProxyService httpProxyService)
{
_eventManager = eventManager;
_hostManager = hostManager;
Expand All @@ -43,6 +46,7 @@ public GrpcWorkerChannelFactory(IScriptEventManager eventManager, IScriptHostMan
_sharedMemoryManager = sharedMemoryManager;
_workerConcurrencyOptions = workerConcurrencyOptions;
_hostingConfigOptions = hostingConfigOptions;
_appCapabilityProvider = appCapabilityProvider;
_httpProxyService = httpProxyService;
}

Expand All @@ -59,12 +63,13 @@ public IRpcWorkerChannel Create(string scriptRootPath, string runtime, IMetricsL
IWorkerProcess rpcWorkerProcess = _rpcWorkerProcessFactory.Create(workerId, runtime, scriptRootPath, languageWorkerConfig);

return CreateInternal(workerId, _eventManager, _hostManager, languageWorkerConfig, rpcWorkerProcess, workerLogger, metricsLogger, attemptCount,
_environment, _applicationHostOptions, _sharedMemoryManager, _workerConcurrencyOptions, _hostingConfigOptions, _httpProxyService);
_environment, _applicationHostOptions, _sharedMemoryManager, _workerConcurrencyOptions, _hostingConfigOptions, _appCapabilityProvider, _httpProxyService);
}

internal virtual IRpcWorkerChannel CreateInternal(string workerId, IScriptEventManager eventManager, IScriptHostManager hostManager, RpcWorkerConfig languageWorkerConfig, IWorkerProcess rpcWorkerProcess,
ILogger workerLogger, IMetricsLogger metricsLogger, int attemptCount, IEnvironment environment, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions,
ISharedMemoryManager sharedMemoryManager, IOptions<WorkerConcurrencyOptions> workerConcurrencyOptions, IOptions<FunctionsHostingConfigOptions> hostingConfigOptions, IHttpProxyService httpProxyService)
ISharedMemoryManager sharedMemoryManager, IOptions<WorkerConcurrencyOptions> workerConcurrencyOptions, IOptions<FunctionsHostingConfigOptions> hostingConfigOptions,
IAppCapabilitiesProvider appCapabilitiesProvider, IHttpProxyService httpProxyService)
{
return new GrpcWorkerChannel(
workerId,
Expand All @@ -80,6 +85,7 @@ internal virtual IRpcWorkerChannel CreateInternal(string workerId, IScriptEventM
sharedMemoryManager,
workerConcurrencyOptions,
hostingConfigOptions,
appCapabilitiesProvider,
httpProxyService);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ message WorkerInitResponse {

// Worker metadata captured for telemetry purposes
WorkerMetadata worker_metadata = 4;

// App capabilities or features (different from worker capabilities above)
map<string,string> app_capabilities = 5;
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: Move this change to protobuf repo, make sure that is merged into host before this PR

}

message WorkerMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs.Script.AppCapabilities;
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
using Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.Policies;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.WebJobs.Script.WebHost.Controllers
{
public class AppCapabilitiesController
{
private readonly AppCapabilitiesOptions _capabilitiesOptions;

public AppCapabilitiesController(IOptions<AppCapabilitiesOptions> capabilitiesOptions)
{
_capabilitiesOptions = capabilitiesOptions.Value;
}

[HttpGet]
[Route("admin/capabilities")]
[Authorize(Policy = PolicyNames.AdminAuthLevel)]
[RequiresRunningHost]
public IActionResult GetCapabilities([FromServices] IAppCapabilitiesProvider appCapabilitiesProvider)
{
// Get capabilities from options
var optionsCapabilities = _capabilitiesOptions.Capabilities ?? new Dictionary<string, string>();

// Get capabilities from provider (worker)
var providerCapabilities = appCapabilitiesProvider?.GetCapabilities() ?? new Dictionary<string, string>();

// Merge: worker provider wins on collision
var merged = optionsCapabilities
.Concat(providerCapabilities)
.GroupBy(kvp => kvp.Key, System.StringComparer.OrdinalIgnoreCase)
.Select(g => g.Last()) // provider comes after options, so Last() wins
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, System.StringComparer.OrdinalIgnoreCase);

return new OkObjectResult(merged);
}

[HttpGet]
[Route("admin/capabilities/{name}")]
[Authorize(Policy = PolicyNames.AdminAuthLevel)]
[RequiresRunningHost]
public IActionResult Get(string name, [FromServices] IAppCapabilitiesProvider appCapabilitiesProvider)
{
// check worker provider first
var providerCapabilities = appCapabilitiesProvider?.GetCapabilities();
var providerCapability = providerCapabilities?.FirstOrDefault(kvp => string.Equals(kvp.Key, name, System.StringComparison.OrdinalIgnoreCase));

if (providerCapability is KeyValuePair<string, string> kvp && kvp.Key is not null)
{
return new OkObjectResult(kvp.Value);
}

// check options
var optionsCapabilities = _capabilitiesOptions.Capabilities;
var optionCapability = optionsCapabilities.FirstOrDefault(kvp => string.Equals(kvp.Key, name, System.StringComparison.OrdinalIgnoreCase));

if (optionCapability.Key is not null)
{
return new OkObjectResult(optionCapability.Value);
}

return new NotFoundResult();
}
}
}
6 changes: 5 additions & 1 deletion src/WebJobs.Script.WebHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Development",
"AzureWebJobsScriptRoot": "C:\\Users\\sarahvu\\source\\repos\\Dev-Apps\\SimpleHttpTestCapabilities\\SimpleHttpTestCapabilities\\bin\\Debug\\net8.0",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AZURE_FUNCTIONS_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:62513;http://localhost:62514"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static void AddScriptPolicies(this AuthorizationOptions options)
options.AddPolicy(PolicyNames.AdminAuthLevel, p =>
{
p.AddScriptAuthenticationSchemes();
p.AddRequirements(new AuthLevelRequirement(AuthorizationLevel.Admin));
//p.AddRequirements(new AuthLevelRequirement(AuthorizationLevel.Admin));
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: remove local testing change

p.RequireAssertion(c =>
{
if (c.Resource is AuthorizationFilterContext filterContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Azure.Functions.Platform.Metrics.LinuxConsumption;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Script.AppCapabilities;
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Configuration;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
Expand Down Expand Up @@ -252,6 +253,9 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
// Add health checks
services.AddMetrics();
services.AddHealthChecks().AddWebJobsScriptHealthChecks();

// App Capabilities Provider
services.AddSingleton<IAppCapabilitiesProvider, DefaultAppCapabilitiesProvider>();
}

internal static void AddHostingConfigOptions(this IServiceCollection services, IConfiguration configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;

#nullable enable

namespace Microsoft.Azure.WebJobs.Script.AppCapabilities
{
internal sealed class AppCapabilitiesOptionsSetup : IConfigureOptions<AppCapabilitiesOptions>
{
private readonly IConfiguration _configuration;
private readonly string configSectionName = "azureFunctionsJobHost:appCapabilities";
private readonly ILogger<AppCapabilitiesOptionsSetup> _logger;

public AppCapabilitiesOptionsSetup(
IConfiguration configuration,
ILogger<AppCapabilitiesOptionsSetup> logger)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

/// <summary>
/// Configures the <see cref="AppCapabilitiesOptions"/> by reading from known configuration sources.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(AppCapabilitiesOptions options)
{
var jobHostCapabilitiesSection = _configuration.GetSection(configSectionName);
if (jobHostCapabilitiesSection.Exists())
{
AddCapabilitiesFromSection(options, jobHostCapabilitiesSection);
}
}

/// <summary>
/// Adds capabilities from a configuration section.
/// </summary>
/// <param name="options">The options to add capabilities to.</param>
/// <param name="section">The configuration section containing capability definitions.</param>
private void AddCapabilitiesFromSection(
AppCapabilitiesOptions options,
IConfigurationSection section)
{
var children = section.GetChildren();
_logger.LogDebug("Loading App Capabilities from configuration section '{sectionName}' with {count} entries.",
section.Path, children.Count());

foreach (var child in children)
{
var capabilityName = child.Key;
var capabilityValue = child.Value;

options.Capabilities[capabilityName] = capabilityValue ?? string.Empty;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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;

namespace Microsoft.Azure.WebJobs.Script.AppCapabilities
{
internal class DefaultAppCapabilitiesProvider : IAppCapabilitiesProvider
{
private readonly Dictionary<string, string> _capabilities = new Dictionary<string, string>();

public Dictionary<string, string> GetCapabilities()
{
return _capabilities;
}

public void SetCapability(string capability, string value)
{
_capabilities[capability] = value;
}
}
}
14 changes: 14 additions & 0 deletions src/WebJobs.Script/AppCapabilities/IAppCapabilitiesProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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;

namespace Microsoft.Azure.WebJobs.Script.AppCapabilities
{
public interface IAppCapabilitiesProvider
{
Dictionary<string, string> GetCapabilities();

void SetCapability(string capability, string value);
}
}
Loading
Loading