private AvailabilityResultAsyncCollector CreateAvailabilityResultAsyncCollector(AvailabilityTestResultAttribute attribute, ValueBindingContext valueBindingContext) { // A function is defined as an Availability Test iff is has a return value marked with [AvailabilityTestResult]. // If that is the case, this method will be invoked as some point to construct a collector for the return value. // Depending on the kind of the function, this will happen in different ways: // // - For .Net functions (in-proc), this method runs BEFORE function filters: // a) We will register this function as an Availability Test in the Functions registry (this is a NoOp for all, // except the very first invocation). // b) We will create new invocation state bag and register it with the Invocations registry. // c) We will instantiate a result collector and attach it to the state bag. // d) Later on, BEFORE the function body runs, the runtime execute the pre-function filter. At that point we will: // ~ Initialize an Availablity Test Scope and attach it to the invocation state bag; // ~ Link the results collector and the test scope. // e) Subsequently, AFTER the function body runs the result will be set in one of two ways: // ~ If no error: the runtime will add the return value to the result collector -> the collector will Complete the Test Scope; // ~ If error/exception: the runtime will invoke the post-function filter -> the filter will Complete the Test Scope. // // - For non-.Net functions (out-of-proc), this method runs AFTER function filters (and, potentially, even AFTER the function body has completed): // a) Registering this function as an Availability Test in the Functions registry will be a NoOp. // b) We will receive an existing invocation state bag; the Availablity Test Scope will be already set in the state bag. // c&d) We will instantiate a result collector and link it with the test scope right away; we will attach the collector to the state bag. // e) The results will be set in a simillar manner as for .Net described above. Validate.NotNull(attribute, nameof(attribute)); Validate.NotNull(valueBindingContext, nameof(valueBindingContext)); string functionName = valueBindingContext.FunctionContext.MethodName; using (_log.BeginScope(LogMonikers.Scopes.CreateForTestInvocation(functionName))) { // Register this Function as an Availability Test (NoOp for all invocations of this method, except the very first one): _availabilityTestRegistry.Functions.Register(functionName, attribute, _log); // Register this particular invocation of this function: Guid functionInstanceId = valueBindingContext.FunctionInstanceId; AvailabilityTestInvocationState invocationState = _availabilityTestRegistry.Invocations.GetOrRegister(functionInstanceId, _log); // Create the result collector: var resultCollector = new AvailabilityResultAsyncCollector(); // If the test scope is already set (out-of-proc function), then link it with the collector: bool isTestScopeInitialized = invocationState.TryGetTestScope(out AvailabilityTestScope testScope); if (isTestScopeInitialized) { resultCollector.Initialize(testScope); } // Attache the collector to the invocation state bag: invocationState.AttachResultCollector(resultCollector); // Done: return(resultCollector); } }
public bool TryDeregister(Guid functionInstanceId, ILogger log, out AvailabilityTestInvocationState invocationState) { bool wasRegistered = _registeredInvocations.TryRemove(functionInstanceId, out invocationState); if (wasRegistered) { log = AvailabilityTest.Log.CreateFallbackLogIfRequired(log); log?.LogInformation($"A Coded Availability Test invocation instance was deregistered (completed):" + " {{ FunctionInstanceId=\"{FunctionInstanceId}\" }}", functionInstanceId); } return(wasRegistered); }
private Task <AvailabilityTestInfo> CreateAvailabilityTestInfo(AvailabilityTestInfoAttribute attribute, ValueBindingContext valueBindingContext) { // A function is an Availability Test iff is has a return value marked with [AvailabilityTestResult]; // whereas a [AvailabilityTestInfo] is OPTIONAL to get test information at runtime. // User could have marked a parameter with [AvailabilityTestInfo] but no return value with [AvailabilityTestResult]: // That does not make sense, but we need to do something graceful. // There is no telling what will run first: this method, or CreateAvailabilityTelemetryAsyncCollector(..) above. // From here we cannot call _availabilityTestRegistry.Functions.Register(..), becasue the attribute type we get // here does not contain any configuration. // We will attach a raw test info object to this invocation. // If a test-RESULT-attribute is attached to this function later, it will supply configuration eventually. // If not, the test info will remain raw and we must remember to clear the invocation from the registry in the post-function filter. Validate.NotNull(attribute, nameof(attribute)); Validate.NotNull(valueBindingContext, nameof(valueBindingContext)); string functionName = valueBindingContext.FunctionContext.MethodName; using (_log.BeginScope(LogMonikers.Scopes.CreateForTestInvocation(functionName))) { // Register this particular invocation of this function: Guid functionInstanceId = valueBindingContext.FunctionInstanceId; AvailabilityTestInvocationState invocationState = _availabilityTestRegistry.Invocations.GetOrRegister(functionInstanceId, _log); // Create the test info: var testInfo = new AvailabilityTestInfo(); // If the test scope is already set (out-of-proc function), then use it to initialize the test info: bool isTestScopeInitialized = invocationState.TryGetTestScope(out AvailabilityTestScope testScope); if (isTestScopeInitialized) { testInfo.CopyFrom(testScope.CreateAvailabilityTestInfo()); } // Attach the test info to the invocation state bag: invocationState.AttachTestInfo(testInfo); // Done: return(Task.FromResult(testInfo)); } }
private AvailabilityTestInvocationState GetOrRegisterSlow(Guid functionInstanceId, ILogger log) { AvailabilityTestInvocationState newRegistration = null; AvailabilityTestInvocationState usedRegistration = _registeredInvocations.GetOrAdd( functionInstanceId, (id) => { newRegistration = new AvailabilityTestInvocationState(id); return(newRegistration); }); if (usedRegistration == newRegistration) { log = AvailabilityTest.Log.CreateFallbackLogIfRequired(log); log?.LogInformation($"A new Coded Availability Test invocation instance was registered:" + " {{ FunctionInstanceId=\"{FunctionInstanceId}\" }}", functionInstanceId); } return(usedRegistration); }
// Types 'FunctionExecutingContext' and 'IFunctionFilter' (and other Filter-related types) are marked as preview/obsolete, // but the guidance from the Azure Functions team is to use it, so we disable the warning. #pragma warning disable CS0618 public Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancelControl) #pragma warning restore CS0618 { Validate.NotNull(executingContext, nameof(executingContext)); // Grab the invocation id and the logger: Guid functionInstanceId = executingContext.FunctionInstanceId; ILogger log = executingContext.Logger; // Check if this is an Availability Test. // There are 3 cases: // 1) This IS an Availability Test and this is an in-proc/.Net functuion: // This filter runs AFTER the bindings. // The current function was already registered, becasue the attribute binding was already executed. // 2) This IS an Availability Test and this is an out-of-proc/non-.Net function: // This filter runs BEFORE the bindings. // a) If this is the first time the filter runs for the current function, TryGetTestConfig(..) will // read the metadata file, extract the config and return True. // b) If this is not the first time, the function is already registered as described in (a). // 3) This is NOT an Availability Test: // We will get False here and do nothing. bool isAvailabilityTest = _availabilityTestRegistry.Functions.IsAvailabilityTest(executingContext, out string functionName, out IAvailabilityTestConfiguration testConfig); if (!isAvailabilityTest) { if (log != null) { using (log.BeginScope(LogMonikers.Scopes.CreateForTestInvocation(functionName))) { log.LogDebug($"Availability Test Pre-Function routine was invoked and determned that this function is NOT an Availability Test:" + " {{FunctionName=\"{FunctionName}\", FunctionInstanceId=\"{FunctionInstanceId}\"}}", functionName, functionInstanceId); } } return(Task.CompletedTask); } // If configured, use a fall-back logger: log = AvailabilityTest.Log.CreateFallbackLogIfRequired(log); IReadOnlyDictionary <string, object> logScopeInfo = LogMonikers.Scopes.CreateForTestInvocation(functionName); using (log.BeginScopeSafe(logScopeInfo)) { log?.LogDebug($"Availability Test Pre-Function routine was invoked:" + " {{FunctionName=\"{FunctionName}\", FunctionInstanceId=\"{FunctionInstanceId}\"," + " TestConfiguration={{TestDisplayNameTemplate=\"{TestDisplayNameTemplate}\" }} }}", functionName, functionInstanceId, testConfig.TestDisplayName); // - In case (1) described above, we have already registered this invocation: // The function parameters have been instantiated, and attached to the invocationState. // However, the parameters are NOT yet initialized, as we did not have a AvailabilityTestScope instance yet. // We will set up an AvailabilityTestScope and attach it to the invocationState. // Then we will initialize the parameters using data from that scope. // - In case (2) described above, we have not yet registered the invocation: // A new invocationState will end up being created now. // We will set up an AvailabilityTestScope and attach it to the invocationState. // Subsequently, when the binings eventually get invoked by the Functions tuntime, // they will instantiate and initialize the parameters using data from that scope. // Get the invocation state bag: AvailabilityTestInvocationState invocationState = _availabilityTestRegistry.Invocations.GetOrRegister(functionInstanceId, log); // If test configuration makes reference to configuration, resolve the settings IAvailabilityTestInternalConfiguration resolvedTestConfig = _availabilityTestScopeSettingsResolver.Resolve(testConfig, functionName); // Start the availability test scope (this will start timers and set up the activity span): AvailabilityTestScope testScope = AvailabilityTest.StartNew(resolvedTestConfig, _telemetryConfiguration, flushOnDispose: true, log, logScopeInfo); invocationState.AttachTestScope(testScope); // If we have previously instantiated a result collector, initialize it now: if (invocationState.TryGetResultCollector(out AvailabilityResultAsyncCollector resultCollector)) { resultCollector.Initialize(testScope); } // If we have previously instantiated a test info, initialize it now: if (invocationState.TryGetTestInfos(out IEnumerable <AvailabilityTestInfo> testInfos)) { AvailabilityTestInfo model = testScope.CreateAvailabilityTestInfo(); foreach (AvailabilityTestInfo testInfo in testInfos) { testInfo.CopyFrom(model); } } } return(Task.CompletedTask); }