package hub import ( "sort" "github.com/shcizo/package-updater/internal/master/model" "github.com/shcizo/package-updater/internal/protocol" ) // candidate is a connection that has finished its hello and may get jobs. type candidate struct { agentID int64 connID uint64 hello protocol.Hello } // Route picks the connection a job for svc goes to, among agents that speak // at least protocol version minProtocol. It returns model.ErrAgentOffline or // model.ErrAgentTooOld (model.ErrAgentTooOldForSelfUpdate when minProtocol // asks for self-update, model.ErrAgentTooOldForPinned when it asks for a // pinned ref) when there is none. func (h *Hub) Route(svc model.Service, minProtocol int) (model.Route, error) { h.mu.Lock() conns := make([]candidate, 0, len(h.conns)) for id, c := range h.conns { if c.hello != nil { conns = append(conns, candidate{agentID: id, connID: c.id, hello: *c.hello}) } } h.mu.Unlock() return route(svc, conns, minProtocol) } // route picks the connection a job for svc goes to. Compose services and // containers can only run on the agent that reported them. Swarm services // run on the reporting agent if it qualifies, else on any other connected // manager of the same cluster (lowest agent ID first). Too-old wins over // offline only when a connection would otherwise have qualified. func route(svc model.Service, conns []candidate, minProtocol int) (model.Route, error) { sort.Slice(conns, func(a, b int) bool { return conns[a].agentID < conns[b].agentID }) tooOld := false try := func(c candidate) bool { if svc.Kind == protocol.KindSwarm { if !c.hello.SwarmManager || c.hello.SwarmClusterID == "" || c.hello.SwarmClusterID != svc.SwarmClusterID { return false } } if c.hello.ProtocolVersion < minProtocol { tooOld = true return false } return true } if svc.AgentID != nil { for _, c := range conns { if c.agentID == *svc.AgentID && try(c) { return model.Route{AgentID: c.agentID, ConnID: c.connID}, nil } } } if svc.Kind == protocol.KindSwarm { for _, c := range conns { if try(c) { return model.Route{AgentID: c.agentID, ConnID: c.connID}, nil } } } if tooOld { if minProtocol >= protocol.MinSelfUpdateVersion { return model.Route{}, model.ErrAgentTooOldForSelfUpdate } if minProtocol >= protocol.MinPinnedRefVersion { return model.Route{}, model.ErrAgentTooOldForPinned } return model.Route{}, model.ErrAgentTooOld } return model.Route{}, model.ErrAgentOffline }