Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/WebJobs.Script/ScriptHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using Microsoft.Azure.WebJobs.Script.StorageProvider;
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.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -295,6 +296,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 @@ -346,6 +351,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>
/// Set the different profiles for a given worker runtime language
/// </summary>
void SetWorkerDescriptionProfiles(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