Add mirror status endpoint, timestamp on last activity
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Tyler 2022-04-02 04:47:14 -04:00
parent 2f43f2d934
commit e5434a9a7b
8 changed files with 104 additions and 41 deletions

1
assets/status-down.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="82" height="20" role="img" aria-label="status: down"><title>status: down</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="82" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="43" height="20" fill="#555"/><rect x="43" width="39" height="20" fill="#e05d44"/><rect width="82" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">status</text><text x="225" y="140" transform="scale(.1)" fill="#fff" textLength="330">status</text><text aria-hidden="true" x="615" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">down</text><text x="615" y="140" transform="scale(.1)" fill="#fff" textLength="290">down</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="104" height="20" role="img" aria-label="status: unknown"><title>status: unknown</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="104" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="43" height="20" fill="#555"/><rect x="43" width="61" height="20" fill="#9f9f9f"/><rect width="104" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">status</text><text x="225" y="140" transform="scale(.1)" fill="#fff" textLength="330">status</text><text aria-hidden="true" x="725" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">unknown</text><text x="725" y="140" transform="scale(.1)" fill="#fff" textLength="510">unknown</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
assets/status-up.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="66" height="20" role="img" aria-label="status: up"><title>status: up</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="66" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="43" height="20" fill="#555"/><rect x="43" width="23" height="20" fill="#4c1"/><rect width="66" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">status</text><text x="225" y="140" transform="scale(.1)" fill="#fff" textLength="330">status</text><text aria-hidden="true" x="535" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="130">up</text><text x="535" y="140" transform="scale(.1)" fill="#fff" textLength="130">up</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -14,6 +14,8 @@ import (
) )
func reloadConfig() { func reloadConfig() {
log.Info("Loading configuration...")
err := viper.ReadInConfig() // Find and read the config file err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file if err != nil { // Handle errors reading the config file
@ -55,7 +57,15 @@ func reloadConfig() {
mirrors["default"] = append(mirrors["NA"], mirrors["EU"]...) mirrors["default"] = append(mirrors["NA"], mirrors["EU"]...)
mirrorMap = mirrors regionMap = mirrors
hosts := make(map[string]*Server)
for _, server := range servers {
hosts[server.Host] = server
}
hostMap = hosts
// Check top choices size // Check top choices size
if topChoices > len(servers) { if topChoices > len(servers) {

25
http.go
View File

@ -17,29 +17,6 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK")) w.Write([]byte("OK"))
} }
func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
mirrorOutput := make(map[string][]string)
for region, mirrors := range mirrorMap {
list := make([]string, len(mirrors))
for i, mirror := range mirrors {
list[i] = r.URL.Scheme + "://" + mirror.Host + "/" + strings.TrimLeft(mirror.Path, "/")
}
mirrorOutput[region] = list
}
json.NewEncoder(w).Encode(mirrorOutput)
}
func mirrorsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(servers)
}
func redirectHandler(w http.ResponseWriter, r *http.Request) { func redirectHandler(w http.ResponseWriter, r *http.Request) {
ipStr, _, err := net.SplitHostPort(r.RemoteAddr) ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
@ -67,7 +44,7 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/") parts := strings.Split(r.URL.Path, "/")
// region = parts[2] // region = parts[2]
if mirrors, ok := mirrorMap[parts[2]]; ok { if mirrors, ok := regionMap[parts[2]]; ok {
choices := make([]randutil.Choice, len(mirrors)) choices := make([]randutil.Choice, len(mirrors))
for i, item := range mirrors { for i, item := range mirrors {

14
main.go
View File

@ -18,11 +18,12 @@ import (
) )
var ( var (
db *maxminddb.Reader db *maxminddb.Reader
servers ServerList servers ServerList
mirrorMap map[string][]*Server regionMap map[string][]*Server
hostMap map[string]*Server
dlMap map[string]string dlMap map[string]string
topChoices int
redirectsServed = promauto.NewCounter(prometheus.CounterOpts{ redirectsServed = promauto.NewCounter(prometheus.CounterOpts{
Name: "armbian_router_redirects", Name: "armbian_router_redirects",
@ -35,8 +36,6 @@ var (
}) })
serverCache *lru.Cache serverCache *lru.Cache
topChoices int
) )
type LocationLookup struct { type LocationLookup struct {
@ -114,6 +113,7 @@ func main() {
r.Head("/status", statusHandler) r.Head("/status", statusHandler)
r.Get("/status", statusHandler) r.Get("/status", statusHandler)
r.Get("/mirrors", legacyMirrorsHandler) r.Get("/mirrors", legacyMirrorsHandler)
r.Get("/mirrors/{server}.svg", mirrorStatusHandler)
r.Get("/mirrors.json", mirrorsHandler) r.Get("/mirrors.json", mirrorsHandler)
r.Post("/reload", reloadHandler) r.Post("/reload", reloadHandler)
r.Get("/dl_map", dlMapHandler) r.Get("/dl_map", dlMapHandler)

69
mirrors.go Normal file
View File

@ -0,0 +1,69 @@
package main
import (
_ "embed"
"encoding/json"
"github.com/go-chi/chi/v5"
"net/http"
"strings"
)
func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
mirrorOutput := make(map[string][]string)
for region, mirrors := range regionMap {
list := make([]string, len(mirrors))
for i, mirror := range mirrors {
list[i] = r.URL.Scheme + "://" + mirror.Host + "/" + strings.TrimLeft(mirror.Path, "/")
}
mirrorOutput[region] = list
}
json.NewEncoder(w).Encode(mirrorOutput)
}
func mirrorsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(servers)
}
var (
//go:embed assets/status-up.svg
statusUp []byte
//go:embed assets/status-down.svg
statusDown []byte
//go:embed assets/status-unknown.svg
statusUnknown []byte
)
func mirrorStatusHandler(w http.ResponseWriter, r *http.Request) {
serverHost := chi.URLParam(r, "server")
w.Header().Set("Content-Type", "image/svg+xml;charset=utf-8")
if serverHost == "" {
w.Write(statusUnknown)
return
}
serverHost = strings.Replace(serverHost, "_", ".", -1)
server, ok := hostMap[serverHost]
if !ok {
w.Write(statusUnknown)
return
}
if server.Available {
w.Write(statusUp)
} else {
w.Write(statusDown)
}
}

View File

@ -16,19 +16,20 @@ import (
var ( var (
checkClient = &http.Client{ checkClient = &http.Client{
Timeout: 10 * time.Second, Timeout: 20 * time.Second,
} }
) )
type Server struct { type Server struct {
Available bool `json:"available"` Available bool `json:"available"`
Host string `json:"host"` Host string `json:"host"`
Path string `json:"path"` Path string `json:"path"`
Latitude float64 `json:"latitude"` Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"` Longitude float64 `json:"longitude"`
Weight int `json:"weight"` Weight int `json:"weight"`
Continent string `json:"continent"` Continent string `json:"continent"`
Redirects prometheus.Counter `json:"-"` Redirects prometheus.Counter `json:"-"`
LastChange time.Time `json:"lastChange"`
} }
func (server *Server) checkStatus() { func (server *Server) checkStatus() {
@ -55,6 +56,7 @@ func (server *Server) checkStatus() {
}).Info("Server went offline") }).Info("Server went offline")
server.Available = false server.Available = false
server.LastChange = time.Now()
} else { } else {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"server": server.Host, "server": server.Host,
@ -72,6 +74,7 @@ func (server *Server) checkStatus() {
if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusMovedPermanently || res.StatusCode == http.StatusFound || res.StatusCode == http.StatusNotFound { if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusMovedPermanently || res.StatusCode == http.StatusFound || res.StatusCode == http.StatusNotFound {
if !server.Available { if !server.Available {
server.Available = true server.Available = true
server.LastChange = time.Now()
log.WithFields(responseFields).Info("Server is online") log.WithFields(responseFields).Info("Server is online")
} }
} else { } else {
@ -80,6 +83,7 @@ func (server *Server) checkStatus() {
if server.Available { if server.Available {
log.WithFields(responseFields).Info("Server went offline") log.WithFields(responseFields).Info("Server went offline")
server.Available = false server.Available = false
server.LastChange = time.Now()
} }
} }
} }