proxy use room manager.

This commit is contained in:
Miroslav Šedivý
2023-12-01 00:14:04 +01:00
parent b62ac1097c
commit 38e4cab3fe
3 changed files with 65 additions and 127 deletions
+11 -1
View File
@@ -59,6 +59,8 @@ func RoomNotFound(w http.ResponseWriter, r *http.Request, waitEnabled bool) {
if waitEnabled { if waitEnabled {
roomWait(w, r) roomWait(w, r)
} else {
w.Write([]byte(`<meta http-equiv="refresh" content="60">`))
} }
} }
@@ -81,10 +83,12 @@ func RoomNotRunning(w http.ResponseWriter, r *http.Request, waitEnabled bool) {
if waitEnabled { if waitEnabled {
roomWait(w, r) roomWait(w, r)
} else {
w.Write([]byte(`<meta http-equiv="refresh" content="10">`))
} }
} }
func RoomNotReady(w http.ResponseWriter, r *http.Request) { func RoomNotReady(w http.ResponseWriter, r *http.Request, waitEnabled bool) {
utils.Swal2Response(w, ` utils.Swal2Response(w, `
<meta http-equiv="refresh" content="2"> <meta http-equiv="refresh" content="2">
@@ -102,6 +106,12 @@ func RoomNotReady(w http.ResponseWriter, r *http.Request) {
<button type="button" onclick="location = location" class="swal2-confirm swal2-styled" style="margin-top: 1.25em">Reload</button> <button type="button" onclick="location = location" class="swal2-confirm swal2-styled" style="margin-top: 1.25em">Reload</button>
</div> </div>
`) `)
if waitEnabled {
roomWait(w, r)
} else {
w.Write([]byte(`<meta http-equiv="refresh" content="2">`))
}
} }
func RoomReady(w http.ResponseWriter, r *http.Request) { func RoomReady(w http.ResponseWriter, r *http.Request) {
+53 -124
View File
@@ -2,7 +2,6 @@ package proxy
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
@@ -11,18 +10,18 @@ import (
"strings" "strings"
"sync" "sync"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/m1k1o/neko-rooms/internal/room"
"github.com/m1k1o/neko-rooms/internal/types"
"github.com/m1k1o/neko-rooms/pkg/prefix" "github.com/m1k1o/neko-rooms/pkg/prefix"
) )
type entry struct { type entry struct {
id string id string
running bool running bool
ready bool
handler *httputil.ReverseProxy handler *httputil.ReverseProxy
} }
@@ -41,20 +40,18 @@ type ProxyManagerCtx struct {
waitChans map[string]*wait waitChans map[string]*wait
waitEnabled bool waitEnabled bool
client *dockerClient.Client rooms *room.RoomManagerCtx
instanceName string handlers prefix.Tree[*entry]
handlers prefix.Tree[*entry]
} }
func New(client *dockerClient.Client, instanceName string, waitEnabled bool) *ProxyManagerCtx { func New(rooms *room.RoomManagerCtx, waitEnabled bool) *ProxyManagerCtx {
return &ProxyManagerCtx{ return &ProxyManagerCtx{
logger: log.With().Str("module", "proxy").Logger(), logger: log.With().Str("module", "proxy").Logger(),
waitChans: map[string]*wait{}, waitChans: map[string]*wait{},
client: client, rooms: rooms,
instanceName: instanceName, waitEnabled: waitEnabled,
waitEnabled: waitEnabled, handlers: prefix.NewTree[*entry](),
handlers: prefix.NewTree[*entry](),
} }
} }
@@ -67,47 +64,31 @@ func (p *ProxyManagerCtx) Start() {
p.logger.Err(err).Msg("unable to refresh containers") p.logger.Err(err).Msg("unable to refresh containers")
} }
msgs, errs := p.client.Events(p.ctx, dockerTypes.EventsOptions{ msgs, errs := p.rooms.Events(p.ctx)
Filters: filters.NewArgs(
filters.Arg("type", "container"),
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.instanceName)),
filters.Arg("event", "create"),
filters.Arg("event", "start"),
//filters.Arg("event", "health_status"),
filters.Arg("event", "stop"),
filters.Arg("event", "destroy"),
),
})
for { for {
select { select {
case err, ok := <-errs: case err, ok := <-errs:
if !ok { if !ok {
p.logger.Fatal().Msg("docker event error channel closed")
return return
} }
p.logger.Err(err).Msg("got docker event error") p.logger.Err(err).Msg("room event error")
case msg, ok := <-msgs: case msg, ok := <-msgs:
if !ok { enabled, path, port, ok := p.parseLabels(msg.ContainerLabels)
p.logger.Fatal().Msg("docker event channel closed")
return
}
enabled, path, port, ok := p.parseLabels(msg.Actor.Attributes)
if !ok { if !ok {
break break
} }
host := msg.ID[:12] + ":" + port host := msg.ID + ":" + port
p.logger.Info(). p.logger.Info().
Str("action", msg.Action). Str("action", string(msg.Action)).
Str("path", path). Str("path", path).
Str("host", host). Str("host", host).
Msg("got docker event") Msg("got room event")
// terminate waiting for any events // terminate waiting for any event
if p.waitEnabled { if p.waitEnabled {
p.waitMu.Lock() p.waitMu.Lock()
ch, ok := p.waitChans[path] ch, ok := p.waitChans[path]
@@ -120,15 +101,22 @@ func (p *ProxyManagerCtx) Start() {
p.mu.Lock() p.mu.Lock()
switch msg.Action { switch msg.Action {
case "create": case types.RoomEventCreated:
p.handlers.Insert(path, &entry{ p.handlers.Insert(path, &entry{
id: msg.ID, id: msg.ID,
running: false, running: false,
}) })
case "start": case types.RoomEventStarted:
p.handlers.Insert(path, &entry{
id: msg.ID,
running: true,
ready: false,
})
case types.RoomEventReady:
e := &entry{ e := &entry{
id: msg.ID, id: msg.ID,
running: true, running: true,
ready: true,
} }
// if proxying is disabled // if proxying is disabled
@@ -140,12 +128,12 @@ func (p *ProxyManagerCtx) Start() {
} }
p.handlers.Insert(path, e) p.handlers.Insert(path, e)
case "stop": case types.RoomEventStopped:
p.handlers.Insert(path, &entry{ p.handlers.Insert(path, &entry{
id: msg.ID, id: msg.ID,
running: false, running: false,
}) })
case "destroy": case types.RoomEventDestroyed:
p.handlers.Remove(path) p.handlers.Remove(path)
} }
p.mu.Unlock() p.mu.Unlock()
@@ -163,41 +151,33 @@ func (p *ProxyManagerCtx) Refresh() error {
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{ rooms, err := p.rooms.List(p.ctx, nil)
All: true,
Filters: filters.NewArgs(
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.instanceName)),
),
})
if err != nil { if err != nil {
return err return err
} }
p.handlers = prefix.NewTree[*entry]() p.handlers = prefix.NewTree[*entry]()
for _, cont := range containers { for _, room := range rooms {
enabled, path, port, ok := p.parseLabels(cont.Labels) enabled, path, port, ok := p.parseLabels(room.ContainerLabels)
if !ok { if !ok {
continue continue
} }
host := cont.ID[:12] + ":" + port host := room.ID + ":" + port
entry := &entry{ entry := &entry{
id: cont.ID, id: room.ID,
running: room.Running,
ready: room.IsReady,
} }
if cont.State == "running" { // if proxying is enabled and room is ready
entry.running = true if enabled && room.IsReady {
entry.handler = httputil.NewSingleHostReverseProxy(&url.URL{
// if proxying is disabled Scheme: "http",
if enabled { Host: host,
entry.handler = httputil.NewSingleHostReverseProxy(&url.URL{ })
Scheme: "http",
Host: host,
})
}
} }
p.handlers.Insert(path, entry) p.handlers.Insert(path, entry)
@@ -207,41 +187,9 @@ func (p *ProxyManagerCtx) Refresh() error {
} }
func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (enabled bool, path string, port string, ok bool) { func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (enabled bool, path string, port string, ok bool) {
enabledStr, found := labels["m1k1o.neko_rooms.proxy.enabled"] var enabledStr string
if !found { enabledStr, ok = labels["m1k1o.neko_rooms.proxy.enabled"]
// if !ok {
// workaround for legacy traefik labels
//
// get room name
var roomName string
roomName, ok = labels["m1k1o.neko_rooms.name"]
if !ok {
return
}
// get container name
containerName := p.instanceName + "-" + roomName
// get path
path, ok = labels["traefik.http.middlewares."+containerName+"-prf.stripprefix.prefixes"]
if !ok {
return
}
// remove last /
path = strings.TrimSuffix(path, "/")
// get port
port, ok = labels["traefik.http.services."+containerName+"-frontend.loadbalancer.server.port"]
if !ok {
return
}
//
// workaround for legacy traefik labels
//
return return
} }
@@ -319,50 +267,31 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
// if room not running // if room not running
if !proxy.running { if !proxy.running || !proxy.ready {
// blocking until room is running // blocking until room is ready
if r.URL.Query().Has("wait") && p.waitEnabled { if r.URL.Query().Has("wait") && p.waitEnabled {
p.waitForPath(w, r, cleanPath) p.waitForPath(w, r, cleanPath)
return return
} }
RoomNotRunning(w, r, p.waitEnabled) if !proxy.running {
RoomNotRunning(w, r, p.waitEnabled)
} else {
RoomNotReady(w, r, p.waitEnabled)
}
return return
} }
// if not proxying, check for container readiness status // if not proxying, just return room ready
if proxy.handler == nil { if proxy.handler == nil {
containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{ RoomReady(w, r)
All: true,
Filters: filters.NewArgs(
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.instanceName)),
filters.Arg("id", proxy.id),
),
})
if err != nil || len(containers) != 1 {
p.logger.Err(err).Msg("error while getting container ready status")
RoomNotReady(w, r)
return
}
container := containers[0]
if strings.Contains(container.Status, "starting") {
RoomNotReady(w, r)
} else {
RoomReady(w, r)
}
return return
} }
// handle not ready room // handle not ready room
proxy.handler.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { proxy.handler.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
if r.URL.Path == "/" {
RoomNotReady(w, r)
}
p.logger.Err(err).Str("prefix", prefix).Msg("proxying error") p.logger.Err(err).Str("prefix", prefix).Msg("proxying error")
http.Error(w, "unable to connect to room", http.StatusBadGateway)
} }
// strip prefix from proxy // strip prefix from proxy
+1 -2
View File
@@ -144,8 +144,7 @@ func (main *MainCtx) Start() {
) )
main.proxyManager = proxy.New( main.proxyManager = proxy.New(
client, main.roomManager,
main.Configs.Room.InstanceName,
main.Configs.Room.WaitEnabled, main.Configs.Room.WaitEnabled,
) )
main.proxyManager.Start() main.proxyManager.Start()