Esempio n. 1
0
        private string[] GetAllContainerIds(Microsoft.Tye.Hosting.Model.Application application)
        {
            var replicas = application.Services.SelectMany(s => s.Value.Replicas);
            var ids      = replicas.Where(r => r.Value is DockerStatus).Select(r => ((DockerStatus)r.Value).ContainerId !).ToArray();

            return(ids);
        }
Esempio n. 2
0
        private int[] GetAllAppPids(Microsoft.Tye.Hosting.Model.Application application)
        {
            var replicas = application.Services.SelectMany(s => s.Value.Replicas);
            var ids      = replicas.Where(r => r.Value is ProcessStatus).Select(r => ((ProcessStatus)r.Value).Pid ?? -1).ToArray();

            return(ids);
        }
Esempio n. 3
0
        private async Task CheckServiceIsUp(Microsoft.Tye.Hosting.Model.Application application, HttpClient client, string serviceName, Uri dashboardUri, TimeSpan?timeout = default)
        {
            // make sure backend is up before frontend
            var dashboardString = await client.GetStringAsync($"{dashboardUri}api/v1/services/{serviceName}");

            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}");

            var startTime = DateTime.UtcNow;

            try
            {
                // Wait up until the timeout to see if we can access the service.
                // For instance if we have to pull a base-image it can take a while.
                while (timeout.HasValue && startTime + timeout.Value > DateTime.UtcNow)
                {
                    try
                    {
                        await client.GetAsync(uriBackendProcess);

                        break;
                    }
                    catch (HttpRequestException)
                    {
                        await Task.Delay(TimeSpan.FromSeconds(3));
                    }
                }

                var appResponse = await client.GetAsync(uriBackendProcess);

                var content = await appResponse.Content.ReadAsStringAsync();

                output.WriteLine(content);
                Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
                if (serviceName == "frontend")
                {
                    Assert.Matches("Frontend Listening IP: (.+)\n", content);
                    Assert.Matches("Backend Listening IP: (.+)\n", content);
                }
            }
            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 application.Services.Values)
                {
                    var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/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);
                }
            }
        }
        private async Task TransformProjectToContainer(Model.Application application, Model.Service service, ProjectRunInfo project)
        {
            var serviceDescription = service.Description;
            var serviceName        = serviceDescription.Name;

            service.Status.ProjectFilePath = project.ProjectFile.FullName;
            var targetFramework = project.TargetFramework;

            // Sometimes building can fail because of file locking (like files being open in VS)
            _logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath);

            var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} /nologo";

            service.Logs.OnNext($"dotnet {publishCommand}");

            var buildResult = await ProcessUtil.RunAsync("dotnet", publishCommand, throwOnError : false);

            service.Logs.OnNext(buildResult.StandardOutput);

            if (buildResult.ExitCode != 0)
            {
                _logger.LogInformation("Publishing {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode);

                // Null out the RunInfo so that
                serviceDescription.RunInfo = null;
                return;
            }

            // We transform the project information into the following docker command:
            // docker run -w /app -v {publishDir}:/app -it {image} dotnet {outputfile}.dll

            // We swap the slashes since we're going to run on linux
            var containerImage = DetermineContainerImage(targetFramework);
            var outputFileName = project.AssemblyName + ".dll";
            var dockerRunInfo  = new DockerRunInfo(containerImage, $"dotnet {outputFileName} {project.Args}")
            {
                WorkingDirectory = "/app"
            };

            dockerRunInfo.VolumeMappings[project.PublishOutputPath] = "/app";

            // Make volume mapping works when running as a container
            foreach (var mapping in project.VolumeMappings)
            {
                dockerRunInfo.VolumeMappings[mapping.Key] = mapping.Value;
            }

            // Change the project into a container info
            serviceDescription.RunInfo = dockerRunInfo;
        }
Esempio n. 5
0
        private static bool IsPortInUseByBinding(Model.Application application, int port)
        {
            foreach (var service in application.Services)
            {
                foreach (var binding in service.Value.Description.Bindings)
                {
                    if (binding.Port == port)
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
        public Task StartAsync(Model.Application application)
        {
            // This transforms a ProjectRunInfo into a container
            var tasks = new List <Task>();

            foreach (var s in application.Services.Values)
            {
                if (s.Description.RunInfo is ProjectRunInfo project)
                {
                    tasks.Add(TransformProjectToContainer(application, s, project));
                }
            }

            return(Task.WhenAll(tasks));
        }
Esempio n. 7
0
        private async Task CheckServiceIsUp(Microsoft.Tye.Hosting.Model.Application application, HttpClient client, string serviceName, Uri dashboardUri)
        {
            // make sure backend is up before frontend
            var service = application.Services.Where(a => a.Value.Description.Name == serviceName).First().Value;
            var binding = service.Description.Bindings.First();

            var protocol = binding.Protocol != null && 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.

            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);
                var content = await appResponse.Content.ReadAsStringAsync();

                if (serviceName == "frontend")
                {
                    Assert.Matches("Frontend Listening IP: (.+)\n", content);
                    Assert.Matches("Backend Listening IP: (.+)\n", content);
                }
            }
            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 application.Services.Values)
                {
                    var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/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);
                }
            }
        }
Esempio n. 8
0
 public async Task StopAsync(Model.Application application)
 {
     foreach (var webApp in _webApplications)
     {
         try
         {
             await webApp.StopAsync();
         }
         catch (OperationCanceledException)
         {
         }
         finally
         {
             webApp.Dispose();
         }
     }
 }
        private async Task TransformProjectToContainer(Model.Application application, Model.Service service, ProjectRunInfo project)
        {
            var serviceDescription = service.Description;
            var serviceName        = serviceDescription.Name;

            var expandedProject = Environment.ExpandEnvironmentVariables(project.Project);
            var fullProjectPath = Path.GetFullPath(Path.Combine(application.ContextDirectory, expandedProject));

            service.Status.ProjectFilePath = fullProjectPath;

            // Sometimes building can fail because of file locking (like files being open in VS)
            _logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath);

            service.Logs.OnNext($"dotnet publish \"{service.Status.ProjectFilePath}\" /nologo");

            var buildResult = await ProcessUtil.RunAsync("dotnet", $"publish \"{service.Status.ProjectFilePath}\" /nologo", throwOnError : false);

            service.Logs.OnNext(buildResult.StandardOutput);

            if (buildResult.ExitCode != 0)
            {
                _logger.LogInformation("Publishing {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode);

                // Null out the RunInfo so that
                serviceDescription.RunInfo = null;
                return;
            }

            var targetFramework = GetTargetFramework(service.Status.ProjectFilePath);

            // We transform the project information into the following docker command:
            // docker run -w /app -v {projectDir}:/app -it {image} dotnet /app/bin/Debug/{tfm}/publish/{outputfile}.dll
            var containerImage = DetermineContainerImage(targetFramework);
            var outputFileName = Path.GetFileNameWithoutExtension(service.Status.ProjectFilePath) + ".dll";
            var dockerRunInfo  = new DockerRunInfo(containerImage, $"dotnet /app/bin/Debug/{targetFramework}/publish/{outputFileName} {project.Args}")
            {
                WorkingDirectory = "/app"
            };

            dockerRunInfo.VolumeMappings[Path.GetDirectoryName(service.Status.ProjectFilePath) !] = "/app";
Esempio n. 10
0
        private async Task CheckServiceIsUp(Microsoft.Tye.Hosting.Model.Application application, HttpClient client, string serviceName, Uri dashboardUri)
        {
            // make sure backend is up before frontend
            var dashboardString = await client.GetStringAsync($"{dashboardUri}api/v1/services/{serviceName}");

            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}");

            try
            {
                var appResponse = await client.GetAsync(uriBackendProcess);

                Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
                var content = await appResponse.Content.ReadAsStringAsync();

                if (serviceName == "frontend")
                {
                    Assert.Matches("Frontend Listening IP: (.+)\n", content);
                    Assert.Matches("Backend Listening IP: (.+)\n", content);
                }
            }
            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 application.Services.Values)
                {
                    var request  = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/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);
                }
            }
        }
Esempio n. 11
0
        private string[] GetAllReplicasNames(Microsoft.Tye.Hosting.Model.Application application)
        {
            var replicas = application.Services.SelectMany(s => s.Value.Replicas);

            return(replicas.Select(r => r.Value.Name).ToArray());
        }
 public Task StopAsync(Model.Application application)
 {
     return(Task.CompletedTask);
 }
Esempio n. 13
0
        public async Task StartAsync(Model.Application application)
        {
            var invoker = new HttpMessageInvoker(new ConnectionRetryHandler(new SocketsHttpHandler
            {
                AllowAutoRedirect      = false,
                AutomaticDecompression = DecompressionMethods.None,
                UseProxy = false
            }));

            foreach (var service in application.Services.Values)
            {
                var serviceDescription = service.Description;

                if (service.Description.RunInfo is IngressRunInfo runInfo)
                {
                    var builder = new WebApplicationBuilder();

                    builder.Services.AddSingleton <MatcherPolicy, IngressHostMatcherPolicy>();

                    builder.Logging.AddProvider(new ServiceLoggerProvider(service.Logs));

                    var addresses = new List <string>();

                    // Bind to the addresses on this resource
                    for (int i = 0; i < serviceDescription.Replicas; i++)
                    {
                        // Fake replicas since it's all running processes
                        var replica = service.Description.Name + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
                        var status  = new IngressStatus(service, replica);
                        service.Replicas[replica] = status;

                        var ports = new List <int>();

                        foreach (var binding in serviceDescription.Bindings)
                        {
                            if (binding.Port == null)
                            {
                                continue;
                            }

                            var port = service.PortMap[binding.Port.Value][i];
                            ports.Add(port);
                            var url = $"{binding.Protocol ?? "http"}://localhost:{port}";
                            addresses.Add(url);
                        }

                        status.Ports = ports;

                        service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));
                    }

                    builder.Server.UseUrls(addresses.ToArray());
                    var webApp = builder.Build();

                    _webApplications.Add(webApp);

                    // For each ingress rule, bind to the path and host
                    foreach (var rule in runInfo.Rules)
                    {
                        if (!application.Services.TryGetValue(rule.Service, out var target))
                        {
                            continue;
                        }

                        _logger.LogInformation("Processing ingress rule: Path:{Path}, Host:{Host}, Service:{Service}", rule.Path, rule.Host, rule.Service);

                        var targetServiceDescription = target.Description;

                        var uris = new List <Uri>();

                        // For each of the target service replicas, get the base URL
                        // based on the replica port
                        for (int i = 0; i < targetServiceDescription.Replicas; i++)
                        {
                            foreach (var binding in targetServiceDescription.Bindings)
                            {
                                if (binding.Port == null)
                                {
                                    continue;
                                }

                                var port = target.PortMap[binding.Port.Value][i];
                                var url  = $"{binding.Protocol ?? "http"}://localhost:{port}";
                                uris.Add(new Uri(url));
                            }
                        }

                        // The only load balancing strategy here is round robin
                        long            count = 0;
                        RequestDelegate del   = context =>
                        {
                            var next = (int)(Interlocked.Increment(ref count) % uris.Count);

                            var uri = new UriBuilder(uris[next])
                            {
                                Path = (string)context.Request.RouteValues["path"]
                            };

                            return(context.ProxyRequest(invoker, uri.Uri));
                        };

                        IEndpointConventionBuilder conventions = null !;

                        if (rule.Path != null)
                        {
                            conventions = ((IEndpointRouteBuilder)webApp).Map(rule.Path.TrimEnd('/') + "/{**path}", del);
                        }
                        else
                        {
                            conventions = webApp.MapFallback(del);
                        }

                        if (rule.Host != null)
                        {
                            conventions.WithMetadata(new IngressHostMetadata(rule.Host));
                        }

                        conventions.WithDisplayName(rule.Service);
                    }
                }
            }

            foreach (var app in _webApplications)
            {
                await app.StartAsync();
            }
        }