public WebScriptHostExceptionHandler(ScriptHostManager manager)
        {
            if (manager == null)
            {
                throw new ArgumentNullException(nameof(manager));
            }

            _manager = manager;
        }
        public async Task UpdateFileAndRestart()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            var fixture = new NodeEndToEndTests.TestFixture();
            var blob1 = UpdateOutputName("testblob", "first", fixture);

            await fixture.Host.StopAsync();
            var config = fixture.Host.ScriptConfig;

            ExceptionDispatchInfo exception = null;

            using (var manager = new ScriptHostManager(config))
            {
                // Background task to run while the main thread is pumping events at RunAndBlock(). 
                Thread t = new Thread(_ =>
                   {
                       // don't start until the manager is running
                       TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();

                       try
                       {
                           // Wait for initial execution.
                           TestHelpers.Await(() =>
                           {
                               bool exists = blob1.Exists();
                               return exists;
                           }, timeout: 10 * 1000).Wait();

                           // This changes the bindings so that we now write to blob2
                           var blob2 = UpdateOutputName("first", "second", fixture);

                           // wait for newly executed
                           TestHelpers.Await(() =>
                           {
                               bool exists = blob2.Exists();
                               return exists;
                           }, timeout: 30 * 1000).Wait();
                       }
                       catch (Exception ex)
                       {
                           exception = ExceptionDispatchInfo.Capture(ex);
                       }

                       cts.Cancel();
                   });
                t.Start();

                manager.RunAndBlock(cts.Token);

                t.Join();

                Assert.True(exception == null, exception?.SourceException?.ToString());
            }
        }
Esempio n. 3
0
        public static void Main(string[] args)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }

            string rootPath = Environment.CurrentDirectory;
            if (args.Length > 0)
            {
                rootPath = (string)args[0];
            }

            ScriptHostConfiguration config = new ScriptHostConfiguration()
            {
                RootScriptPath = rootPath
            };

            ScriptHostManager scriptHostManager = new ScriptHostManager(config);
            scriptHostManager.RunAndBlock();
        }    
Esempio n. 4
0
        public async Task HostHealthMonitor_TriggersShutdown_WhenHostUnhealthy()
        {
            string functionDir = Path.Combine(TestHelpers.FunctionsTestDirectory, "Functions", Guid.NewGuid().ToString());

            Directory.CreateDirectory(functionDir);
            string logDir = Path.Combine(TestHelpers.FunctionsTestDirectory, "Logs", Guid.NewGuid().ToString());

            JObject hostConfig = new JObject
            {
                { "id", "123456" }
            };

            File.WriteAllText(Path.Combine(functionDir, ScriptConstants.HostMetadataFileName), hostConfig.ToString());

            var config = new ScriptHostConfiguration
            {
                RootScriptPath  = functionDir,
                RootLogPath     = logDir,
                FileLoggingMode = FileLoggingMode.Always,
            };

            // configure the monitor so it will fail within a couple seconds
            config.HostHealthMonitor.HealthCheckInterval  = TimeSpan.FromMilliseconds(100);
            config.HostHealthMonitor.HealthCheckWindow    = TimeSpan.FromSeconds(1);
            config.HostHealthMonitor.HealthCheckThreshold = 5;

            var environmentMock = new Mock <IScriptHostEnvironment>(MockBehavior.Strict);

            environmentMock.Setup(p => p.Shutdown());

            var mockSettings = new Mock <ScriptSettingsManager>(null);

            mockSettings.Setup(p => p.IsAppServiceEnvironment).Returns(true);

            var eventManagerMock           = new Mock <IScriptEventManager>();
            var hostHealthConfig           = new HostHealthMonitorConfiguration();
            var mockHostPerformanceManager = new Mock <HostPerformanceManager>(mockSettings.Object, hostHealthConfig);

            bool underHighLoad = false;

            mockHostPerformanceManager.Setup(p => p.IsUnderHighLoad(It.IsAny <Collection <string> >(), It.IsAny <ILogger>()))
            .Callback <Collection <string>, ILogger>((c, l) =>
            {
                c.Add("Connections");
            })
            .Returns(() => underHighLoad);

            var loggerProvider        = new TestLoggerProvider();
            var loggerProviderFactory = new TestLoggerProviderFactory(loggerProvider);
            var hostManager           = new ScriptHostManager(config, mockSettings.Object, new ScriptHostFactory(), eventManagerMock.Object, environmentMock.Object, loggerProviderFactory, mockHostPerformanceManager.Object);

            Assert.True(hostManager.ShouldMonitorHostHealth);
            Task runTask = Task.Run(() => hostManager.RunAndBlock());
            await TestHelpers.Await(() => hostManager.State == ScriptHostState.Running);

            // now that host is running make host unhealthy and wait
            // for host shutdown
            underHighLoad = true;

            await TestHelpers.Await(() => hostManager.State == ScriptHostState.Error);

            Assert.Equal(ScriptHostState.Error, hostManager.State);
            environmentMock.Verify(p => p.Shutdown(), Times.Once);

            // we expect a few restart iterations
            var thresholdErrors = loggerProvider.GetAllLogMessages().Where(p => p.Exception is InvalidOperationException && p.Exception.Message == "Host thresholds exceeded: [Connections]. For more information, see https://aka.ms/functions-thresholds.");

            Assert.True(thresholdErrors.Count() > 1);

            var log = loggerProvider.GetAllLogMessages().Last();

            Assert.True(loggerProvider.GetAllLogMessages().Count(p => p.FormattedMessage == "Host is unhealthy. Initiating a restart." && p.Level == LogLevel.Error) > 0);
            Assert.Equal("Host unhealthy count exceeds the threshold of 5 for time window 00:00:01. Initiating shutdown.", log.FormattedMessage);
            Assert.Equal(LogLevel.Error, log.Level);
        }
Esempio n. 5
0
        public async Task UpdateFileAndRestart()
        {
            var fixture = new NodeScriptHostTests.TestFixture(false);
            var config  = fixture.Host.ScriptConfig;

            config.OnConfigurationApplied = c =>
            {
                c.Functions = new Collection <string> {
                    "TimerTrigger"
                };
            };

            var blob1 = await UpdateOutputName("testblob", "first", fixture);

            using (var eventManager = new ScriptEventManager())
                using (var manager = new ScriptHostManager(config, eventManager))
                {
                    string GetErrorTraces()
                    {
                        var messages = fixture.LoggerProvider.GetAllLogMessages()
                                       .Where(t => t.Level == LogLevel.Error)
                                       .Select(t => t.FormattedMessage);

                        return(string.Join(Environment.NewLine, messages));
                    }

                    List <Exception> exceptions = new List <Exception>();

                    // Background task to run while the main thread is pumping events at RunAndBlock().
                    Thread backgroundThread = new Thread(_ =>
                    {
                        try
                        {
                            // don't start until the manager is running
                            TestHelpers.Await(() => manager.State == ScriptHostState.Running,
                                              userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();

                            // Wait for initial execution.
                            TestHelpers.Await(async() =>
                            {
                                bool exists = await blob1.ExistsAsync();
                                return(exists);
                            }, timeout: 10 * 1000, userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();

                            // This changes the bindings so that we now write to blob2
                            var blob2 = UpdateOutputName("first", "testblob", fixture).Result;

                            // wait for newly executed
                            TestHelpers.Await(async() =>
                            {
                                bool exists = await blob2.ExistsAsync();
                                return(exists);
                            }, timeout: 30 * 1000, userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();

                            // The TimerTrigger can fire before the host is fully started. To be more
                            // reliably clean up the test, wait until it is running before calling Stop.
                            TestHelpers.Await(() => manager.State == ScriptHostState.Running,
                                              userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();
                        }
                        catch (Exception ex)
                        {
                            exceptions.Add(ex);
                        }
                        finally
                        {
                            try
                            {
                                // Calling Stop (rather than using a token) lets us wait until all listeners have stopped.
                                manager.Stop();
                            }
                            catch (Exception ex)
                            {
                                exceptions.Add(ex);
                            }
                        }
                    });

                    try
                    {
                        backgroundThread.Start();
                        manager.RunAndBlock();
                        Assert.True(backgroundThread.Join(60000), "The background task did not complete in 60 seconds.");

                        string exceptionString = string.Join(Environment.NewLine, exceptions.Select(p => p.ToString()));
                        Assert.True(exceptions.Count() == 0, exceptionString);
                    }
                    finally
                    {
                        // make sure to put the original names back
                        await UpdateOutputName("first", "testblob", fixture);
                    }
                }
        }
Esempio n. 6
0
        public async Task RenameFunctionAndRestart()
        {
            var oldDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestScripts/Node/TimerTrigger");
            var newDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestScripts/Node/MovedTrigger");

            var fixture = new NodeScriptHostTests.TestFixture(false);
            var config  = fixture.Host.ScriptConfig;

            config.OnConfigurationApplied = c =>
            {
                c.Functions = new Collection <string> {
                    "TimerTrigger", "MovedTrigger"
                };
            };

            var blob = fixture.TestOutputContainer.GetBlockBlobReference("testblob");
            await blob.DeleteIfExistsAsync();

            var mockEnvironment = new Mock <IScriptHostEnvironment>();

            using (var eventManager = new ScriptEventManager())
                using (var manager = new ScriptHostManager(config, eventManager, mockEnvironment.Object))
                    using (var resetEvent = new ManualResetEventSlim())
                    {
                        List <Exception> exceptions = new List <Exception>();

                        mockEnvironment.Setup(e => e.RestartHost())
                        .Callback(() =>
                        {
                            resetEvent.Set();
                            manager.RestartHost();
                        });

                        // Background task to run while the main thread is pumping events at RunAndBlock().
                        Thread backgroundThread = new Thread(_ =>
                        {
                            try
                            {
                                // don't start until the manager is running
                                TestHelpers.Await(() => manager.State == ScriptHostState.Running,
                                                  userMessageCallback: () => "Host did not start in time.").GetAwaiter().GetResult();

                                // Wait for initial execution.
                                TestHelpers.Await(async() =>
                                {
                                    bool exists = await blob.ExistsAsync();
                                    return(exists);
                                }, timeout: 10 * 1000,
                                                  userMessageCallback: () => $"Blob '{blob.Uri}' was not created by 'TimerTrigger' in time.").GetAwaiter().GetResult();

                                // find __dirname from blob
                                string text;
                                using (var stream = new MemoryStream())
                                {
                                    blob.DownloadToStreamAsync(stream).Wait();
                                    text = System.Text.Encoding.UTF8.GetString(stream.ToArray());
                                }

                                Assert.Contains("TimerTrigger", text);

                                // rename directory & delete old blob
                                Directory.Move(oldDirectory, newDirectory);

                                resetEvent.Wait(TimeSpan.FromSeconds(10));

                                blob.DeleteIfExistsAsync().GetAwaiter().GetResult();

                                // wait for newly executed
                                TestHelpers.Await(async() =>
                                {
                                    bool exists = await blob.ExistsAsync();
                                    return(exists);
                                }, timeout: 30 * 1000,
                                                  userMessageCallback: () => $"Blob '{blob.Uri}' was not created by 'MovedTrigger' in time.").GetAwaiter().GetResult();

                                using (var stream = new MemoryStream())
                                {
                                    blob.DownloadToStreamAsync(stream).Wait();
                                    text = System.Text.Encoding.UTF8.GetString(stream.ToArray());
                                }

                                Assert.Contains("MovedTrigger", text);

                                // The TimerTrigger can fire before the host is fully started. To be more
                                // reliably clean up the test, wait until it is running before calling Stop.
                                TestHelpers.Await(() => manager.State == ScriptHostState.Running).GetAwaiter().GetResult();
                            }
                            catch (Exception ex)
                            {
                                exceptions.Add(ex);
                            }
                            finally
                            {
                                try
                                {
                                    manager.Stop();
                                }
                                catch (Exception ex)
                                {
                                    exceptions.Add(ex);
                                }
                            }
                        });

                        try
                        {
                            backgroundThread.Start();
                            manager.RunAndBlock();
                            Assert.True(backgroundThread.Join(60000), "The background task did not complete in 60 seconds.");

                            string exceptionString = string.Join(Environment.NewLine, exceptions.Select(p => p.ToString()));
                            Assert.True(exceptions.Count() == 0, exceptionString);
                        }
                        finally
                        {
                            // Move the directory back after the host has stopped to prevent
                            // unnecessary host restarts
                            if (Directory.Exists(newDirectory))
                            {
                                Directory.Move(newDirectory, oldDirectory);
                            }
                        }
                    }
        }
        public async Task RunAndBlock_SetsLastError_WhenExceptionIsThrown()
        {
            ScriptHostConfiguration config = new ScriptHostConfiguration()
            {
                RootScriptPath = @"TestScripts\Empty"
            };

            var factoryMock = new Mock<IScriptHostFactory>();
            var scriptHostFactory = new TestScriptHostFactory()
            {
                Throw = true
            };
            var hostManager = new ScriptHostManager(config, _settingsManager, scriptHostFactory);
            Task taskIgnore = Task.Run(() => hostManager.RunAndBlock());

            // we expect a host exception immediately
            await Task.Delay(2000);

            Assert.Equal(ScriptHostState.Error, hostManager.State);
            Assert.False(hostManager.CanInvoke());
            Assert.NotNull(hostManager.LastError);
            Assert.Equal("Kaboom!", hostManager.LastError.Message);

            // now verify that if no error is thrown on the next iteration
            // the cached error is cleared
            scriptHostFactory.Throw = false;
            await TestHelpers.Await(() =>
            {
                return hostManager.State == ScriptHostState.Running;
            });

            Assert.Null(hostManager.LastError);
            Assert.True(hostManager.CanInvoke());
            Assert.Equal(ScriptHostState.Running, hostManager.State);
        }
        public async Task EmptyHost_StartsSuccessfully()
        {
            string functionDir = Path.Combine(TestHelpers.FunctionsTestDirectory, "Functions", Guid.NewGuid().ToString());
            Directory.CreateDirectory(functionDir);

            // important for the repro that this directory does not exist
            string logDir = Path.Combine(TestHelpers.FunctionsTestDirectory, "Logs", Guid.NewGuid().ToString());

            JObject hostConfig = new JObject
            {
                { "id", "123456" }
            };
            File.WriteAllText(Path.Combine(functionDir, ScriptConstants.HostMetadataFileName), hostConfig.ToString());

            ScriptHostConfiguration config = new ScriptHostConfiguration
            {
                RootScriptPath = functionDir,
                RootLogPath = logDir,
                FileLoggingMode = FileLoggingMode.Always
            };
            ScriptHostManager hostManager = new ScriptHostManager(config);

            Task runTask = Task.Run(() => hostManager.RunAndBlock());

            await TestHelpers.Await(() => hostManager.State == ScriptHostState.Running, timeout: 10000);

            hostManager.Stop();
            Assert.Equal(ScriptHostState.Default, hostManager.State);

            await Task.Delay(FileTraceWriter.LogFlushIntervalMs);

            string hostLogFilePath = Directory.EnumerateFiles(Path.Combine(logDir, "Host")).Single();
            string hostLogs = File.ReadAllText(hostLogFilePath);

            Assert.Contains("Generating 0 job function(s)", hostLogs);
            Assert.Contains("No job functions found.", hostLogs);
            Assert.Contains("Job host started", hostLogs);
            Assert.Contains("Job host stopped", hostLogs);
        }
 private MockExceptionHandler GetExceptionHandler(ScriptHostManager manager)
 {
     return(manager.Instance.ScriptConfig.HostConfig.GetService <IWebJobsExceptionHandler>() as MockExceptionHandler);
 }
Esempio n. 10
0
        public async Task UpdateFileAndRestart()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            var fixture = new NodeEndToEndTests.TestFixture();
            var blob1   = UpdateOutputName("testblob", "first", fixture);

            await fixture.Host.StopAsync();

            var config = fixture.Host.ScriptConfig;

            ExceptionDispatchInfo exception = null;

            using (var eventManager = new ScriptEventManager())
                using (var manager = new ScriptHostManager(config, eventManager))
                {
                    // Background task to run while the main thread is pumping events at RunAndBlock().
                    Thread t = new Thread(_ =>
                    {
                        // don't start until the manager is running
                        TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();

                        try
                        {
                            // Wait for initial execution.
                            TestHelpers.Await(() =>
                            {
                                bool exists = blob1.Exists();
                                return(exists);
                            }, timeout: 10 * 1000).Wait();

                            // This changes the bindings so that we now write to blob2
                            var blob2 = UpdateOutputName("first", "testblob", fixture);

                            // wait for newly executed
                            TestHelpers.Await(() =>
                            {
                                bool exists = blob2.Exists();
                                return(exists);
                            }, timeout: 30 * 1000).Wait();
                        }
                        catch (Exception ex)
                        {
                            exception = ExceptionDispatchInfo.Capture(ex);
                        }
                        finally
                        {
                            try
                            {
                                UpdateOutputName("first", "testblob", fixture);
                            }
                            catch
                            {
                            }
                        }

                        cts.Cancel();
                    });
                    t.Start();

                    manager.RunAndBlock(cts.Token);

                    t.Join();

                    Assert.True(exception == null, exception?.SourceException?.ToString());
                }
        }
Esempio n. 11
0
        public void Restart_CreatesNew_FunctionTraceWriter()
        {
            string functionDir             = @"TestScripts\CSharp";
            var    traceWriter             = new TestTraceWriter(System.Diagnostics.TraceLevel.Info);
            ScriptHostConfiguration config = new ScriptHostConfiguration
            {
                RootScriptPath  = functionDir,
                FileLoggingMode = FileLoggingMode.Always,
                TraceWriter     = traceWriter
            };

            string hostJsonPath     = Path.Combine(functionDir, ScriptConstants.HostMetadataFileName);
            string originalHostJson = File.ReadAllText(hostJsonPath);

            // Only load two functions to start:
            JObject hostConfig = new JObject
            {
                { "id", "123456" },
                { "functions", new JArray("ManualTrigger", "Scenarios") }
            };

            File.WriteAllText(hostJsonPath, hostConfig.ToString());

            CancellationTokenSource cts       = new CancellationTokenSource();
            ExceptionDispatchInfo   exception = null;

            try
            {
                using (var manager = new ScriptHostManager(config))
                {
                    // Background task to run while the main thread is pumping events at RunAndBlock().
                    Thread t = new Thread(_ =>
                    {
                        try
                        {
                            // don't start until the manager is running
                            TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();

                            var firstFileWriters = GetRemovableTraceWriters(manager.Instance);
                            Assert.Equal(2, firstFileWriters.Count());

                            // update the host.json to only have one function
                            hostConfig["functions"] = new JArray("ManualTrigger");
                            traceWriter.Traces.Clear();
                            File.WriteAllText(hostJsonPath, hostConfig.ToString());
                            TestHelpers.Await(() => traceWriter.Traces.Select(p => p.Message).Contains("Job host started")).Wait();
                            TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();

                            var secondFileWriters = GetRemovableTraceWriters(manager.Instance);
                            Assert.Equal(1, secondFileWriters.Count());

                            // make sure we have a new instance of the ManualTrigger writer and that it does
                            // not throw an ObjectDisposedException when we use it
                            Assert.DoesNotContain(secondFileWriters.Single(), firstFileWriters);
                            secondFileWriters.Single().Info("test");

                            // add back the other function -- make sure the writer is not disposed
                            hostConfig["functions"] = new JArray("ManualTrigger", "Scenarios");
                            traceWriter.Traces.Clear();
                            File.WriteAllText(hostJsonPath, hostConfig.ToString());
                            TestHelpers.Await(() => traceWriter.Traces.Select(p => p.Message).Contains("Job host started")).Wait();
                            TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();

                            var thirdFileWriters = GetRemovableTraceWriters(manager.Instance);
                            Assert.Equal(2, thirdFileWriters.Count());

                            // make sure these are all new and that they also do not throw
                            var previousWriters = firstFileWriters.Concat(secondFileWriters);
                            Assert.DoesNotContain(thirdFileWriters.First(), previousWriters);
                            Assert.DoesNotContain(thirdFileWriters.Last(), previousWriters);
                            thirdFileWriters.First().Info("test");
                            thirdFileWriters.Last().Info("test");
                        }
                        catch (Exception ex)
                        {
                            exception = ExceptionDispatchInfo.Capture(ex);
                        }
                        finally
                        {
                            cts.Cancel();
                        }
                    });
                    t.Start();
                    manager.RunAndBlock(cts.Token);
                    t.Join();
                }

                Assert.True(exception == null, exception?.SourceException?.ToString());
            }
            finally
            {
                File.WriteAllText(hostJsonPath, originalHostJson);
            }
        }
Esempio n. 12
0
        public async Task RenameFunctionAndRestart()
        {
            var oldDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestScripts/Node/TimerTrigger");
            var newDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestScripts/Node/MovedTrigger");

            CancellationTokenSource cts = new CancellationTokenSource();
            var fixture = new NodeEndToEndTests.TestFixture();
            await fixture.Host.StopAsync();

            var config = fixture.Host.ScriptConfig;

            var blob = fixture.TestOutputContainer.GetBlockBlobReference("testblob");

            ExceptionDispatchInfo exception = null;
            var mockEnvironment             = new Mock <IScriptHostEnvironment>();

            using (var eventManager = new ScriptEventManager())
                using (var manager = new ScriptHostManager(config, eventManager, mockEnvironment.Object))
                    using (var resetEvent = new ManualResetEventSlim())
                    {
                        mockEnvironment.Setup(e => e.RestartHost())
                        .Callback(() =>
                        {
                            resetEvent.Set();
                            manager.RestartHost();
                        });

                        // Background task to run while the main thread is pumping events at RunAndBlock().
                        Thread t = new Thread(_ =>
                        {
                            // don't start until the manager is running
                            TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();

                            try
                            {
                                // Wait for initial execution.
                                TestHelpers.Await(() =>
                                {
                                    bool exists = blob.Exists();
                                    return(exists);
                                }, timeout: 10 * 1000).Wait();

                                // find __dirname from blob
                                string text;
                                using (var stream = new MemoryStream())
                                {
                                    blob.DownloadToStream(stream);
                                    text = System.Text.Encoding.UTF8.GetString(stream.ToArray());
                                }

                                Assert.Contains("TimerTrigger", text);

                                // rename directory & delete old blob
                                Directory.Move(oldDirectory, newDirectory);

                                resetEvent.Wait(TimeSpan.FromSeconds(10));

                                blob.Delete();

                                // wait for newly executed
                                TestHelpers.Await(() =>
                                {
                                    bool exists = blob.Exists();
                                    return(exists);
                                }, timeout: 30 * 1000).Wait();

                                using (var stream = new MemoryStream())
                                {
                                    blob.DownloadToStream(stream);
                                    text = System.Text.Encoding.UTF8.GetString(stream.ToArray());
                                }

                                Assert.Contains("MovedTrigger", text);
                            }
                            catch (Exception ex)
                            {
                                exception = ExceptionDispatchInfo.Capture(ex);
                            }
                            finally
                            {
                                try
                                {
                                    Directory.Move(newDirectory, oldDirectory);
                                }
                                catch
                                {
                                }
                            }

                            cts.Cancel();
                        });
                        t.Start();

                        manager.RunAndBlock(cts.Token);

                        t.Join();

                        Assert.True(exception == null, exception?.SourceException?.ToString());
                    }
        }
 private MockExceptionHandler GetExceptionHandler(ScriptHostManager manager)
 {
     return manager.Instance.ScriptConfig.HostConfig.GetService<IWebJobsExceptionHandler>() as MockExceptionHandler;
 }