public async Task IndexTypeAsync(Type type, IFunctionIndexCollector index, CancellationToken cancellationToken)
        {
            foreach (MethodInfo method in type.GetMethods(PublicMethodFlags).Where(IsJobMethod))
            {
                try
                {
                    await IndexMethodAsync(method, index, cancellationToken);
                }
                catch (FunctionIndexingException fex)
                {
                    if (_allowPartialHostStartup)
                    {
                        fex.Handled = true;
                    }

                    fex.TryRecover(_logger);

                    // If recoverable, continue to the rest of the methods.
                    // The method in error simply won't be running in the JobHost.
                    string msg = $"Function '{method.GetShortName()}' failed indexing and will be disabled.";
                    _logger?.LogWarning(msg);
                    continue;
                }
            }
        }
Ejemplo n.º 2
0
        public async Task IndexTypeAsync(Type type, IFunctionIndexCollector index, CancellationToken cancellationToken)
        {
            foreach (MethodInfo method in type.GetMethods(PublicMethodFlags).Where(IsJobMethod))
            {
                try
                {
                    await IndexMethodAsync(method, index, cancellationToken);
                }
                catch (FunctionIndexingException fex)
                {
                    // Route the indexing exception through the TraceWriter pipeline
                    _trace.Error(fex.Message, fex);

                    // If the error has been marked as handled, ignore the indexing exception
                    // and continue on to the rest of the methods. In this case the method in
                    // error simply won't be running in the JobHost.
                    if (fex.Handled)
                    {
                        continue;
                    }
                    else
                    {
                        throw;
                    }
                }
            }
        }
 public async Task IndexTypeAsync(Type type, IFunctionIndexCollector index, CancellationToken cancellationToken)
 {
     foreach (MethodInfo method in type.GetMethods(_publicMethodFlags).Where(IsSdkMethod))
     {
         await IndexMethodAsync(method, index, cancellationToken);
     }
 }
Ejemplo n.º 4
0
        public async Task IndexMethod_IfMethodReturnsAsyncVoid_Throws()
        {
            var traceWriter    = new TestTraceWriter(TraceLevel.Verbose);
            var loggerFactory  = new LoggerFactory();
            var loggerProvider = new TestLoggerProvider();

            loggerFactory.AddProvider(loggerProvider);

            // Arrange
            IFunctionIndexCollector index   = CreateStubFunctionIndex();
            FunctionIndexer         product = CreateProductUnderTest(traceWriter: traceWriter, loggerFactory: loggerFactory);

            // Act & Assert
            await product.IndexMethodAsync(typeof(FunctionIndexerTests).GetMethod("ReturnAsyncVoid"), index, CancellationToken.None);

            string expectedMessage = "Function 'ReturnAsyncVoid' is async but does not return a Task. Your function may not run correctly.";

            // Validate TraceWriter
            var traceWarning = traceWriter.Traces.First(p => p.Level == TraceLevel.Warning);

            Assert.Equal(expectedMessage, traceWarning.Message);

            // Validate Logger
            var logger        = loggerProvider.CreatedLoggers.Single(l => l.Category == Logging.LogCategories.Startup);
            var loggerWarning = logger.LogMessages.Single();

            Assert.Equal(LogLevel.Warning, loggerWarning.Level);
            Assert.Equal(expectedMessage, loggerWarning.FormattedMessage);
        }
        // Helper to do the indexing.
        private static Tuple <FunctionDescriptor, IFunctionDefinition> IndexMethod(string methodName, INameResolver nameResolver = null)
        {
            MethodInfo method = typeof(FunctionIndexerIntegrationTests).GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

            Assert.NotNull(method);

            FunctionIndexer indexer = FunctionIndexerFactory.Create(CloudStorageAccount.DevelopmentStorageAccount,
                                                                    nameResolver);

            Tuple <FunctionDescriptor, IFunctionDefinition> indexEntry = null;
            Mock <IFunctionIndexCollector> indexMock = new Mock <IFunctionIndexCollector>(MockBehavior.Strict);

            indexMock
            .Setup((i) => i.Add(
                       It.IsAny <IFunctionDefinition>(),
                       It.IsAny <FunctionDescriptor>(),
                       It.IsAny <MethodInfo>()))
            .Callback <IFunctionDefinition, FunctionDescriptor, MethodInfo>(
                (ifd, fd, i) => indexEntry = Tuple.Create(fd, ifd));
            IFunctionIndexCollector index = indexMock.Object;

            indexer.IndexMethodAsync(method, index, CancellationToken.None).GetAwaiter().GetResult();

            return(indexEntry);
        }
Ejemplo n.º 6
0
        public void IndexMethod_IfMethodReturnsVoid_DoesNotThrow()
        {
            // Arrange
            IFunctionIndexCollector index   = CreateStubFunctionIndex();
            FunctionIndexer         product = CreateProductUnderTest();

            // Act & Assert
            product.IndexMethodAsync(typeof(FunctionIndexerTests).GetMethod("ReturnVoid"),
                                     index, CancellationToken.None).GetAwaiter().GetResult();
        }
Ejemplo n.º 7
0
        public void IndexMethod_IfMethodReturnsTaskOfTResult_Throws()
        {
            // Arrange
            IFunctionIndexCollector index   = CreateDummyFunctionIndex();
            FunctionIndexer         product = CreateProductUnderTest();

            // Act & Assert
            FunctionIndexingException exception = Assert.Throws <FunctionIndexingException>(
                () => product.IndexMethodAsync(typeof(FunctionIndexerTests).GetMethod("ReturnGenericTask"), index,
                                               CancellationToken.None).GetAwaiter().GetResult());
            InvalidOperationException innerException = exception.InnerException as InvalidOperationException;

            Assert.NotNull(innerException);
            Assert.Equal("Functions must return Task or void.", innerException.Message);
        }
Ejemplo n.º 8
0
 public async Task IndexMethodAsync(MethodInfo method, IFunctionIndexCollector index, CancellationToken cancellationToken)
 {
     try
     {
         await IndexMethodAsyncCore(method, index, cancellationToken);
     }
     catch (OperationCanceledException)
     {
         throw;
     }
     catch (Exception exception)
     {
         throw new FunctionIndexingException(method.GetShortName(), exception);
     }
 }
Ejemplo n.º 9
0
        public async Task IndexMethod_IfMethodReturnsAsyncVoid_Throws()
        {
            var traceWriter = new TestTraceWriter(TraceLevel.Verbose);

            // Arrange
            IFunctionIndexCollector index   = CreateStubFunctionIndex();
            FunctionIndexer         product = CreateProductUnderTest(traceWriter: traceWriter);

            // Act & Assert
            await product.IndexMethodAsync(typeof(FunctionIndexerTests).GetMethod("ReturnAsyncVoid"), index, CancellationToken.None);

            var warning = traceWriter.Traces.First(p => p.Level == TraceLevel.Warning);

            Assert.Equal("Function 'ReturnAsyncVoid' is async but does not return a Task. Your function may not run correctly.", warning.Message);
        }
Ejemplo n.º 10
0
 public async Task IndexTypeAsync(Type type, IFunctionIndexCollector index, CancellationToken cancellationToken)
 {
     foreach (MethodInfo method in type.GetMethods(PublicMethodFlags).Where(IsJobMethod))
     {
         try
         {
             await IndexMethodAsync(method, index, cancellationToken);
         }
         catch (FunctionIndexingException fex)
         {
             fex.TryRecover(_trace, _logger);
             // If recoverable, continue to the rest of the methods.
             // The method in error simply won't be running in the JobHost.
             continue;
         }
     }
 }
        public void IndexMethod_Throws_IfMethodHasUnboundOutParameterWithJobsAttribute()
        {
            // Arrange
            Mock <IFunctionIndexCollector> indexMock = new Mock <IFunctionIndexCollector>(MockBehavior.Strict);
            int calls = 0;

            indexMock
            .Setup(i => i.Add(It.IsAny <IFunctionDefinition>(), It.IsAny <FunctionDescriptor>(), It.IsAny <MethodInfo>()))
            .Callback(() => calls++);
            IFunctionIndexCollector index   = indexMock.Object;
            FunctionIndexer         product = CreateProductUnderTest();

            // Act & Assert
            FunctionIndexingException exception = Assert.Throws <FunctionIndexingException>(
                () => product.IndexMethodAsync(typeof(FunctionIndexerTests).GetMethod("FailIndexing"), index,
                                               CancellationToken.None).GetAwaiter().GetResult());
            InvalidOperationException innerException = exception.InnerException as InvalidOperationException;

            Assert.NotNull(innerException);
            Assert.Equal($"Cannot bind parameter 'parsed' to type Foo&. Make sure the parameter Type is supported by the binding. {Resource.ExtensionInitializationMessage}", innerException.Message);
        }
Ejemplo n.º 12
0
        internal async Task IndexMethodAsyncCore(MethodInfo method, IFunctionIndexCollector index, CancellationToken cancellationToken)
        {
            Debug.Assert(method != null);
            bool hasNoAutomaticTriggerAttribute = method.GetCustomAttribute <NoAutomaticTriggerAttribute>() != null;

            ITriggerBinding triggerBinding   = null;
            ParameterInfo   triggerParameter = null;

            ParameterInfo[] parameters = method.GetParameters();

            foreach (ParameterInfo parameter in parameters)
            {
                ITriggerBinding possibleTriggerBinding = await _triggerBindingProvider.TryCreateAsync(new TriggerBindingProviderContext(parameter, cancellationToken));

                if (possibleTriggerBinding != null)
                {
                    if (triggerBinding == null)
                    {
                        triggerBinding   = possibleTriggerBinding;
                        triggerParameter = parameter;
                    }
                    else
                    {
                        throw new InvalidOperationException("More than one trigger per function is not allowed.");
                    }
                }
            }

            Dictionary <string, IBinding>      nonTriggerBindings = new Dictionary <string, IBinding>();
            IReadOnlyDictionary <string, Type> bindingDataContract;

            if (triggerBinding != null)
            {
                bindingDataContract = triggerBinding.BindingDataContract;
            }
            else
            {
                bindingDataContract = null;
            }

            bool      hasParameterBindingAttribute  = false;
            Exception invalidInvokeBindingException = null;

            foreach (ParameterInfo parameter in parameters)
            {
                if (parameter == triggerParameter)
                {
                    continue;
                }

                IBinding binding = await _bindingProvider.TryCreateAsync(new BindingProviderContext(parameter, bindingDataContract, cancellationToken));

                if (binding == null)
                {
                    if (triggerBinding != null && !hasNoAutomaticTriggerAttribute)
                    {
                        throw new InvalidOperationException(
                                  string.Format(Constants.UnableToBindParameterFormat,
                                                parameter.Name, parameter.ParameterType.Name, Constants.ExtensionInitializationMessage));
                    }
                    else
                    {
                        // Host.Call-only parameter
                        binding = InvokeBinding.Create(parameter.Name, parameter.ParameterType);
                        if (binding == null && invalidInvokeBindingException == null)
                        {
                            // This function might not have any attribute, in which case we shouldn't throw an
                            // exception when we can't bind it. Instead, save this exception for later once we determine
                            // whether or not it is an SDK function.
                            invalidInvokeBindingException = new InvalidOperationException(
                                string.Format(Constants.UnableToBindParameterFormat,
                                              parameter.Name, parameter.ParameterType.Name, Constants.ExtensionInitializationMessage));
                        }
                    }
                }
                else if (!hasParameterBindingAttribute)
                {
                    hasParameterBindingAttribute = binding.FromAttribute;
                }

                nonTriggerBindings.Add(parameter.Name, binding);
            }

            // Only index functions with some kind of attribute on them. Three ways that could happen:
            // 1. There's an attribute on a trigger parameter (all triggers come from attributes).
            // 2. There's an attribute on a non-trigger parameter (some non-trigger bindings come from attributes).
            if (triggerBinding == null && !hasParameterBindingAttribute)
            {
                // 3. There's an attribute on the method itself (NoAutomaticTrigger).
                if (method.GetCustomAttribute <NoAutomaticTriggerAttribute>() == null)
                {
                    return;
                }
            }

            if (TypeUtility.IsAsyncVoid(method))
            {
                this._trace.Warning($"Function '{method.Name}' is async but does not return a Task. Your function may not run correctly.");
            }

            Type returnType = method.ReturnType;

            if (returnType != typeof(void) && returnType != typeof(Task))
            {
                throw new InvalidOperationException("Functions must return Task or void.");
            }

            if (invalidInvokeBindingException != null)
            {
                throw invalidInvokeBindingException;
            }

            // Validation: prevent multiple ConsoleOutputs
            if (nonTriggerBindings.OfType <TraceWriterBinding>().Count() > 1)
            {
                throw new InvalidOperationException("Can't have multiple TraceWriter/TextWriter parameters in a single function.");
            }

            string              triggerParameterName = triggerParameter != null ? triggerParameter.Name : null;
            FunctionDescriptor  functionDescriptor   = CreateFunctionDescriptor(method, triggerParameterName, triggerBinding, nonTriggerBindings);
            IFunctionInvoker    invoker = FunctionInvokerFactory.Create(method, _activator);
            IFunctionDefinition functionDefinition;

            if (triggerBinding != null)
            {
                functionDefinition = CreateTriggeredFunctionDefinition(triggerBinding, triggerParameterName, _executor, functionDescriptor, nonTriggerBindings, invoker, _singletonManager);

                if (hasNoAutomaticTriggerAttribute && functionDefinition != null)
                {
                    functionDefinition = new FunctionDefinition(functionDescriptor, functionDefinition.InstanceFactory, listenerFactory: null);
                }
            }
            else
            {
                IFunctionInstanceFactory instanceFactory = new FunctionInstanceFactory(new FunctionBinding(functionDescriptor, nonTriggerBindings, _singletonManager), invoker, functionDescriptor);
                functionDefinition = new FunctionDefinition(functionDescriptor, instanceFactory, listenerFactory: null);
            }

            index.Add(functionDefinition, functionDescriptor, method);
        }
        internal async Task IndexMethodAsyncCore(MethodInfo method, IFunctionIndexCollector index, CancellationToken cancellationToken)
        {
            Debug.Assert(method != null);
            bool hasNoAutomaticTriggerAttribute = method.GetCustomAttribute <NoAutomaticTriggerAttribute>() != null;

            ITriggerBinding             triggerBinding   = null;
            ParameterInfo               triggerParameter = null;
            IEnumerable <ParameterInfo> parameters       = method.GetParameters();

            foreach (ParameterInfo parameter in parameters)
            {
                ITriggerBinding possibleTriggerBinding = await _triggerBindingProvider.TryCreateAsync(new TriggerBindingProviderContext(parameter, cancellationToken));

                if (possibleTriggerBinding != null)
                {
                    if (triggerBinding == null)
                    {
                        triggerBinding   = possibleTriggerBinding;
                        triggerParameter = parameter;
                    }
                    else
                    {
                        throw new InvalidOperationException("More than one trigger per function is not allowed.");
                    }
                }
            }

            Dictionary <string, IBinding>      nonTriggerBindings = new Dictionary <string, IBinding>();
            IReadOnlyDictionary <string, Type> bindingDataContract;

            if (triggerBinding != null)
            {
                bindingDataContract = triggerBinding.BindingDataContract;

                // See if a regular binding can handle it.
                IBinding binding = await _bindingProvider.TryCreateAsync(new BindingProviderContext(triggerParameter, bindingDataContract, cancellationToken));

                if (binding != null)
                {
                    triggerBinding = new TriggerWrapper(triggerBinding, binding);
                }
            }
            else
            {
                bindingDataContract = null;
            }

            bool      hasParameterBindingAttribute  = false;
            Exception invalidInvokeBindingException = null;

            ReturnParameterInfo returnParameter = null;
            bool triggerHasReturnBinding        = false;

            if (TypeUtility.TryGetReturnType(method, out Type methodReturnType))
            {
                if (bindingDataContract != null && bindingDataContract.TryGetValue(ReturnParamName, out Type triggerReturnType))
                {
                    // The trigger will handle the return value.
                    triggerHasReturnBinding = true;
                }

                // We treat binding to the return type the same as binding to an 'out T' parameter.
                // An explicit return binding takes precedence over an implicit trigger binding.
                returnParameter = new ReturnParameterInfo(method, methodReturnType);
                parameters      = parameters.Concat(new ParameterInfo[] { returnParameter });
            }

            foreach (ParameterInfo parameter in parameters)
            {
                if (parameter == triggerParameter)
                {
                    continue;
                }

                IBinding binding = await _bindingProvider.TryCreateAsync(new BindingProviderContext(parameter, bindingDataContract, cancellationToken));

                if (binding == null)
                {
                    if (parameter == returnParameter)
                    {
                        if (triggerHasReturnBinding)
                        {
                            // Ok. Skip and let trigger own the return binding.
                            continue;
                        }
                        else
                        {
                            // If the method has a return value, then we must have a binding to it.
                            // This is similar to the error we used to throw.
                            invalidInvokeBindingException = new InvalidOperationException("Functions must return Task or void, have a binding attribute for the return value, or be triggered by a binding that natively supports return values.");
                        }
                    }

                    if (triggerBinding != null && !hasNoAutomaticTriggerAttribute)
                    {
                        throw new InvalidOperationException(
                                  string.Format(Constants.UnableToBindParameterFormat,
                                                parameter.Name, parameter.ParameterType.Name, Constants.ExtensionInitializationMessage));
                    }
                    else
                    {
                        // Host.Call-only parameter
                        binding = InvokeBinding.Create(parameter.Name, parameter.ParameterType);
                        if (binding == null && invalidInvokeBindingException == null)
                        {
                            // This function might not have any attribute, in which case we shouldn't throw an
                            // exception when we can't bind it. Instead, save this exception for later once we determine
                            // whether or not it is an SDK function.
                            invalidInvokeBindingException = new InvalidOperationException(
                                string.Format(Constants.UnableToBindParameterFormat,
                                              parameter.Name, parameter.ParameterType.Name, Constants.ExtensionInitializationMessage));
                        }
                    }
                }
                else if (!hasParameterBindingAttribute)
                {
                    hasParameterBindingAttribute = binding.FromAttribute;
                }

                nonTriggerBindings.Add(parameter.Name, binding);
            }

            // Only index functions with some kind of attribute on them. Three ways that could happen:
            // 1. There's an attribute on a trigger parameter (all triggers come from attributes).
            // 2. There's an attribute on a non-trigger parameter (some non-trigger bindings come from attributes).
            if (triggerBinding == null && !hasParameterBindingAttribute)
            {
                // 3. There's an attribute on the method itself (NoAutomaticTrigger).
                if (method.GetCustomAttribute <NoAutomaticTriggerAttribute>() == null)
                {
                    return;
                }
            }

            if (TypeUtility.IsAsyncVoid(method))
            {
                string msg = $"Function '{method.Name}' is async but does not return a Task. Your function may not run correctly.";
                _logger?.LogWarning(msg);
            }

            if (invalidInvokeBindingException != null)
            {
                throw invalidInvokeBindingException;
            }

            // Validation: prevent multiple ConsoleOutputs
            if (nonTriggerBindings.OfType <TraceWriterBinding>().Count() > 1)
            {
                throw new InvalidOperationException("Can't have multiple TraceWriter/TextWriter parameters in a single function.");
            }

            string              triggerParameterName = triggerParameter != null ? triggerParameter.Name : null;
            FunctionDescriptor  functionDescriptor   = CreateFunctionDescriptor(method, triggerParameterName, triggerBinding, nonTriggerBindings);
            IFunctionInvoker    invoker = FunctionInvokerFactory.Create(method, _activator);
            IFunctionDefinition functionDefinition;

            if (triggerBinding != null)
            {
                Type triggerValueType = triggerBinding.TriggerValueType;
                var  methodInfo       = typeof(FunctionIndexer).GetMethod("CreateTriggeredFunctionDefinition", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(triggerValueType);
                functionDefinition = (FunctionDefinition)methodInfo.Invoke(this, new object[] { triggerBinding, triggerParameterName, functionDescriptor, nonTriggerBindings, invoker });

                if (hasNoAutomaticTriggerAttribute && functionDefinition != null)
                {
                    functionDefinition = new FunctionDefinition(functionDescriptor, functionDefinition.InstanceFactory, listenerFactory: null);
                }
            }
            else
            {
                IFunctionInstanceFactory instanceFactory = new FunctionInstanceFactory(new FunctionBinding(functionDescriptor, nonTriggerBindings, _singletonManager), invoker, functionDescriptor);
                functionDefinition = new FunctionDefinition(functionDescriptor, instanceFactory, listenerFactory: null);
            }

            index.Add(functionDefinition, functionDescriptor, method);
        }