Example #1
0
        internal void TraceConfiguration(EndToEndTraceHelper traceHelper, JObject storageProviderConfig)
        {
            // Clone the options to avoid making changes to the original.
            // We make updates to the clone rather than to JSON to ensure we're updating what we think we're updating.
            DurableTaskOptions clone = JObject.FromObject(this).ToObject <DurableTaskOptions>();

            // At this stage the task hub name is expected to have been resolved. However, we want to know
            // what the original value was in addition to the resolved value, so we're updating the JSON
            // blob property to use the original, unresolved value.
            clone.HubName = this.originalHubName;

            // Format the options data as JSON in a way that is friendly for technical humans to read.
            JObject configurationJson = JObject.FromObject(
                clone,
                new JsonSerializer
            {
                Converters       = { new StringEnumConverter() },
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
            });

            if (storageProviderConfig.Count != 0)
            {
                configurationJson["storageProvider"] = storageProviderConfig;
            }

            // This won't be exactly the same as what is declared in host.json because any unspecified values
            // will have been initialized with their defaults. We need the Functions runtime to handle tracing
            // of the actual host.json values: https://github.com/Azure/azure-functions-host/issues/5422.
            traceHelper.TraceConfiguration(this.HubName, configurationJson.ToString(Formatting.None));
        }
Example #2
0
        internal void TraceConfiguration(EndToEndTraceHelper traceHelper)
        {
            // Clone the options to avoid making changes to the original.
            // We make updates to the clone rather than to JSON to ensure we're updating what we think we're updating.
            DurableTaskOptions clone = JObject.FromObject(this).ToObject <DurableTaskOptions>();

            // Don't trace the notification URL query string since it may contain secrets.
            // This is the only property which we expect to contain secrets. Everything else should be *names*
            // of secrets that are resolved later from environment variables, etc.
            if (clone.NotificationUrl != null)
            {
                clone.NotificationUrl = new Uri(clone.NotificationUrl.GetLeftPart(UriPartial.Path));
            }

            // At this stage the task hub name is expected to have been resolved. However, we want to know
            // what the original value was in addition to the resolved value, so we're updating the JSON
            // blob property to use the original, unresolved value.
            clone.HubName = this.originalHubName;

            // Format the options data as JSON in a way that is friendly for technical humans to read.
            JObject configurationJson = JObject.FromObject(
                clone,
                new JsonSerializer
            {
                Converters       = { new StringEnumConverter() },
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
            });

            // This won't be exactly the same as what is declared in host.json because any unspecified values
            // will have been initialized with their defaults. We need the Functions runtime to handle tracing
            // of the actual host.json values: https://github.com/Azure/azure-functions-host/issues/5422.
            traceHelper.TraceConfiguration(this.HubName, configurationJson.ToString(Formatting.None));
        }
 /// <summary>
 /// Create an EventSourceListener to capture and log Durable EventSource
 /// data in Linux.
 /// </summary>
 /// <param name="logger">A LinuxAppService logger configured for the current linux host.</param>
 /// <param name="enableVerbose">If true, durableTask.Core verbose logs are enabled. The opposite if false.</param>
 /// <param name="traceHelper">A tracing client to log exceptions.</param>
 /// <param name="durabilityProviderEventSourceName">The durability provider's event source name.</param>
 public EventSourceListener(LinuxAppServiceLogger logger, bool enableVerbose, EndToEndTraceHelper traceHelper, string durabilityProviderEventSourceName)
 {
     this.logger         = logger;
     this.disableVerbose = !enableVerbose; // We track the opposite value ro simplify logic later
     this.traceHelper    = traceHelper;
     this.durabilityProviderEventSourceName = durabilityProviderEventSourceName;
 }
        void IExtensionConfigProvider.Initialize(ExtensionConfigContext context)
        {
            ConfigureLoaderHooks();

            context.ApplyConfig(this, "DurableTask");

            // Register the trigger bindings
            JobHostConfiguration hostConfig = context.Config;

            this.traceHelper    = new EndToEndTraceHelper(context.Trace);
            this.httpApiHandler = new HttpApiHandler(this, context.Trace);

            // Register the non-trigger bindings, which have a different model.
            var bindings = new BindingHelper(this, this.traceHelper);

            // For 202 support
            if (this.NotificationUrl == null)
            {
                this.NotificationUrl = context.GetWebhookHandler();
            }

            // Note that the order of the rules is important
            var rule = context.AddBindingRule <OrchestrationClientAttribute>()
                       .AddConverter <JObject, StartOrchestrationArgs>(bindings.JObjectToStartOrchestrationArgs);

            rule.BindToCollector <StartOrchestrationArgs>(bindings.CreateAsyncCollector);
            rule.BindToInput <DurableOrchestrationClient>(GetClient);

            context.AddBindingRule <OrchestrationTriggerAttribute>()
            .BindToTrigger(new OrchestrationTriggerAttributeBindingProvider(this, context, this.traceHelper));

            context.AddBindingRule <ActivityTriggerAttribute>()
            .BindToTrigger(new ActivityTriggerAttributeBindingProvider(this, context, this.traceHelper));
        }
Example #5
0
 public ActivityTriggerAttributeBindingProvider(
     DurableTaskExtension durableTaskConfig,
     ExtensionConfigContext extensionContext,
     EndToEndTraceHelper traceHelper)
 {
     this.durableTaskConfig = durableTaskConfig;
     this.extensionContext  = extensionContext;
     this.traceHelper       = traceHelper;
 }
Example #6
0
 public OrchestrationTriggerAttributeBindingProvider(
     DurableTaskExtension config,
     ExtensionConfigContext extensionContext,
     EndToEndTraceHelper traceHelper)
 {
     this.config           = config;
     this.extensionContext = extensionContext;
     this.traceHelper      = traceHelper;
 }
Example #7
0
 public EntityTriggerAttributeBindingProvider(
     DurableTaskExtension config,
     ExtensionConfigContext extensionContext,
     string storageConnectionString,
     EndToEndTraceHelper traceHelper)
 {
     this.config                  = config;
     this.extensionContext        = extensionContext;
     this.storageConnectionString = storageConnectionString;
     this.traceHelper             = traceHelper;
 }
        public ActivityTriggerAttributeBindingProvider(
            DurableTaskConfiguration durableTaskConfig,
            ExtensionConfigContext extensionContext,
            EndToEndTraceHelper traceHelper)
        {
            this.durableTaskConfig = durableTaskConfig;
            this.extensionContext  = extensionContext;
            this.traceHelper       = traceHelper;

            ActivityTriggerBinding.RegisterBindingRules(extensionContext.Config);
        }
        public LifeCycleNotificationHelper(
            DurableTaskOptions config,
            INameResolver nameResolver,
            EndToEndTraceHelper traceHelper)
        {
            this.config      = config ?? throw new ArgumentNullException(nameof(config));
            this.traceHelper = traceHelper ?? throw new ArgumentNullException(nameof(traceHelper));

            if (nameResolver == null)
            {
                throw new ArgumentNullException(nameof(nameResolver));
            }

            this.eventGridKeyValue      = nameResolver.Resolve(config.EventGridKeySettingName);
            this.eventGridTopicEndpoint = config.EventGridTopicEndpoint;
            if (nameResolver.TryResolveWholeString(config.EventGridTopicEndpoint, out var endpoint))
            {
                this.eventGridTopicEndpoint = endpoint;
            }

            if (!string.IsNullOrEmpty(this.eventGridTopicEndpoint))
            {
                if (!string.IsNullOrEmpty(config.EventGridKeySettingName))
                {
                    this.useTrace = true;

                    var retryStatusCode = config.EventGridPublishRetryHttpStatus?
                                          .Where(x => Enum.IsDefined(typeof(HttpStatusCode), x))
                                          .Select(x => (HttpStatusCode)x)
                                          .ToArray()
                                          ?? Array.Empty <HttpStatusCode>();

                    // Currently, we support Event Grid Custom Topic for notify the lifecycle event of an orchestrator.
                    // For more detail about the Event Grid, please refer this document.
                    // Post to custom topic for Azure Event Grid
                    // https://docs.microsoft.com/en-us/azure/event-grid/post-to-custom-topic
                    this.HttpMessageHandler = config.NotificationHandler ?? new HttpRetryMessageHandler(
                        new HttpClientHandler(),
                        config.EventGridPublishRetryCount,
                        config.EventGridPublishRetryInterval,
                        retryStatusCode);

                    if (string.IsNullOrEmpty(this.eventGridKeyValue))
                    {
                        throw new ArgumentException($"Failed to start lifecycle notification feature. Please check the configuration values for {config.EventGridKeySettingName} on AppSettings.");
                    }
                }
                else
                {
                    throw new ArgumentException($"Failed to start lifecycle notification feature. Please check the configuration values for {config.EventGridTopicEndpoint} and {config.EventGridKeySettingName}.");
                }
            }
        }
        public LocalHttpListener(
            EndToEndTraceHelper traceHelper,
            DurableTaskOptions durableTaskOptions,
            Func <HttpRequestMessage, Task <HttpResponseMessage> > handler)
        {
            this.traceHelper        = traceHelper ?? throw new ArgumentNullException(nameof(traceHelper));
            this.handler            = handler ?? throw new ArgumentNullException(nameof(handler));
            this.durableTaskOptions = durableTaskOptions ?? throw new ArgumentNullException(nameof(durableTaskOptions));

            // Set to a non null value
            this.InternalRpcUri = new Uri($"http://uninitialized");
            this.localWebHost   = new NoOpWebHost();
        }
Example #11
0
        private void InitializeForFunctionsV1(ExtensionConfigContext context)
        {
#if !NETSTANDARD2_0
            context.ApplyConfig(this.Options, "DurableTask");

            ILogger logger = context.Config.LoggerFactory.CreateLogger(LoggerCategoryName);

            this.TraceHelper                 = new EndToEndTraceHelper(logger, this.Options.LogReplayEvents);
            this.HttpApiHandler              = new HttpApiHandler(this, logger);
            this.connectionStringResolver    = new WebJobsConnectionStringProvider();
            this.LifeCycleNotificationHelper = this.CreateLifeCycleNotificationHelper();
            this.nameResolver                = context.Config.NameResolver;
#endif
        }
Example #12
0
        private void InitializeForFunctionsV1(ExtensionConfigContext context)
        {
#if FUNCTIONS_V1
            context.ApplyConfig(this.Options, "DurableTask");
            ILogger logger = context.Config.LoggerFactory.CreateLogger(LoggerCategoryName);
            this.TraceHelper = new EndToEndTraceHelper(logger, this.Options.Tracing.TraceReplayEvents);
            this.connectionStringResolver  = new WebJobsConnectionStringProvider();
            this.durabilityProviderFactory = new AzureStorageDurabilityProviderFactory(new OptionsWrapper <DurableTaskOptions>(this.Options), this.connectionStringResolver);
            this.defaultDurabilityProvider = this.durabilityProviderFactory.GetDurabilityProvider();
            this.nameResolver = context.Config.NameResolver;
            this.LifeCycleNotificationHelper = this.CreateLifeCycleNotificationHelper();
            this.HttpApiHandler = new HttpApiHandler(this, logger);
#endif
        }
        private readonly DurableClientAttribute attribute; // for rehydrating a Client after a webhook

        internal DurableClient(
            DurabilityProvider serviceClient,
            DurableTaskExtension config,
            HttpApiHandler httpHandler,
            DurableClientAttribute attribute)
        {
            this.config = config ?? throw new ArgumentNullException(nameof(config));

            this.client             = new TaskHubClient(serviceClient);
            this.durabilityProvider = serviceClient;
            this.traceHelper        = config.TraceHelper;
            this.httpApiHandler     = httpHandler;
            this.hubName            = attribute.TaskHub ?? config.Options.HubName;
            this.attribute          = attribute;
        }
 public DurableTaskScaleMonitor(
     string functionId,
     FunctionName functionName,
     string hubName,
     string storageConnectionString,
     EndToEndTraceHelper traceHelper,
     DisconnectedPerformanceMonitor performanceMonitor = null)
 {
     this.functionId              = functionId;
     this.functionName            = functionName;
     this.hubName                 = hubName;
     this.storageConnectionString = storageConnectionString;
     this.performanceMonitor      = performanceMonitor;
     this.traceHelper             = traceHelper;
     this.scaleMonitorDescriptor  = new ScaleMonitorDescriptor($"{this.functionId}-DurableTaskTrigger-{this.hubName}".ToLower());
 }
        /// <summary>
        /// Internal initialization call from the WebJobs host.
        /// </summary>
        /// <param name="context">Extension context provided by WebJobs.</param>
        void IExtensionConfigProvider.Initialize(ExtensionConfigContext context)
        {
            ConfigureLoaderHooks();

            context.ApplyConfig(this, "DurableTask");

            // Register the trigger bindings
            JobHostConfiguration hostConfig = context.Config;
            ILogger logger = context.Config.LoggerFactory.CreateLogger(LoggerCategoryName);

            this.traceHelper    = new EndToEndTraceHelper(hostConfig, logger, this.LogReplayEvents);
            this.httpApiHandler = new HttpApiHandler(this, logger);

            this.lifeCycleNotificationHelper = new LifeCycleNotificationHelper(this, context);

            // Register the non-trigger bindings, which have a different model.
            var bindings = new BindingHelper(this, this.traceHelper);

            // For 202 support
            if (this.NotificationUrl == null)
            {
#pragma warning disable CS0618 // Type or member is obsolete
                this.NotificationUrl = context.GetWebhookHandler();
#pragma warning restore CS0618 // Type or member is obsolete
            }

            // Note that the order of the rules is important
            var rule = context.AddBindingRule <OrchestrationClientAttribute>()
                       .AddConverter <string, StartOrchestrationArgs>(bindings.StringToStartOrchestrationArgs)
                       .AddConverter <JObject, StartOrchestrationArgs>(bindings.JObjectToStartOrchestrationArgs);

            rule.BindToCollector <StartOrchestrationArgs>(bindings.CreateAsyncCollector);
            rule.BindToInput <DurableOrchestrationClient>(this.GetClient);

            context.AddBindingRule <OrchestrationTriggerAttribute>()
            .BindToTrigger(new OrchestrationTriggerAttributeBindingProvider(this, context, this.traceHelper));

            context.AddBindingRule <ActivityTriggerAttribute>()
            .BindToTrigger(new ActivityTriggerAttributeBindingProvider(this, context, this.traceHelper));

            AzureStorageOrchestrationServiceSettings settings = this.GetOrchestrationServiceSettings();
            this.orchestrationService = new AzureStorageOrchestrationService(settings);
            this.taskHubWorker        = new TaskHubWorker(this.orchestrationService, this, this);
            this.taskHubWorker.AddOrchestrationDispatcherMiddleware(this.OrchestrationMiddleware);

            context.Config.AddService <IOrchestrationService>(this.orchestrationService);
        }
Example #16
0
        /// <summary>
        /// Create an EventSourceListener to capture and log Durable EventSource
        /// data in Linux.
        /// </summary>
        /// <param name="logger">A LinuxAppService logger configured for the current linux host.</param>
        /// <param name="enableVerbose">If true, durableTask.Core verbose logs are enabled. The opposite if false.</param>
        /// <param name="traceHelper">A tracing client to log exceptions.</param>
        /// <param name="durabilityProviderEventSourceName">The durability provider's event source name.</param>
        public EventSourceListener(LinuxAppServiceLogger logger, bool enableVerbose, EndToEndTraceHelper traceHelper, string durabilityProviderEventSourceName)
        {
            this.logger         = logger;
            this.disableVerbose = !enableVerbose; // We track the opposite value ro simplify logic later
            this.traceHelper    = traceHelper;
            this.durabilityProviderEventSourceName = durabilityProviderEventSourceName;

            // Check to see if any event sources were created before we knew the event source
            // name for the durability provider and enable that provider.
            var eventSourcesToEnable = this.pendingEventSources.Where(eventSource => eventSource.Name == this.durabilityProviderEventSourceName);

            foreach (var eventSource in eventSourcesToEnable)
            {
                this.EnableEvents(eventSource, EventLevel.LogAlways, EventKeywords.All);
            }

            this.pendingEventSources.Clear();
        }
        internal DurableClient(
            DurabilityProvider serviceClient,
            HttpApiHandler httpHandler,
            DurableClientAttribute attribute,
            MessagePayloadDataConverter messageDataConverter,
            EndToEndTraceHelper traceHelper,
            DurableTaskOptions durableTaskOptions)
        {
            this.messageDataConverter = messageDataConverter;

            this.client             = new TaskHubClient(serviceClient, this.messageDataConverter);
            this.durabilityProvider = serviceClient;
            this.traceHelper        = traceHelper;
            this.httpApiHandler     = httpHandler;
            this.durableTaskOptions = durableTaskOptions;
            this.hubName            = attribute.TaskHub ?? this.durableTaskOptions.HubName;
            this.attribute          = attribute;
        }
        internal void Validate(INameResolver environmentVariableResolver, EndToEndTraceHelper traceHelper)
        {
            if (string.IsNullOrEmpty(this.HubName))
            {
                throw new InvalidOperationException($"A non-empty {nameof(this.HubName)} configuration is required.");
            }

            if (IsInNonProductionSlot() && this.IsDefaultHubName())
            {
                throw new InvalidOperationException($"Task Hub name must be specified in host.json when using slots. Specified name must not equal the default HubName ({this.defaultHubName})." +
                                                    "See documentation on Task Hubs for information on how to set this: https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-task-hubs");
            }

            string runtimeLanguage = environmentVariableResolver.Resolve("FUNCTIONS_WORKER_RUNTIME");

            if (this.ExtendedSessionsEnabled &&
                runtimeLanguage != null && // If we don't know from the environment variable, don't assume customer isn't .NET
                !string.Equals(runtimeLanguage, "dotnet", StringComparison.OrdinalIgnoreCase))
            {
                traceHelper.ExtensionWarningEvent(
                    hubName: this.HubName,
                    functionName: string.Empty,
                    instanceId: string.Empty,
                    message: "Durable Functions does not work with extendedSessions = true for non-.NET languages. This value is being set to false instead. See https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-perf-and-scale#extended-sessions for more details.");
                this.ExtendedSessionsEnabled = false;
            }

            this.Notifications.Validate();

            if (this.MaxConcurrentActivityFunctions <= 0)
            {
                throw new InvalidOperationException($"{nameof(this.MaxConcurrentActivityFunctions)} must be a non-negative integer value.");
            }

            if (this.MaxConcurrentOrchestratorFunctions <= 0)
            {
                throw new InvalidOperationException($"{nameof(this.MaxConcurrentOrchestratorFunctions)} must be a non-negative integer value.");
            }
        }
        public LocalHttpListener(
            EndToEndTraceHelper traceHelper,
            DurableTaskOptions durableTaskOptions,
            Func <HttpRequestMessage, Task <HttpResponseMessage> > handler)
        {
            this.traceHelper        = traceHelper ?? throw new ArgumentNullException(nameof(traceHelper));
            this.handler            = handler ?? throw new ArgumentNullException(nameof(handler));
            this.durableTaskOptions = durableTaskOptions ?? throw new ArgumentNullException(nameof(durableTaskOptions));

#if !FUNCTIONS_V1
            this.InternalRpcUri = new Uri($"http://127.0.0.1:{this.GetAvailablePort()}/durabletask/");
            var listenUri = new Uri(this.InternalRpcUri.GetLeftPart(UriPartial.Authority));
            this.localWebHost = new WebHostBuilder()
                                .UseKestrel()
                                .UseUrls(listenUri.OriginalString)
                                .Configure(a => a.Run(this.HandleRequestAsync))
                                .Build();
#else
            // Just use default port for internal Uri. No need to check for port availability since
            // we won't be listening to this endpoint.
            this.InternalRpcUri = new Uri($"http://127.0.0.1:{DefaultPort}/durabletask/");
            this.localWebHost   = new NoOpWebHost();
#endif
        }
 public BindingHelper(DurableTaskExtension config, EndToEndTraceHelper traceHelper)
 {
     this.config      = config;
     this.traceHelper = traceHelper;
 }
Example #21
0
 /// <summary>
 /// Create an EventSourceListener to capture and log Durable EventSource
 /// data in Linux.
 /// </summary>
 /// <param name="logger">A LinuxAppService logger configured for the current linux host.</param>
 /// <param name="enableVerbose">If true, durableTask.Core verbose logs are enabled. The opposite if false.</param>
 /// <param name="traceHelper">A tracing client to log exceptions.</param>
 public EventSourceListener(LinuxAppServiceLogger logger, bool enableVerbose, EndToEndTraceHelper traceHelper)
 {
     this.logger         = logger;
     this.disableVerbose = !enableVerbose; // We track the opposite value ro simplify logic later
     this.traceHelper    = traceHelper;
 }
        public EventGridLifeCycleNotificationHelper(
            DurableTaskOptions config,
            INameResolver nameResolver,
            EndToEndTraceHelper traceHelper)
        {
            this.config      = config ?? throw new ArgumentNullException(nameof(config));
            this.traceHelper = traceHelper ?? throw new ArgumentNullException(nameof(traceHelper));

            if (nameResolver == null)
            {
                throw new ArgumentNullException(nameof(nameResolver));
            }

            this.eventGridKeyValue      = nameResolver.Resolve(config.EventGridKeySettingName);
            this.eventGridTopicEndpoint = config.EventGridTopicEndpoint;

            if (nameResolver.TryResolveWholeString(config.EventGridTopicEndpoint, out var endpoint))
            {
                this.eventGridTopicEndpoint = endpoint;
            }

            if (!string.IsNullOrEmpty(this.eventGridTopicEndpoint))
            {
                if (!string.IsNullOrEmpty(config.EventGridKeySettingName))
                {
                    this.useTrace = true;

                    var retryStatusCode = config.EventGridPublishRetryHttpStatus?
                                          .Where(x => Enum.IsDefined(typeof(HttpStatusCode), x))
                                          .Select(x => (HttpStatusCode)x)
                                          .ToArray()
                                          ?? Array.Empty <HttpStatusCode>();

                    if (config.EventGridPublishEventTypes == null || config.EventGridPublishEventTypes.Length == 0)
                    {
                        this.eventGridPublishEventTypes = (OrchestrationRuntimeStatus[])Enum.GetValues(typeof(OrchestrationRuntimeStatus));
                    }
                    else
                    {
                        var startedIndex = Array.FindIndex(config.EventGridPublishEventTypes, x => x == "Started");
                        if (startedIndex > -1)
                        {
                            config.EventGridPublishEventTypes[startedIndex] = OrchestrationRuntimeStatus.Running.ToString();
                        }

                        OrchestrationRuntimeStatus ParseAndvalidateEvents(string @event)
                        {
                            var success = Enum.TryParse(@event, out OrchestrationRuntimeStatus @enum);

                            if (success)
                            {
                                switch (@enum)
                                {
                                case OrchestrationRuntimeStatus.Canceled:
                                case OrchestrationRuntimeStatus.ContinuedAsNew:
                                case OrchestrationRuntimeStatus.Pending:
                                    success = false;
                                    break;

                                default:
                                    break;
                                }
                            }

                            if (!success)
                            {
                                throw new ArgumentException("Failed to start lifecycle notification feature. Unsupported event types detected in 'EventGridPublishEventTypes'. You may only specify one or more of the following 'Started', 'Completed', 'Failed', 'Terminated'.");
                            }

                            return(@enum);
                        }

                        this.eventGridPublishEventTypes = config.EventGridPublishEventTypes.Select(
                            x => ParseAndvalidateEvents(x)).ToArray();
                    }

                    // Currently, we support Event Grid Custom Topic for notify the lifecycle event of an orchestrator.
                    // For more detail about the Event Grid, please refer this document.
                    // Post to custom topic for Azure Event Grid
                    // https://docs.microsoft.com/en-us/azure/event-grid/post-to-custom-topic
                    this.HttpMessageHandler = config.NotificationHandler ?? new HttpRetryMessageHandler(
                        new HttpClientHandler(),
                        config.EventGridPublishRetryCount,
                        config.EventGridPublishRetryInterval,
                        retryStatusCode);

                    if (string.IsNullOrEmpty(this.eventGridKeyValue))
                    {
                        throw new ArgumentException($"Failed to start lifecycle notification feature. Please check the configuration values for {config.EventGridKeySettingName} on AppSettings.");
                    }
                }
                else
                {
                    throw new ArgumentException($"Failed to start lifecycle notification feature. Please check the configuration values for {config.EventGridTopicEndpoint} and {config.EventGridKeySettingName}.");
                }
            }
        }