Skip to content

Commit 158067f

Browse files
Conditional based profiles (#8475)
Conditional based profiles WIP: Example refactoring using registered services Use the Profile manager to store, load and evaluate profiles Addressing review comments Fixed dependency injection and review comments Fixing unit tests Updating WorkerProfileConditionProvider and tests Updated tests
1 parent ad6e6ae commit 158067f

29 files changed

+1134
-42
lines changed

src/WebJobs.Script/ScriptHostBuilderExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using Microsoft.Azure.WebJobs.Script.StorageProvider;
3030
using Microsoft.Azure.WebJobs.Script.Workers;
3131
using Microsoft.Azure.WebJobs.Script.Workers.Http;
32+
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
3233
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
3334
using Microsoft.Extensions.Configuration;
3435
using Microsoft.Extensions.DependencyInjection;
@@ -295,6 +296,10 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp
295296

296297
services.AddSingleton<IFileLoggingStatusManager, FileLoggingStatusManager>();
297298

299+
services.AddSingleton<IWorkerProfileManager, WorkerProfileManager>();
300+
301+
services.AddSingleton<IWorkerProfileConditionProvider, WorkerProfileConditionProvider>();
302+
298303
if (!applicationHostOptions.HasParentScope)
299304
{
300305
AddCommonServices(services);
@@ -346,6 +351,8 @@ public static void AddCommonServices(IServiceCollection services)
346351
services.TryAddSingleton<IWorkerConsoleLogSource, WorkerConsoleLogSource>();
347352
services.AddSingleton<IWorkerProcessFactory, DefaultWorkerProcessFactory>();
348353
services.AddSingleton<IRpcWorkerProcessFactory, RpcWorkerProcessFactory>();
354+
services.AddSingleton<IWorkerProfileManager, WorkerProfileManager>();
355+
services.AddSingleton<IWorkerProfileConditionProvider, WorkerProfileConditionProvider>();
349356
services.TryAddSingleton<IWebHostRpcWorkerChannelManager, WebHostRpcWorkerChannelManager>();
350357
services.TryAddSingleton<IDebugManager, DebugManager>();
351358
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)