From cb709e8e966d6152edbc6ed46b6dcf5ee7de515b Mon Sep 17 00:00:00 2001 From: ivcosla Date: Mon, 19 Aug 2019 12:16:21 +0200 Subject: [PATCH] added health endpoint --- pkg/hypervisor/hypervisor.go | 29 +++++++++++++++++++++++++++++ pkg/visor/rpc.go | 35 +++++++++++++++++++++++++++++++++++ pkg/visor/rpc_client.go | 9 +++++++++ pkg/visor/rpc_test.go | 21 +++++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/pkg/hypervisor/hypervisor.go b/pkg/hypervisor/hypervisor.go index d7203b5b69..427a59d3b4 100644 --- a/pkg/hypervisor/hypervisor.go +++ b/pkg/hypervisor/hypervisor.go @@ -130,6 +130,7 @@ func (m *Node) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.Get("/user", m.users.UserInfo()) r.Post("/change-password", m.users.ChangePassword()) r.Get("/nodes", m.getNodes()) + r.Get("/health", m.getHealth()) r.Get("/nodes/{pk}", m.getNode()) r.Get("/nodes/{pk}/apps", m.getApps()) r.Get("/nodes/{pk}/apps/{app}", m.getApp()) @@ -150,6 +151,34 @@ func (m *Node) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.ServeHTTP(w, req) } +// VisorHealth represents a node's health report attached to it's pk for identification +type VisorHealth struct { + PK cipher.PubKey `json:"pk"` + *visor.HealthInfo +} + +// provides summary of health information for every visor +func (m *Node) getHealth() http.HandlerFunc { + healthStatuses := make([]*VisorHealth, len(m.nodes)) + + return func(w http.ResponseWriter, r *http.Request) { + m.mu.RLock() + for pk, c := range m.nodes { + vh := &VisorHealth{PK: pk} + + hi, err := c.Client.Health() + if err != nil { + httputil.WriteJSON(w, r, http.StatusInternalServerError, err) + return + } + + vh.HealthInfo = hi + healthStatuses = append(healthStatuses, vh) + } + + } +} + type summaryResp struct { TCPAddr string `json:"tcp_addr"` *visor.Summary diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index 2f79d9d997..e32990b1ed 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -5,6 +5,8 @@ import ( "errors" "time" + "net/http" + "github.com/google/uuid" "github.com/skycoin/dmsg/cipher" @@ -33,6 +35,39 @@ type RPC struct { node *Node } +/* + <<< NODE HEALTH >>> +*/ + +// HealthInfo carries information about visor's external services health represented as http status codes +type HealthInfo struct { + TransportDiscovery int `json:"transport_discovery"` + RouteFinder int `json:"route_finder"` + SetupNode int `json:"setup_node"` +} + +// Health returns health information about the visor +func (r *RPC) Health(_ *struct{}, out *HealthInfo) error { + out.TransportDiscovery = http.StatusOK + out.RouteFinder = http.StatusOK + out.SetupNode = http.StatusOK + + _, err := r.node.config.TransportDiscovery() + if err != nil { + out.TransportDiscovery = http.StatusNotFound + } + + if r.node.config.Routing.RouteFinder == "" { + out.RouteFinder = http.StatusNotFound + } + + if len(r.node.config.Routing.SetupNodes) == 0 { + out.SetupNode = http.StatusNotFound + } + + return nil +} + /* <<< NODE SUMMARY >>> */ diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index a0dfcadc56..341feab47f 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -20,6 +20,8 @@ import ( type RPCClient interface { Summary() (*Summary, error) + Health() (*HealthInfo, error) + Apps() ([]*AppState, error) StartApp(appName string) error StopApp(appName string) error @@ -64,6 +66,13 @@ func (rc *rpcClient) Summary() (*Summary, error) { return out, err } +// Health calls Health +func (rc *rpcClient) Health() (*HealthInfo, error) { + hi := &HealthInfo{} + err := rc.Call("Health", &struct{}{}, hi) + return hi, err +} + // Apps calls Apps. func (rc *rpcClient) Apps() ([]*AppState, error) { states := make([]*AppState, 0) diff --git a/pkg/visor/rpc_test.go b/pkg/visor/rpc_test.go index 3f151ea60e..9c6bcc5261 100644 --- a/pkg/visor/rpc_test.go +++ b/pkg/visor/rpc_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "net" + "net/http" "net/rpc" "os" "testing" @@ -19,6 +20,26 @@ import ( "github.com/skycoin/skywire/pkg/util/pathutil" ) +func TestHealth(t *testing.T) { + sPK, _ := cipher.GenerateKeyPair() + + c := &Config{} + c.Transport.Discovery = "foo" + c.Routing.SetupNodes = []cipher.PubKey{sPK} + c.Routing.RouteFinder = "foo" + + t.Run("Report all the services as available", func(t *testing.T) { + rpc := &RPC{&Node{config: c}} + h := &HealthInfo{} + err := rpc.Health(&struct{}{}, h) + require.NoError(t, err) + + assert.Equal(t, h.TransportDiscovery, http.StatusOK) + assert.Equal(t, h.SetupNode, http.StatusOK) + assert.Equal(t, h.RouteFinder, http.StatusOK) + }) +} + func TestListApps(t *testing.T) { apps := []AppConfig{ {App: "foo", AutoStart: false, Port: 10},