Skip to content

Commit bed3b56

Browse files
Conditional based profiles for workers (#8365)
* Conditional based profiles * WIP: Example refactoring using registered services * Use the Profile manager to store, load and evaluate profiles * Addressing review comments * Adding unit tests for profiles * Fixed dependency injection and review comments * Fixing unit tests * Updating WorkerProfileConditionProvider and tests * Rename SetWorkerDescriptionProfiles Co-authored-by: Fabio Cavalcante <[email protected]>
1 parent b568b7f commit bed3b56

28 files changed

+1092
-42
lines changed

src/WebJobs.Script/ScriptHostBuilderExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using Microsoft.Azure.WebJobs.Script.Scale;
2828
using Microsoft.Azure.WebJobs.Script.Workers;
2929
using Microsoft.Azure.WebJobs.Script.Workers.Http;
30+
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
3031
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
3132
using Microsoft.Extensions.Azure;
3233
using Microsoft.Extensions.Configuration;
@@ -291,6 +292,10 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp
291292

292293
services.AddSingleton<IFileLoggingStatusManager, FileLoggingStatusManager>();
293294

295+
services.AddSingleton<IWorkerProfileManager, WorkerProfileManager>();
296+
297+
services.AddSingleton<IWorkerProfileConditionProvider, WorkerProfileConditionProvider>();
298+
294299
if (!applicationHostOptions.HasParentScope)
295300
{
296301
AddCommonServices(services);
@@ -334,6 +339,8 @@ public static void AddCommonServices(IServiceCollection services)
334339
services.TryAddSingleton<IWorkerConsoleLogSource, WorkerConsoleLogSource>();
335340
services.AddSingleton<IWorkerProcessFactory, DefaultWorkerProcessFactory>();
336341
services.AddSingleton<IRpcWorkerProcessFactory, RpcWorkerProcessFactory>();
342+
services.AddSingleton<IWorkerProfileManager, WorkerProfileManager>();
343+
services.AddSingleton<IWorkerProfileConditionProvider, WorkerProfileConditionProvider>();
337344
services.TryAddSingleton<IWebHostRpcWorkerChannelManager, WebHostRpcWorkerChannelManager>();
338345
services.TryAddSingleton<IDebugManager, DebugManager>();
339346
services.TryAddSingleton<IDebugStateProvider, DebugStateProvider>();
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.ComponentModel.DataAnnotations;
6+
using System.Text.RegularExpressions;
7+
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Microsoft.Azure.WebJobs.Script.Workers
11+
{
12+
/// <summary>
13+
/// An implementation of an <see cref="IWorkerProfileCondition"/> that checks if
14+
/// environment variables match the expected output
15+
/// </summary>
16+
public class EnvironmentCondition : IWorkerProfileCondition
17+
{
18+
private readonly ILogger _logger;
19+
private readonly IEnvironment _environment;
20+
private readonly string _name;
21+
private readonly string _expression;
22+
private Regex _regex;
23+
24+
internal EnvironmentCondition(ILogger logger, IEnvironment environment, WorkerProfileConditionDescriptor descriptor)
25+
{
26+
if (descriptor is null)
27+
{
28+
throw new ArgumentNullException(nameof(descriptor));
29+
}
30+
31+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
32+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
33+
34+
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
35+
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);
36+
37+
Validate();
38+
}
39+
40+
public string Name => _name;
41+
42+
public string Expression => _expression;
43+
44+
/// <inheritdoc />
45+
public bool Evaluate()
46+
{
47+
string value = _environment.GetEnvironmentVariable(Name);
48+
49+
if (string.IsNullOrEmpty(value))
50+
{
51+
return false;
52+
}
53+
54+
_logger.LogDebug($"Evaluating EnvironmentCondition with value '{value}' and expression '{Expression}'");
55+
56+
return _regex.IsMatch(value);
57+
}
58+
59+
// Validates if condition parametrs meet expected values, fail if they don't
60+
private void Validate()
61+
{
62+
if (string.IsNullOrEmpty(Name))
63+
{
64+
throw new ValidationException($"EnvironmentCondition {nameof(Name)} cannot be empty.");
65+
}
66+
67+
if (string.IsNullOrEmpty(Expression))
68+
{
69+
throw new ValidationException($"EnvironmentCondition {nameof(Expression)} cannot be empty.");
70+
}
71+
72+
try
73+
{
74+
_regex = new Regex(Expression);
75+
}
76+
catch
77+
{
78+
throw new ValidationException($"EnvironmentCondition {nameof(Expression)} must be a valid regular expression.");
79+
}
80+
}
81+
}
82+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Azure.WebJobs.Script.Workers
5+
{
6+
/// <summary>
7+
/// An implementation of an <see cref="IWorkerProfileCondition"/> that always evaluates to false.
8+
/// This condition is used to disable a profile when condition providers fail to resolve conditions.
9+
/// </summary>
10+
public class FalseCondition : IWorkerProfileCondition
11+
{
12+
public bool Evaluate()
13+
{
14+
return false;
15+
}
16+
}
17+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.ComponentModel.DataAnnotations;
6+
using System.Linq;
7+
using System.Text.RegularExpressions;
8+
using Microsoft.Azure.WebJobs.Script.Config;
9+
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
10+
using Microsoft.Extensions.Logging;
11+
12+
namespace Microsoft.Azure.WebJobs.Script.Workers
13+
{
14+
/// <summary>
15+
/// An implementation of an <see cref="IWorkerProfileCondition"/> that checks if different host properties
16+
/// such as Sku, Platform, HostVersion match the expected output
17+
/// </summary>
18+
public class HostPropertyCondition : IWorkerProfileCondition
19+
{
20+
private readonly ILogger _logger;
21+
private readonly ISystemRuntimeInformation _systemRuntimeInformation;
22+
private readonly string _name;
23+
private readonly string _expression;
24+
private Regex _regex;
25+
26+
public HostPropertyCondition(ILogger logger, ISystemRuntimeInformation systemRuntimeInformation, WorkerProfileConditionDescriptor descriptor)
27+
{
28+
if (descriptor is null)
29+
{
30+
throw new ArgumentNullException(nameof(descriptor));
31+
}
32+
33+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
34+
_systemRuntimeInformation = systemRuntimeInformation ?? throw new ArgumentNullException(nameof(systemRuntimeInformation));
35+
36+
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
37+
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);
38+
39+
Validate();
40+
}
41+
42+
public enum HostProperty
43+
{
44+
Sku,
45+
Platform,
46+
HostVersion
47+
}
48+
49+
public string Name => _name;
50+
51+
public string Expression => _expression;
52+
53+
/// <inheritdoc />
54+
public bool Evaluate()
55+
{
56+
var hostPropertyName = Enum.Parse(typeof(HostProperty), Name, true);
57+
58+
string value = hostPropertyName switch
59+
{
60+
HostProperty.Sku => ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.AzureWebsiteSku),
61+
HostProperty.Platform => _systemRuntimeInformation.GetOSPlatform().ToString(),
62+
HostProperty.HostVersion => ScriptHost.Version,
63+
_ => null
64+
};
65+
66+
if (string.IsNullOrEmpty(value))
67+
{
68+
return false;
69+
}
70+
71+
_logger.LogDebug($"Evaluating HostPropertyCondition with value: {value} and expression {Expression}");
72+
73+
return _regex.IsMatch(value);
74+
}
75+
76+
// Validates if condition parametrs meet expected values, fail if they don't
77+
private void Validate()
78+
{
79+
if (string.IsNullOrEmpty(Name))
80+
{
81+
throw new ValidationException($"HostPropertyCondition {nameof(Name)} cannot be empty.");
82+
}
83+
84+
if (!Enum.GetNames(typeof(HostProperty)).Any(x => x.ToLower().Contains(Name.ToLower())))
85+
{
86+
throw new ValidationException($"HostPropertyCondition {nameof(Name)} is not a valid host property name.");
87+
}
88+
89+
if (string.IsNullOrEmpty(Expression))
90+
{
91+
throw new ValidationException($"HostPropertyCondition {nameof(Expression)} cannot be empty.");
92+
}
93+
94+
try
95+
{
96+
_regex = new Regex(Expression);
97+
}
98+
catch
99+
{
100+
throw new ValidationException($"HostPropertyCondition {nameof(Expression)} must be a valid regular expression.");
101+
}
102+
}
103+
}
104+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
namespace Microsoft.Azure.WebJobs.Script.Workers
5+
{
6+
/// <summary>
7+
/// Interface for different types of profile conditions
8+
/// </summary>
9+
public interface IWorkerProfileCondition
10+
{
11+
/// <summary>
12+
/// Check if different condition type meet their criteria
13+
/// </summary>
14+
bool Evaluate();
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
namespace Microsoft.Azure.WebJobs.Script.Workers.Profiles
5+
{
6+
/// <summary>
7+
/// Interface to manage different profile condition providers
8+
/// </summary>
9+
internal interface IWorkerProfileConditionProvider
10+
{
11+
/// <summary>
12+
/// Factory method to create a profile condition
13+
/// </summary>
14+
bool TryCreateCondition(WorkerProfileConditionDescriptor descriptor, out IWorkerProfileCondition condition);
15+
}
16+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
6+
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.Workers
9+
{
10+
/// <summary>
11+
/// Interface to regulate profile operations through the profile manager
12+
/// </summary>
13+
public interface IWorkerProfileManager
14+
{
15+
/// <summary>
16+
/// Creates profile condition using different condition descriptor properties
17+
/// </summary>
18+
bool TryCreateWorkerProfileCondition(WorkerProfileConditionDescriptor conditionDescriptor, out IWorkerProfileCondition condition);
19+
20+
/// <summary>
21+
/// Set the different profiles for a given worker runtime language
22+
/// </summary>
23+
void SetWorkerDescriptionProfiles(List<WorkerDescriptionProfile> workerDescriptionProfiles, string language);
24+
25+
/// <summary>
26+
/// Load a profile that meets it's conditions
27+
/// </summary>
28+
void LoadWorkerDescriptionFromProfiles(RpcWorkerDescription defaultWorkerDescription, out RpcWorkerDescription workerDescription);
29+
30+
/// <summary>
31+
/// Verify if the current profile's conditions have changed
32+
/// </summary>
33+
bool IsCorrectProfileLoaded(string workerRuntime);
34+
}
35+
}

0 commit comments

Comments
 (0)