From b6fc11ca6556c20e882001c646b737772f93aa73 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Wed, 12 Jan 2022 09:51:09 -0800 Subject: [PATCH] start serving requests for the channel via IReverseProxy Signed-off-by: Matthew Fisher --- .devcontainer/devcontainer.json | 16 +++----------- .../ActiveRevisionChangedEventHandler.cs | 21 +++++++++++++------ .../ChannelCreatedEventHandler.cs | 2 ++ .../ChannelDeletedEventHandler.cs | 12 ++++++++++- .../Common/Interfaces/IJobScheduler.cs | 2 +- .../Common/Interfaces/IReverseProxy.cs | 12 +++++++++++ .../JobSchedulers/LocalJobScheduler.cs | 13 ++++++------ .../ReverseProxies/YarpReverseProxy.cs | 12 ++++++----- src/Web/appsettings.Development.json | 12 +++++------ 9 files changed, 63 insertions(+), 39 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3be668a5c..1a69a6522 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -28,9 +28,7 @@ // this will not work as Headers don't seem to be forwarded by the Codespaces proxy. "forwardPorts": [ 5309, - 32768, - 32769, - 32770 + 8080 ], "remoteUser": "vscode", "updateRemoteUserUID": false, @@ -41,16 +39,8 @@ "label": "Hippo HTTPS Port", "protocol": "https" }, - "32768": { - "label": "Application Channel HTTP Port ", - "protocol": "http" - }, - "32769": { - "label": "Application Channel HTTP Port", - "protocol": "http" - }, - "32770": { - "label": "Application Channel HTTP Port", + "8080": { + "label": "Bindle HTTP Port", "protocol": "http" } } diff --git a/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs b/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs index 7ec187c2c..54c3befac 100644 --- a/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs +++ b/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs @@ -12,10 +12,13 @@ public class ActiveRevisionChangedEventHandler : INotificationHandler logger, IJobScheduler jobScheduler) + private readonly IReverseProxy _reverseProxy; + + public ActiveRevisionChangedEventHandler(ILogger logger, IJobScheduler jobScheduler, IReverseProxy reverseProxy) { _logger = logger; _jobScheduler = jobScheduler; + _reverseProxy = reverseProxy; } public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) @@ -26,15 +29,21 @@ public Task Handle(DomainEventNotification notificat if (domainEvent.Channel.ActiveRevision != null) { - _logger.LogInformation($"ExecuteAsync: stopping {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name} at rev {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + _logger.LogInformation($"{domainEvent.Channel.App.Name}: Stopping channel {domainEvent.Channel.Name} at revision {domainEvent.Channel.ActiveRevision.RevisionNumber}"); _jobScheduler.Stop(domainEvent.Channel); - _logger.LogInformation($"ExecuteAsync: starting {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name} at rev {domainEvent.Channel.ActiveRevision.RevisionNumber}"); - _jobScheduler.Start(domainEvent.Channel); - _logger.LogInformation($"ExecuteAsync: started {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name} at rev {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + _logger.LogInformation($"{domainEvent.Channel.App.Name}: Starting channel {domainEvent.Channel.Name} at revision {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + var status = _jobScheduler.Start(domainEvent.Channel); + if (!status.IsRunning) + { + _logger.LogInformation($"{domainEvent.Channel.App.Name}: Channel {domainEvent.Channel.Name} at revision {domainEvent.Channel.ActiveRevision.RevisionNumber} failed to start"); + _reverseProxy.Stop(domainEvent.Channel); + } + _logger.LogInformation($"Started {domainEvent.Channel.App.Name} Channel {domainEvent.Channel.Name} at revision {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + _reverseProxy.Start(domainEvent.Channel, status.ListenAddress); } else { - _logger.LogInformation($"ExecuteAsync: not restarting {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name}: no active revision"); + _logger.LogInformation($"Not restarting {domainEvent.Channel.App.Name} Channel {domainEvent.Channel.Name}: no active revision"); } return Task.CompletedTask; diff --git a/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs b/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs index 7b28a8571..b60e5290e 100644 --- a/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs +++ b/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs @@ -20,6 +20,8 @@ public Task Handle(DomainEventNotification notification, Ca _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + // TODO: schedule a "parking lot" app instance to start serving requests + return Task.CompletedTask; } } diff --git a/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs b/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs index f7a76a4a1..ecb70d6c2 100644 --- a/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs +++ b/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs @@ -1,3 +1,4 @@ +using Hippo.Application.Common.Interfaces; using Hippo.Application.Common.Models; using Hippo.Core.Events; using MediatR; @@ -9,9 +10,15 @@ public class ChannelDeletedEventHandler : INotificationHandler _logger; - public ChannelDeletedEventHandler(ILogger logger) + private readonly IJobScheduler _jobScheduler; + + private readonly IReverseProxy _reverseProxy; + + public ChannelDeletedEventHandler(ILogger logger, IJobScheduler jobScheduler, IReverseProxy reverseProxy) { _logger = logger; + _jobScheduler = jobScheduler; + _reverseProxy = reverseProxy; } public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) @@ -20,6 +27,9 @@ public Task Handle(DomainEventNotification notification, Ca _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + _reverseProxy.Stop(domainEvent.Channel); + _jobScheduler.Stop(domainEvent.Channel); + return Task.CompletedTask; } } diff --git a/src/Application/Common/Interfaces/IJobScheduler.cs b/src/Application/Common/Interfaces/IJobScheduler.cs index c3b003632..44177ac20 100644 --- a/src/Application/Common/Interfaces/IJobScheduler.cs +++ b/src/Application/Common/Interfaces/IJobScheduler.cs @@ -7,7 +7,7 @@ public interface IJobScheduler /// /// Schedule the current release. /// - void Start(Channel c); + ChannelStatus Start(Channel c); /// /// Gracefully shut down the current release. diff --git a/src/Application/Common/Interfaces/IReverseProxy.cs b/src/Application/Common/Interfaces/IReverseProxy.cs index 0bcf4c069..d6c5a0e9d 100644 --- a/src/Application/Common/Interfaces/IReverseProxy.cs +++ b/src/Application/Common/Interfaces/IReverseProxy.cs @@ -8,3 +8,15 @@ public interface IReverseProxy void Stop(Channel c); } + +public class ChannelStatus +{ + public readonly bool IsRunning; + public readonly string ListenAddress; + + public ChannelStatus(bool isRunning, string listenAddress) + { + IsRunning = isRunning; + ListenAddress = listenAddress; + } +} diff --git a/src/Infrastructure/JobSchedulers/LocalJobScheduler.cs b/src/Infrastructure/JobSchedulers/LocalJobScheduler.cs index 155918602..cad3bd04a 100644 --- a/src/Infrastructure/JobSchedulers/LocalJobScheduler.cs +++ b/src/Infrastructure/JobSchedulers/LocalJobScheduler.cs @@ -47,7 +47,7 @@ public LocalJobScheduler(ILogger logger, IConfiguration confi }); } - public void Start(Channel c) + public ChannelStatus Start(Channel c) { var port = c.PortId + EphemeralPortStartRange; var wagiProgram = WagiBinaryPath(); @@ -56,7 +56,7 @@ public void Start(Channel c) if (c.ActiveRevision == null || string.IsNullOrWhiteSpace(c.ActiveRevision.RevisionNumber)) { _logger.LogWarning($"Can't start {c.App.Name}:{c.Name}: no active revision"); - return; + return new ChannelStatus(false, ""); } var env = String.Join(' ', c.EnvironmentVariables.Select(ev => $"--env {ev.Key}=\"{ev.Value}\"")); @@ -81,7 +81,7 @@ public void Start(Channel c) { // TODO: probably want to throw an Exception here instead _logger.LogError($"Process {psi.FileName} with arguments {psi.Arguments} never started"); - return; + return new ChannelStatus(false, ""); } process.EnableRaisingEvents = true; @@ -100,11 +100,10 @@ public void Start(Channel c) if (process.HasExited) { _logger.LogError($"Process {psi.FileName} with arguments {psi.Arguments} terminated unexpectedly"); + return new ChannelStatus(false, ""); } - else - { - _wagiProcessIds[c.Id] = (process.Id, log); - } + _wagiProcessIds[c.Id] = (process.Id, log); + return new ChannelStatus(true, listenAddress); } } catch (Win32Exception e) // yes, even on Linux diff --git a/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs b/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs index e562f7de5..647b583db 100644 --- a/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs +++ b/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs @@ -1,12 +1,16 @@ using Hippo.Application.Common.Interfaces; using Hippo.Core.Entities; using Hippo.Infrastructure.ReverseProxies.Configuration; +using Microsoft.Extensions.Logging; using Yarp.ReverseProxy.Configuration; namespace Hippo.Infrastructure.ReverseProxies; public class YarpReverseProxy : IReverseProxy { + // TODO: when the host restarts, we should re-hydrate the reverse proxy route config + // + // We could fix this by implementing a file-backed config provider. private readonly InMemoryConfigProvider _configProvider; public YarpReverseProxy(InMemoryConfigProvider configProvider) @@ -26,10 +30,8 @@ public void Start(Channel c, string address) ClusterId = key, Match = new RouteMatch { - Hosts = new List() - { - c.Domain - } + Hosts = new List(){c.Domain}, + Path = "{**catch-all}" } }; @@ -41,7 +43,7 @@ public void Start(Channel c, string address) { key, new DestinationConfig() { - Address = address + Address = "http://" + address } } } diff --git a/src/Web/appsettings.Development.json b/src/Web/appsettings.Development.json index 64a9150ef..352589c11 100644 --- a/src/Web/appsettings.Development.json +++ b/src/Web/appsettings.Development.json @@ -1,13 +1,13 @@ { "Logging": { "LogLevel": { - "Default": "Debug", - "Hippo.Application": "Debug", - "Hippo.Core": "Debug", - "Hippo.Infrastructure": "Debug", - "Hippo.Web": "Debug", - "System": "Information", + "Default": "Information", "Microsoft": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning", + "System": "Information", + + "Hippo": "Debug", "Yarp" : "Debug" } },