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