示例#1
0
        public async Task ReadyServiceShouldRestartWhenLivenessFailsTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-all.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            SetReplicasInitialState(host, true, true);

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-all" }, ReplicaState.Ready);

            var replicasToBecomeReady      = host.Application.Services["health-all"].Replicas.Select(r => r.Value).ToList();
            var replicasNamesToBecomeReady = replicasToBecomeReady.Select(r => r.Name).ToHashSet();

            var randomReplica = replicasToBecomeReady[new Random().Next(0, replicasToBecomeReady.Count)];

            Assert.True(await DoOperationAndWaitForReplicasToRestart(host, new[] { randomReplica.Name }.ToHashSet(), replicasNamesToBecomeReady.Where(r => r != randomReplica.Name).ToHashSet(), TimeSpan.FromSeconds(1), async _ =>
            {
                await SetHealthyReadyInReplica(randomReplica, healthy: false);
            }));
        }
示例#2
0
        public async Task HeadersTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-all.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            SetReplicasInitialState(host, true, true);

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-all" }, ReplicaState.Ready);

            var res = await _client.GetAsync($"http://localhost:{host.Application.Services["health-all"].Description.Bindings.First().Port}/livenessHeaders");

            Assert.True(res.IsSuccessStatusCode);

            var headers = JsonSerializer.Deserialize <Dictionary <string, string> >(await res.Content.ReadAsStringAsync());

            Assert.Equal("value1", headers["name1"]);
            Assert.Equal("value2", headers["name2"]);

            res = await _client.GetAsync($"http://localhost:{host.Application.Services["health-all"].Description.Bindings.First().Port}/readinessHeaders");

            Assert.True(res.IsSuccessStatusCode);

            headers = JsonSerializer.Deserialize <Dictionary <string, string> >(await res.Content.ReadAsStringAsync());
            Assert.Equal("value3", headers["name3"]);
            Assert.Equal("value4", headers["name4"]);
        }
示例#3
0
        public async Task ProbeShouldRespectTimeoutTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-all.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            SetReplicasInitialState(host, true, true);

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-all" }, ReplicaState.Ready);

            var replicasToBecomeReady      = host.Application.Services["health-all"].Replicas.Select(r => r.Value).ToList();
            var replicasNamesToBecomeReady = replicasToBecomeReady.Select(r => r.Name).ToHashSet();

            var randomNumber   = new Random().Next(0, replicasToBecomeReady.Count);
            var randomReplica1 = replicasToBecomeReady[randomNumber];
            var randomReplica2 = replicasToBecomeReady[(randomNumber + 1) % replicasToBecomeReady.Count];

            Assert.True(await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Healthy, 1, new[] { randomReplica1.Name }.ToHashSet(), replicasNamesToBecomeReady.Where(r => r != randomReplica1.Name).ToHashSet(), TimeSpan.FromSeconds(2), async _ =>
            {
                await Task.WhenAll(new[]
                {
                    SetHealthyReadyInReplica(randomReplica1, readyDelay: 2),
                    SetHealthyReadyInReplica(randomReplica2, readyDelay: 1)
                });
            }));
        }
示例#4
0
        private void SetReplicasInitialState(TyeHost host, bool?healthy, bool?ready, string[]?services = null)
        {
            if (services == null)
            {
                services = host.Application.Services.Select(s => s.Key).ToArray();
            }
            else
            {
                if (services.Any(s => !host.Application.Services.ContainsKey(s)))
                {
                    throw new ArgumentException($"not all services given in {nameof(services)} exist");
                }
            }

            foreach (var service in services)
            {
                if (healthy.HasValue)
                {
                    host.Application.Services[service].Description.Configuration.Add(new EnvironmentVariable("healthy")
                    {
                        Value = "true"
                    });
                }

                if (ready.HasValue)
                {
                    host.Application.Services[service].Description.Configuration.Add(new EnvironmentVariable("ready")
                    {
                        Value = "true"
                    });
                }
            }
        }
示例#5
0
        private static Command CreateRunCommand(string[] args)
        {
            var command = new Command("run", "run the application")
            {
                CommonArguments.Path_Required,
            };

            // TODO: We'll need to support a --build-args
            command.AddOption(new Option("--no-build")
            {
                Description = "Do not build project files before running.",
                Required    = false
            });

            command.AddOption(new Option("--port")
            {
                Description = "The port to run control plane on.",
                Argument    = new Argument <int>("port"),
                Required    = false
            });

            command.AddOption(new Option("--logs")
            {
                Description = "Write structured application logs to the specified log providers. Supported providers are console, elastic (Elasticsearch), ai (ApplicationInsights), seq.",
                Argument    = new Argument <string>("logs"),
                Required    = false
            });

            command.AddOption(new Option("--dtrace")
            {
                Description = "Write distributed traces to the specified providers. Supported providers are zipkin.",
                Argument    = new Argument <string>("logs"),
                Required    = false
            });

            command.AddOption(new Option("--debug")
            {
                Description = "Wait for debugger attach in all services.",
                Required    = false
            });

            command.Handler = CommandHandler.Create <IConsole, FileInfo>(async(console, path) =>
            {
                // Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
                if (path is null)
                {
                    throw new CommandException("No project or solution file was found.");
                }

                var application  = ConfigFactory.FromFile(path);
                var serviceCount = application.Services.Count;

                InitializeThreadPoolSettings(serviceCount);

                using var host = new TyeHost(application.ToHostingApplication(), args);
                await host.RunAsync();
            });

            return(command);
        }
示例#6
0
        public async Task IngressShouldNotProxyToNonReadyReplicasTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-ingress.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            SetReplicasInitialState(host, true, true);

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-ingress-svc" }, ReplicaState.Ready);

            var replicasToBecomeReady = host.Application.Services["health-ingress-svc"].Replicas.Select(r => r.Value).ToList();
            var ingressBinding        = host.Application.Services.First(s => s.Value.Description.RunInfo is IngressRunInfo).Value.Description.Bindings.First();
            var uniqueIdUrl           = $"{ingressBinding.Protocol}://localhost:{ingressBinding.Port}/api/id";

            var uniqueIds = await ProbeNumberOfUniqueReplicas(uniqueIdUrl);

            Assert.Equal(2, uniqueIds);

            var firstReplica  = replicasToBecomeReady.First();
            var secondReplica = replicasToBecomeReady.Skip(1).First();

            await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Healthy, 1, new[] { firstReplica.Name }.ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await SetHealthyReadyInReplica(firstReplica, ready: false);
            });

            uniqueIds = await ProbeNumberOfUniqueReplicas(uniqueIdUrl);

            Assert.Equal(1, uniqueIds);

            await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Healthy, 1, new[] { secondReplica.Name }.ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await SetHealthyReadyInReplica(secondReplica, ready: false);
            });

            var res = await _client.GetAsync(uniqueIdUrl);

            Assert.Equal(HttpStatusCode.BadGateway, res.StatusCode);

            await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Ready, 2, new[] { firstReplica.Name, secondReplica.Name }.ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await SetHealthyReadyInReplica(firstReplica, ready: true);
                await SetHealthyReadyInReplica(secondReplica, ready: true);
            });

            uniqueIds = await ProbeNumberOfUniqueReplicas(uniqueIdUrl);

            Assert.Equal(2, uniqueIds);
        }
示例#7
0
        public static async Task <bool> DoOperationAndWaitForReplicasToChangeState(TyeHost host, ReplicaState desiredState, int n, HashSet <string> toChange, HashSet <string> rest, Func <ReplicaEvent, string> entitySelector, TimeSpan waitUntilSuccess, Func <TyeHost, Task> operation)
        {
            if (toChange != null && rest != null && rest.Overlaps(toChange))
            {
                throw new ArgumentException($"{nameof(toChange)} and {nameof(rest)} can't overlap");
            }

            var changedTask = new TaskCompletionSource <bool>();
            var remaining   = n;

            void OnReplicaChange(ReplicaEvent ev)
            {
                if (rest != null && rest.Contains(entitySelector(ev)))
                {
                    changedTask !.TrySetResult(false);
                }
                else if ((toChange == null || toChange.Contains(entitySelector(ev))) && ev.State == desiredState)
                {
                    Interlocked.Decrement(ref remaining);
                }

                if (remaining == 0)
                {
                    Task.Delay(waitUntilSuccess)
                    .ContinueWith(_ =>
                    {
                        if (!changedTask !.Task.IsCompleted)
                        {
                            changedTask !.TrySetResult(remaining == 0);
                        }
                    });
                }
            }

            var servicesStateObserver = host.Application.Services.Select(srv => srv.Value.ReplicaEvents.Subscribe(OnReplicaChange)).ToList();

            await operation(host);

            using var cancellation = new CancellationTokenSource(WaitForServicesTimeout);
            try
            {
                await using (cancellation.Token.Register(() => changedTask.TrySetCanceled()))
                {
                    return(await changedTask.Task);
                }
            }
            finally
            {
                foreach (var observer in servicesStateObserver)
                {
                    observer.Dispose();
                }
            }
        }
示例#8
0
            static async Task Purge(TyeHost host)
            {
                var logger          = host.DashboardWebApplication !.Logger;
                var replicaRegistry = new ReplicaRegistry(host.Application.ContextDirectory, logger);
                var processRunner   = new ProcessRunner(logger, replicaRegistry, new ProcessRunnerOptions());
                var dockerRunner    = new DockerRunner(logger, replicaRegistry);

                await processRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary <string, Service>()));

                await dockerRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary <string, Service>()));
            }
示例#9
0
        public async Task MultiProjectPurgeTest()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "multi-project"));

            using var tempDirectory = TempDirectory.Create();
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile   = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
            var tyeDir        = new DirectoryInfo(Path.Combine(tempDirectory.DirectoryPath, ".tye"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
            {
                Sink = _sink,
            };

            try
            {
                await TestHelpers.StartHostAndWaitForReplicasToStart(host);

                try
                {
                    var pids       = GetAllAppPids(host.Application);
                    var containers = GetAllContainerIds(host.Application);

                    Assert.True(Directory.Exists(tyeDir.FullName));
                    Assert.Subset(new HashSet <int>(GetAllPids()), new HashSet <int>(pids));
                    Assert.Subset(new HashSet <string>(await DockerAssert.GetRunningContainersIdsAsync(_output)),
                                  new HashSet <string>(containers));

                    await TestHelpers.PurgeHostAndWaitForGivenReplicasToStop(host,
                                                                             GetAllReplicasNames(host.Application));

                    var runningPids = new HashSet <int>(GetAllPids());
                    Assert.True(pids.All(pid => !runningPids.Contains(pid)));
                    var runningContainers =
                        new HashSet <string>(await DockerAssert.GetRunningContainersIdsAsync(_output));
                    Assert.True(containers.All(c => !runningContainers.Contains(c)));
                }
                finally
                {
                    await host.StopAsync();
                }
            }
            finally
            {
                host.Dispose();
                Assert.False(Directory.Exists(tyeDir.FullName));
            }
        }
示例#10
0
        public async Task ServiceWithoutLivenessShouldDefaultToHealthyTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-readiness.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-readiness" }, ReplicaState.Healthy);
        }
示例#11
0
        public static async Task StartHostAndWaitForReplicasToStart(TyeHost host)
        {
            var startedTask    = new TaskCompletionSource <bool>();
            var alreadyStarted = 0;
            var totalReplicas  = host.Application.Services.Sum(s => s.Value.Description.Replicas);

            void OnReplicaChange(ReplicaEvent ev)
            {
                if (ev.State == ReplicaState.Started)
                {
                    Interlocked.Increment(ref alreadyStarted);
                }
                else if (ev.State == ReplicaState.Stopped)
                {
                    Interlocked.Decrement(ref alreadyStarted);
                }

                if (alreadyStarted == totalReplicas)
                {
                    startedTask.TrySetResult(true);
                }
            }

            var servicesStateObserver = host.Application.Services.Select(srv => srv.Value.ReplicaEvents.Subscribe(OnReplicaChange)).ToList();
            await host.StartAsync();

            using var cancellation = new CancellationTokenSource(WaitForServicesTimeout);
            try
            {
                await using (cancellation.Token.Register(() => startedTask.TrySetCanceled()))
                {
                    await startedTask.Task;
                }
            }
            catch (TaskCanceledException)
            {
                await host.StopAsync();

                throw;
            }
            finally
            {
                foreach (var observer in servicesStateObserver)
                {
                    observer.Dispose();
                }
            }
        }
示例#12
0
        public static async Task StartHostAndWaitForReplicasToStart(TyeHost host, string[] services = null, ReplicaState desiredState = ReplicaState.Started)
        {
            if (services == null)
            {
                await DoOperationAndWaitForReplicasToChangeState(host, desiredState, host.Application.Services.Sum(s => s.Value.Description.Replicas), null, null, TimeSpan.Zero, h => h.StartAsync());
            }
            else
            {
                if (services.Any(s => !host.Application.Services.ContainsKey(s)))
                {
                    throw new ArgumentException($"not all services given in {nameof(services)} exist");
                }

                await DoOperationAndWaitForReplicasToChangeState(host, desiredState, host.Application.Services.Where(s => services.Contains(s.Value.Description.Name)).Sum(s => s.Value.Description.Replicas), services.ToHashSet(), null, ev => ev.Replica.Service.Description.Name, TimeSpan.Zero, h => h.StartAsync());
            }
        }
示例#13
0
        public async Task NullDebugTargetsDoesNotThrow()
        {
            using var projectDirectory = CopyTestProjectDirectory(Path.Combine("single-project", "test-project"));

            var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "test-project.csproj"));

            // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
                        {
                            Sink = _sink,
                        };

            await host.StartAsync();
        }
示例#14
0
        public async Task FrontendBackendRunTestWithDocker()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "frontend-backend"));

            using var tempDirectory = TempDirectory.Create(preferUserDirectoryOnMacOS: true);
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile   = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
            var outputContext = new OutputContext(sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            using var host = new TyeHost(application.ToHostingApplication(), new[] { "--docker" })
                  {
                      Sink = sink,
                  };

            await host.StartAsync();

            try
            {
                // Make sure we're runningn containers
                Assert.True(host.Application.Services.All(s => s.Value.Description.RunInfo is DockerRunInfo));

                var handler = new HttpClientHandler
                {
                    ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
                    AllowAutoRedirect = false
                };

                var client = new HttpClient(new RetryHandler(handler));

                var dashboardUri = new Uri(host.DashboardWebApplication !.Addresses.First());

                await CheckServiceIsUp(host.Application, client, "backend", dashboardUri, timeout : TimeSpan.FromSeconds(60));
                await CheckServiceIsUp(host.Application, client, "frontend", dashboardUri, timeout : TimeSpan.FromSeconds(60));
            }
            finally
            {
                await host.StopAsync();
            }
        }
示例#15
0
        public async Task NullDebugTargetsDoesNotThrow()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "single-project", "test-project"));

            using var tempDirectory = TempDirectory.Create();
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "test-project.csproj"));

            // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
            var outputContext = new OutputContext(sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            using var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
                  {
                      Sink = sink,
                  };

            await host.StartAsync();

            await host.StopAsync();
        }
示例#16
0
        public async Task ServicWithoutLivenessShouldBecomeReadyWhenReadyTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-readiness.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-readiness" }, ReplicaState.Healthy);

            var replicas = host.Application.Services["health-readiness"].Replicas.Select(r => r.Value).ToList();

            Assert.True(await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Ready, replicas.Count, replicas.Select(r => r.Name).ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await Task.WhenAll(replicas.Select(r => SetHealthyReadyInReplica(r, ready: true)));
            }));
        }
示例#17
0
        public async Task FrontendBackendRunTest()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "frontend-backend"));

            using var tempDirectory = TempDirectory.Create();
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile   = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
            var outputContext = new OutputContext(sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            using var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
                  {
                      Sink = sink,
                  };

            await host.StartAsync();

            try
            {
                var handler = new HttpClientHandler
                {
                    ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
                    AllowAutoRedirect = false
                };

                var client = new HttpClient(new RetryHandler(handler));

                var dashboardUri = new Uri(host.DashboardWebApplication !.Addresses.First());

                await CheckServiceIsUp(host.Application, client, "backend", dashboardUri);
                await CheckServiceIsUp(host.Application, client, "frontend", dashboardUri);
            }
            finally
            {
                await host.StopAsync();
            }
        }
示例#18
0
        private async Task RunHostingApplication(ApplicationBuilder application, string[] args, Func <Application, Uri, Task> execute)
        {
            await using var host = new TyeHost(application.ToHostingApplication(), args)
                        {
                            Sink = _sink,
                        };

            try
            {
                await StartHostAndWaitForReplicasToStart(host);

                var uri = new Uri(host.DashboardWebApplication !.Addresses.First());

                await execute(host.Application, uri !);
            }
            finally
            {
                if (host.DashboardWebApplication != null)
                {
                    var uri = new Uri(host.DashboardWebApplication !.Addresses.First());

                    using var client = new HttpClient();

                    foreach (var s in host.Application.Services.Values)
                    {
                        var logs = await client.GetStringAsync(new Uri(uri, $"/api/v1/logs/{s.Description.Name}"));

                        _output.WriteLine($"Logs for service: {s.Description.Name}");
                        _output.WriteLine(logs);

                        var description = await client.GetStringAsync(new Uri(uri, $"/api/v1/services/{s.Description.Name}"));

                        _output.WriteLine($"Service defintion: {s.Description.Name}");
                        _output.WriteLine(description);
                    }
                }
            }
        }
示例#19
0
        public async Task ProxyShouldNotProxyToNonReadyReplicasTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-proxy.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            SetReplicasInitialState(host, true, true);

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-proxy" }, ReplicaState.Ready);

            var replicasToBecomeReady = host.Application.Services["health-proxy"].Replicas.Select(r => r.Value).ToList();

            // we assume that proxy will continue sending http request to the same replica
            var randomReplicaPortRes1 = await _client.GetAsync($"http://localhost:{host.Application.Services["health-proxy"].Description.Bindings.First().Port}/ports");

            var randomReplicaPort1 = JsonSerializer.Deserialize <int[]>(await randomReplicaPortRes1.Content.ReadAsStringAsync()) ![0];
示例#20
0
        private async Task RunHostingApplication(ApplicationBuilder application, string[] args, TestOutputLogEventSink sink, Func <Uri, Task> execute)
        {
            using var host = new TyeHost(application.ToHostingApplication(), args)
                  {
                      Sink = sink,
                  };

            await StartHostAndWaitForReplicasToStart(host);

            var serviceApi = new Uri(host.DashboardWebApplication !.Addresses.First());

            try
            {
                await execute(serviceApi !);
            }
            finally
            {
                using (var client = new HttpClient())
                {
                    // If we failed, there's a good chance the service isn't running. Let's get the logs either way and put
                    // them in the output.
                    foreach (var s in host.Application.Services.Values)
                    {
                        var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(serviceApi, $"/api/v1/logs/{s.Description.Name}"));
                        var response = await client.SendAsync(request);

                        var text = await response.Content.ReadAsStringAsync();

                        _output.WriteLine($"Logs for service: {s.Description.Name}");
                        _output.WriteLine(text);
                    }
                }

                await host.StopAsync();
            }
        }
示例#21
0
        public async Task FrontendBackendPurgeTest()
        {
            using var projectDirectory = CopySampleProjectDirectory("frontend-backend");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
            var tyeDir        = new DirectoryInfo(Path.Combine(projectDirectory.DirectoryPath, ".tye"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
            {
                Sink = _sink,
            };

            try
            {
                await TestHelpers.StartHostAndWaitForReplicasToStart(host);

                var pids = GetAllAppPids(host.Application);

                Assert.True(Directory.Exists(tyeDir.FullName));
                Assert.Subset(new HashSet <int>(GetAllPids()), new HashSet <int>(pids));

                await TestHelpers.PurgeHostAndWaitForGivenReplicasToStop(host,
                                                                         GetAllReplicasNames(host.Application));

                var runningPids = new HashSet <int>(GetAllPids());
                Assert.True(pids.All(pid => !runningPids.Contains(pid)));
            }
            finally
            {
                await host.DisposeAsync();

                Assert.False(Directory.Exists(tyeDir.FullName));
            }
        }
示例#22
0
 public static async Task PurgeHostAndWaitForGivenReplicasToStop(TyeHost host, string[] replicas)
 {
示例#23
0
        private static Command CreateRunCommand(string[] args)
        {
            var command = new Command("run", "run the application")
            {
                CommonArguments.Path_Required,
            };

            // TODO: We'll need to support a --build-args
            command.AddOption(new Option("--no-build")
            {
                Description = "Do not build project files before running.",
                Required    = false
            });

            command.AddOption(new Option("--port")
            {
                Description = "The port to run control plane on.",
                Argument    = new Argument <int>("port"),
                Required    = false
            });

            command.AddOption(new Option("--logs")
            {
                Description = "Write structured application logs to the specified log providers. Supported providers are console, elastic (Elasticsearch), ai (ApplicationInsights), seq.",
                Argument    = new Argument <string>("logs"),
                Required    = false
            });

            command.AddOption(new Option("--dtrace")
            {
                Description = "Write distributed traces to the specified providers. Supported providers are zipkin.",
                Argument    = new Argument <string>("logs"),
                Required    = false
            });

            command.AddOption(new Option("--debug")
            {
                Argument    = new Argument <string[]>("service"),
                Description = "Wait for debugger attach to specific service. Specify \"*\" to wait for all services.",
                Required    = false
            });

            command.AddOption(new Option("--docker")
            {
                Description = "Run projects as docker containers.",
                Required    = false
            });

            command.Handler = CommandHandler.Create <IConsole, FileInfo, string[]>(async(console, path, debug) =>
            {
                // Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
                if (path is null)
                {
                    throw new CommandException("No project or solution file was found.");
                }

                var output      = new OutputContext(console, Verbosity.Quiet);
                var application = await ApplicationFactory.CreateAsync(output, path);

                await application.ProcessExtensionsAsync(ExtensionContext.OperationKind.LocalRun);

                InitializeThreadPoolSettings(application.Services.Count);

                if (application.Services.Count == 0)
                {
                    throw new CommandException($"No services found in \"{application.Source.Name}\"");
                }

                await using var host = new TyeHost(application.ToHostingApplication(), args, debug);
                await host.RunAsync();
            });

            return(command);
        }
示例#24
0
        public async Task SingleProjectRunTest()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "single-project", "test-project"));

            using var tempDirectory = TempDirectory.Create();
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "test-project.csproj"));

            using var host = new TyeHost(ConfigFactory.FromFile(projectFile).ToHostingApplication(), Array.Empty <string>())
                  {
                      Sink = sink,
                  };

            await host.StartAsync();

            try
            {
                var handler = new HttpClientHandler
                {
                    ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
                    AllowAutoRedirect = false
                };

                var client = new HttpClient(new RetryHandler(handler));

                // Make sure dashboard and applications are up.
                // Dashboard should be hosted in same process.
                var dashboardUri      = new Uri(host.DashboardWebApplication !.Addresses.First());
                var dashboardResponse = await client.GetStringAsync(dashboardUri);

                // Only one service for single application.
                var service = host.Application.Services.First().Value;
                var binding = service.Description.Bindings.First();

                var protocol = binding.Protocol?.Length != 0 ? binding.Protocol : "http";
                var hostName = binding.Host != null && binding.Host.Length != 0 ? binding.Host : "localhost";

                var uriString = $"{protocol}://{hostName}:{binding.Port}";

                // Confirm that the uri is in the dashboard response.
                Assert.Contains(uriString, dashboardResponse);

                var uriBackendProcess = new Uri(uriString);

                // This isn't reliable right now because micronetes only guarantees the process starts, not that
                // that kestrel started.
                try
                {
                    var appResponse = await client.GetAsync(uriBackendProcess);

                    Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
                }
                finally
                {
                    // If we failed, there's a good chance the service isn't running. Let's get the logs either way and put
                    // them in the output.
                    var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/api/v1/logs/{service.Description.Name}"));
                    var response = await client.SendAsync(request);

                    var text = await response.Content.ReadAsStringAsync();

                    output.WriteLine($"Logs for service: {service.Description.Name}");
                    output.WriteLine(text);
                }
            }
            finally
            {
                await host.StopAsync();
            }
        }
示例#25
0
 public static Task <bool> DoOperationAndWaitForReplicasToChangeState(TyeHost host, ReplicaState desiredState, int n, HashSet <string> toChange, HashSet <string> rest, TimeSpan waitUntilSuccess, Func <TyeHost, Task> operation)
 => DoOperationAndWaitForReplicasToChangeState(host, desiredState, n, toChange, rest, ev => ev.Replica.Name, waitUntilSuccess, operation);
示例#26
0
        public async Task ProxyShouldNotProxyToNonReadyReplicasTests()
        {
            using var projectDirectory = CopyTestProjectDirectory("health-checks");

            var projectFile   = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-proxy.yaml"));
            var outputContext = new OutputContext(_sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            await using var host = new TyeHost(application.ToHostingApplication(), new HostOptions())
                        {
                            Sink = _sink,
                        };

            SetReplicasInitialState(host, true, true);

            await StartHostAndWaitForReplicasToStart(host, new[] { "health-proxy" }, ReplicaState.Ready);

            var replicasToBecomeReady = host.Application.Services["health-proxy"].Replicas.Select(r => r.Value).ToList();

            // we assume that proxy will continue sending http request to the same replica
            var randomReplicaPortRes1 = await _client.GetAsync($"http://localhost:{host.Application.Services["health-proxy"].Description.Bindings.First().Port}/ports");

            var randomReplicaPort1 = JsonSerializer.Deserialize <int[]>(await randomReplicaPortRes1.Content.ReadAsStringAsync())[0];
            var randomReplica1     = replicasToBecomeReady.First(r => r.Bindings.Any(b => b.Port == randomReplicaPort1));

            await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Healthy, 1, new[] { randomReplica1.Name }.ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await SetHealthyReadyInReplica(randomReplica1, ready: false);
            });

            var randomReplicaPortRes2 = await _client.GetAsync($"http://localhost:{host.Application.Services["health-proxy"].Description.Bindings.First().Port}/ports");

            var randomReplicaPort2 = JsonSerializer.Deserialize <int[]>(await randomReplicaPortRes2.Content.ReadAsStringAsync())[0];
            var randomReplica2     = replicasToBecomeReady.First(r => r.Bindings.Any(b => b.Port == randomReplicaPort2));

            Assert.NotEqual(randomReplicaPort1, randomReplicaPort2);

            await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Healthy, 1, new[] { randomReplica2.Name }.ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await SetHealthyReadyInReplica(randomReplica2, ready: false);
            });

            try
            {
                var resShouldFail = await _client.GetAsync($"http://localhost:{host.Application.Services["health-proxy"].Description.Bindings.First().Port}/ports");

                Assert.False(resShouldFail.IsSuccessStatusCode);
            }
            catch (HttpRequestException)
            {
            }

            await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Ready, 1, new[] { randomReplica2.Name }.ToHashSet(), null, TimeSpan.Zero, async _ =>
            {
                await SetHealthyReadyInReplica(randomReplica2, ready: true);
            });

            var randomReplicaPortRes3 = await _client.GetAsync($"http://localhost:{host.Application.Services["health-proxy"].Description.Bindings.First().Port}/ports");

            var randomReplicaPort3 = JsonSerializer.Deserialize <int[]>(await randomReplicaPortRes3.Content.ReadAsStringAsync())[0];

            Assert.Equal(randomReplicaPort3, randomReplicaPort2);
        }
示例#27
0
        public async Task IngressRunTest()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "apps-with-ingress"));

            using var tempDirectory = TempDirectory.Create();
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile   = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
            var outputContext = new OutputContext(sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            using var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
                  {
                      Sink = sink,
                  };

            var handler = new HttpClientHandler
            {
                ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
                AllowAutoRedirect = false
            };

            using var client = new HttpClient(new RetryHandler(handler));
            await host.StartAsync();

            var serviceApi = new Uri(host.DashboardWebApplication !.Addresses.First());

            try
            {
                var ingressService = await client.GetStringAsync($"{serviceApi}api/v1/services/ingress");

                var service    = JsonSerializer.Deserialize <V1Service>(ingressService, _options);
                var binding    = service.Description !.Bindings.Single();
                var ingressUri = $"http://localhost:{binding.Port}";

                var responseA = await client.GetAsync(ingressUri + "/A");

                var responseB = await client.GetAsync(ingressUri + "/B");

                Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync());
                Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync());

                var requestA = new HttpRequestMessage(HttpMethod.Get, ingressUri);
                requestA.Headers.Host = "a.example.com";
                var requestB = new HttpRequestMessage(HttpMethod.Get, ingressUri);
                requestB.Headers.Host = "b.example.com";

                responseA = await client.SendAsync(requestA);

                responseB = await client.SendAsync(requestB);

                Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync());
                Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync());
            }
            finally
            {
                // If we failed, there's a good chance the service isn't running. Let's get the logs either way and put
                // them in the output.
                foreach (var s in host.Application.Services.Values)
                {
                    var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(serviceApi, $"/api/v1/logs/{s.Description.Name}"));
                    var response = await client.SendAsync(request);

                    var text = await response.Content.ReadAsStringAsync();

                    output.WriteLine($"Logs for service: {s.Description.Name}");
                    output.WriteLine(text);
                }

                await host.StopAsync();
            }
        }
示例#28
0
        public async Task SingleProjectRunTest()
        {
            var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "single-project", "test-project"));

            using var tempDirectory = TempDirectory.Create();
            DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);

            var projectFile   = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "test-project.csproj"));
            var outputContext = new OutputContext(sink, Verbosity.Debug);
            var application   = await ApplicationFactory.CreateAsync(outputContext, projectFile);

            using var host = new TyeHost(application.ToHostingApplication(), Array.Empty <string>())
                  {
                      Sink = sink,
                  };

            await host.StartAsync();

            try
            {
                var handler = new HttpClientHandler
                {
                    ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
                    AllowAutoRedirect = false
                };

                var client = new HttpClient(new RetryHandler(handler));

                // Make sure dashboard and applications are up.
                // Dashboard should be hosted in same process.
                var dashboardUri    = new Uri(host.DashboardWebApplication !.Addresses.First());
                var dashboardString = await client.GetStringAsync($"{dashboardUri}api/v1/services/test-project");

                var service           = JsonSerializer.Deserialize <V1Service>(dashboardString, _options);
                var binding           = service.Description !.Bindings.Where(b => b.Protocol == "http").Single();
                var uriBackendProcess = new Uri($"{binding.Protocol}://localhost:{binding.Port}");

                // This isn't reliable right now because micronetes only guarantees the process starts, not that
                // that kestrel started.
                try
                {
                    var appResponse = await client.GetAsync(uriBackendProcess);

                    Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
                }
                finally
                {
                    // If we failed, there's a good chance the service isn't running. Let's get the logs either way and put
                    // them in the output.
                    var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/api/v1/logs/{service.Description.Name}"));
                    var response = await client.SendAsync(request);

                    var text = await response.Content.ReadAsStringAsync();

                    output.WriteLine($"Logs for service: {service.Description.Name}");
                    output.WriteLine(text);
                }
            }
            finally
            {
                await host.StopAsync();
            }
        }
示例#29
0
 public static Task <bool> DoOperationAndWaitForReplicasToRestart(TyeHost host, HashSet <string> toRestart, HashSet <string> rest, TimeSpan waitUntilSuccess, Func <TyeHost, Task> operation)
 => DoOperationAndWaitForReplicasToRestart(host, toRestart, rest, ev => ev.Replica.Name, waitUntilSuccess, operation);
示例#30
0
        private static Command CreateRunCommand()
        {
            var command = new Command("run", "run the application")
            {
                CommonArguments.Path_Required,

                new Option("--no-build")
                {
                    Description = "Do not build project files before running.",
                    Required    = false
                },
                new Option("--port")
                {
                    Description = "The port to run control plane on.",
                    Argument    = new Argument <int?>("port"),
                    Required    = false
                },
                new Option("--logs")
                {
                    Description = "Write structured application logs to the specified log provider. Supported providers are 'console', 'elastic' (Elasticsearch), 'ai' (ApplicationInsights), 'seq'.",
                    Argument    = new Argument <string>("logs"),
                    Required    = false
                },
                new Option("--dtrace")
                {
                    Description = "Write distributed traces to the specified tracing provider. Supported providers are 'zipkin'.",
                    Argument    = new Argument <string>("trace"),
                    Required    = false,
                },
                new Option("--metrics")
                {
                    Description = "Write metrics to the specified metrics provider.",
                    Argument    = new Argument <string>("metrics"),
                    Required    = false
                },
                new Option("--debug")
                {
                    Argument = new Argument <string[]>("service")
                    {
                        Arity = ArgumentArity.ZeroOrMore,
                    },
                    Description = "Wait for debugger attach to specific service. Specify \"*\" to wait for all services.",
                    Required    = false
                },
                new Option("--docker")
                {
                    Description = "Run projects as docker containers.",
                    Required    = false
                },
                new Option("--dashboard")
                {
                    Description = "Launch dashboard on run.",
                    Required    = false
                },
                new Option("--watch")
                {
                    Description = "Watches for code changes for all dotnet projects.",
                    Required    = false
                },
                StandardOptions.Framework,
                StandardOptions.Tags,
                StandardOptions.Verbosity,
            };

            command.Handler = CommandHandler.Create <RunCommandArguments>(async args =>
            {
                // Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
                if (args.Path is null)
                {
                    throw new CommandException("No project or solution file was found.");
                }

                var output = new OutputContext(args.Console, args.Verbosity);

                output.WriteInfoLine("Loading Application Details...");

                var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);

                var application = await ApplicationFactory.CreateAsync(output, args.Path, args.Framework, filter);
                if (application.Services.Count == 0)
                {
                    throw new CommandException($"No services found in \"{application.Source.Name}\"");
                }

                var options = new HostOptions()
                {
                    Dashboard = args.Dashboard,
                    Docker    = args.Docker,
                    NoBuild   = args.NoBuild,
                    Port      = args.Port,

                    // parsed later by the diagnostics code
                    DistributedTraceProvider = args.Dtrace,
                    LoggingProvider          = args.Logs,
                    MetricsProvider          = args.Metrics,
                    LogVerbosity             = args.Verbosity,
                    Watch = args.Watch
                };
                options.Debug.AddRange(args.Debug);

                await application.ProcessExtensionsAsync(options, output, ExtensionContext.OperationKind.LocalRun);

                InitializeThreadPoolSettings(application.Services.Count);

                output.WriteInfoLine("Launching Tye Host...");
                output.WriteInfoLine(string.Empty);

                await using var host = new TyeHost(application.ToHostingApplication(), options);
                await host.RunAsync();
            });

            return(command);
        }