Skip to content
7 changes: 7 additions & 0 deletions src/WebJobs.Script/ScriptHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Microsoft.Azure.WebJobs.Script.Scale;
using Microsoft.Azure.WebJobs.Script.Workers;
using Microsoft.Azure.WebJobs.Script.Workers.Http;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -291,6 +292,10 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp

services.AddSingleton<IFileLoggingStatusManager, FileLoggingStatusManager>();

services.AddSingleton<IWorkerProfileManager, WorkerProfileManager>();

services.AddSingleton<IWorkerProfileConditionProvider, WorkerProfileConditionProvider>();

if (!applicationHostOptions.HasParentScope)
{
AddCommonServices(services);
Expand Down Expand Up @@ -334,6 +339,8 @@ public static void AddCommonServices(IServiceCollection services)
services.TryAddSingleton<IWorkerConsoleLogSource, WorkerConsoleLogSource>();
services.AddSingleton<IWorkerProcessFactory, DefaultWorkerProcessFactory>();
services.AddSingleton<IRpcWorkerProcessFactory, RpcWorkerProcessFactory>();
services.AddSingleton<IWorkerProfileManager, WorkerProfileManager>();
services.AddSingleton<IWorkerProfileConditionProvider, WorkerProfileConditionProvider>();
services.TryAddSingleton<IWebHostRpcWorkerChannelManager, WebHostRpcWorkerChannelManager>();
services.TryAddSingleton<IDebugManager, DebugManager>();
services.TryAddSingleton<IDebugStateProvider, DebugStateProvider>();
Expand Down
82 changes: 82 additions & 0 deletions src/WebJobs.Script/Workers/Profiles/EnvironmentCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.WebJobs.Script.Workers
{
/// <summary>
/// An implementation of an <see cref="IWorkerProfileCondition"/> that checks if
/// environment variables match the expected output
/// </summary>
public class EnvironmentCondition : IWorkerProfileCondition
{
private readonly ILogger _logger;
private readonly IEnvironment _environment;
private readonly string _name;
private readonly string _expression;
private Regex _regex;

internal EnvironmentCondition(ILogger logger, IEnvironment environment, WorkerProfileConditionDescriptor descriptor)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_environment = environment ?? throw new ArgumentNullException(nameof(environment));

descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);

Validate();
}

public string Name => _name;

public string Expression => _expression;

/// <inheritdoc />
public bool Evaluate()
{
string value = _environment.GetEnvironmentVariable(Name);

if (string.IsNullOrEmpty(value))
{
return false;
}

_logger.LogDebug($"Evaluating EnvironmentCondition with value '{value}' and expression '{Expression}'");

return _regex.IsMatch(value);
}

// Validates if condition parametrs meet expected values, fail if they don't
private void Validate()
{
if (string.IsNullOrEmpty(Name))
{
throw new ValidationException($"EnvironmentCondition {nameof(Name)} cannot be empty.");
}

if (string.IsNullOrEmpty(Expression))
{
throw new ValidationException($"EnvironmentCondition {nameof(Expression)} cannot be empty.");
}

try
{
_regex = new Regex(Expression);
}
catch
{
throw new ValidationException($"EnvironmentCondition {nameof(Expression)} must be a valid regular expression.");
}
}
}
}
17 changes: 17 additions & 0 deletions src/WebJobs.Script/Workers/Profiles/FalseCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.Azure.WebJobs.Script.Workers
{
/// <summary>
/// An implementation of an <see cref="IWorkerProfileCondition"/> that always evaluates to false.
/// This condition is used to disable a profile when condition providers fail to resolve conditions.
/// </summary>
public class FalseCondition : IWorkerProfileCondition
{
public bool Evaluate()
{
return false;
}
}
}
104 changes: 104 additions & 0 deletions src/WebJobs.Script/Workers/Profiles/HostPropertyCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.WebJobs.Script.Workers
{
/// <summary>
/// An implementation of an <see cref="IWorkerProfileCondition"/> that checks if different host properties
/// such as Sku, Platform, HostVersion match the expected output
/// </summary>
public class HostPropertyCondition : IWorkerProfileCondition
{
private readonly ILogger _logger;
private readonly ISystemRuntimeInformation _systemRuntimeInformation;
private readonly string _name;
private readonly string _expression;
private Regex _regex;

public HostPropertyCondition(ILogger logger, ISystemRuntimeInformation systemRuntimeInformation, WorkerProfileConditionDescriptor descriptor)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_systemRuntimeInformation = systemRuntimeInformation ?? throw new ArgumentNullException(nameof(systemRuntimeInformation));

descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);

Validate();
}

public enum HostProperty
{
Sku,
Platform,
HostVersion
}

public string Name => _name;

public string Expression => _expression;

/// <inheritdoc />
public bool Evaluate()
{
var hostPropertyName = Enum.Parse(typeof(HostProperty), Name, true);

string value = hostPropertyName switch
{
HostProperty.Sku => ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.AzureWebsiteSku),
HostProperty.Platform => _systemRuntimeInformation.GetOSPlatform().ToString(),
HostProperty.HostVersion => ScriptHost.Version,
_ => null
};

if (string.IsNullOrEmpty(value))
{
return false;
}

_logger.LogDebug($"Evaluating HostPropertyCondition with value: {value} and expression {Expression}");

return _regex.IsMatch(value);
}

// Validates if condition parametrs meet expected values, fail if they don't
private void Validate()
{
if (string.IsNullOrEmpty(Name))
{
throw new ValidationException($"HostPropertyCondition {nameof(Name)} cannot be empty.");
}

if (!Enum.GetNames(typeof(HostProperty)).Any(x => x.ToLower().Contains(Name.ToLower())))
{
throw new ValidationException($"HostPropertyCondition {nameof(Name)} is not a valid host property name.");
}

if (string.IsNullOrEmpty(Expression))
{
throw new ValidationException($"HostPropertyCondition {nameof(Expression)} cannot be empty.");
}

try
{
_regex = new Regex(Expression);
}
catch
{
throw new ValidationException($"HostPropertyCondition {nameof(Expression)} must be a valid regular expression.");
}
}
}
}
16 changes: 16 additions & 0 deletions src/WebJobs.Script/Workers/Profiles/IWorkerProfileCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Azure.WebJobs.Script.Workers
{
/// <summary>
/// Interface for different types of profile conditions
/// </summary>
public interface IWorkerProfileCondition
{
/// <summary>
/// Check if different condition type meet their criteria
/// </summary>
bool Evaluate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Azure.WebJobs.Script.Workers.Profiles
{
/// <summary>
/// Interface to manage different profile condition providers
/// </summary>
internal interface IWorkerProfileConditionProvider
{
/// <summary>
/// Factory method to create a profile condition
/// </summary>
bool TryCreateCondition(WorkerProfileConditionDescriptor descriptor, out IWorkerProfileCondition condition);
}
}
35 changes: 35 additions & 0 deletions src/WebJobs.Script/Workers/Profiles/IWorkerProfileManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;

namespace Microsoft.Azure.WebJobs.Script.Workers
{
/// <summary>
/// Interface to regulate profile operations through the profile manager
/// </summary>
public interface IWorkerProfileManager
{
/// <summary>
/// Creates profile condition using different condition descriptor properties
/// </summary>
bool TryCreateWorkerProfileCondition(WorkerProfileConditionDescriptor conditionDescriptor, out IWorkerProfileCondition condition);

/// <summary>
/// Save different profiles for a given worker runtime language
/// </summary>
void SaveWorkerDescriptionProfiles(List<WorkerDescriptionProfile> workerDescriptionProfiles, string language);

/// <summary>
/// Load a profile that meets it's conditions
/// </summary>
void LoadWorkerDescriptionFromProfiles(RpcWorkerDescription defaultWorkerDescription, out RpcWorkerDescription workerDescription);

/// <summary>
/// Verify if the current profile's conditions have changed
/// </summary>
bool IsCorrectProfileLoaded(string workerRuntime);
}
}
Loading