This repository has been archived by the owner on Feb 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add health check listener, closes #42
- Loading branch information
1 parent
d0990ec
commit c6374c3
Showing
13 changed files
with
297 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,4 +22,4 @@ func MakeAlert(alertType string, apiKey string) (api.Alert, error) { | |
default: | ||
return &noopAlert{}, nil | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package monitoring | ||
|
||
// Metrics is an interface that allows a client to pass in key value pairs (keys must be strings) | ||
// and it can dump the metrics as JSON. | ||
type Metrics interface { | ||
UpdateMetrics(metrics map[string]interface{}) | ||
MarshalJSON() ([]byte, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package monitoring | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/lightyeario/kelp/support/networking" | ||
) | ||
|
||
// metricsEndpoint represents a monitoring API endpoint that always responds with a JSON | ||
// encoding of the provided metrics. The auth level for the endpoint can be NoAuth (public access) | ||
// or GoogleAuth which uses a Google account for authorization. | ||
type metricsEndpoint struct { | ||
path string | ||
metrics Metrics | ||
authLevel networking.AuthLevel | ||
} | ||
|
||
// MakeMetricsEndpoint creates an Endpoint for the monitoring server with the desired auth level. | ||
// The endpoint's response is always a JSON dump of the provided metrics. | ||
func MakeMetricsEndpoint(path string, metrics Metrics, authLevel networking.AuthLevel) (networking.Endpoint, error) { | ||
if !strings.HasPrefix(path, "/") { | ||
return nil, fmt.Errorf("endpoint path must begin with /") | ||
} | ||
s := &metricsEndpoint{ | ||
path: path, | ||
metrics: metrics, | ||
authLevel: authLevel, | ||
} | ||
return s, nil | ||
} | ||
|
||
func (m *metricsEndpoint) GetAuthLevel() networking.AuthLevel { | ||
return m.authLevel | ||
} | ||
|
||
func (m *metricsEndpoint) GetPath() string { | ||
return m.path | ||
} | ||
|
||
// GetHandlerFunc returns a HandlerFunc that writes the JSON representation of the metrics | ||
// that's passed into the endpoint. | ||
func (m *metricsEndpoint) GetHandlerFunc() http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
json, e := m.metrics.MarshalJSON() | ||
if e != nil { | ||
log.Printf("error marshalling metrics json: %s\n", e) | ||
http.Error(w, e.Error(), 500) | ||
return | ||
} | ||
w.WriteHeader(200) | ||
w.Header().Set("Content-Type", "application/json") | ||
_, e = w.Write(json) | ||
if e != nil { | ||
log.Printf("error writing to the response writer: %s\n", e) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package monitoring | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/lightyeario/kelp/support/networking" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMetricsEndpoint_NoAuthEndpoint(t *testing.T) { | ||
testMetrics, e := MakeMetricsRecorder(map[string]interface{}{"this is a test message": true}) | ||
if !assert.Nil(t, e) { | ||
return | ||
} | ||
testEndpoint, e := MakeMetricsEndpoint("/test", testMetrics, networking.NoAuth) | ||
if !assert.Nil(t, e) { | ||
return | ||
} | ||
|
||
req, e := http.NewRequest("GET", "/test", nil) | ||
if !assert.Nil(t, e) { | ||
return | ||
} | ||
w := httptest.NewRecorder() | ||
testEndpoint.GetHandlerFunc().ServeHTTP(w, req) | ||
assert.Equal(t, 200, w.Code) | ||
assert.Equal(t, "{\"this is a test message\":true}", w.Body.String()) | ||
|
||
// Mutate the metrics and test if the server response changes | ||
testMetrics.UpdateMetrics(map[string]interface{}{"this is a test message": false}) | ||
|
||
w = httptest.NewRecorder() | ||
req = httptest.NewRequest("GET", "/test", nil) | ||
testEndpoint.GetHandlerFunc().ServeHTTP(w, req) | ||
assert.Equal(t, 200, w.Code) | ||
assert.Equal(t, "{\"this is a test message\":false}", w.Body.String()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package monitoring | ||
|
||
import "encoding/json" | ||
|
||
// MetricsRecorder uses a map to store metrics and implements the api.Metrics interface. | ||
type metricsRecorder struct { | ||
records map[string]interface{} | ||
} | ||
|
||
var _ Metrics = &metricsRecorder{} | ||
|
||
// MakeMetricsRecorder makes a metrics recorder with the records map as the underlying map. If records | ||
// is nil, then an empty map will be initialized for you. | ||
func MakeMetricsRecorder(records map[string]interface{}) (Metrics, error) { | ||
if records == nil { | ||
return &metricsRecorder{ | ||
records: map[string]interface{}{}, | ||
}, nil | ||
} | ||
return &metricsRecorder{ | ||
records: records, | ||
}, nil | ||
} | ||
|
||
// UpdateMetrics updates (or adds if non-existent) metrics in the records for all key-value | ||
// pairs in the provided map of metrics. | ||
func (m *metricsRecorder) UpdateMetrics(metrics map[string]interface{}) { | ||
for k, v := range metrics { | ||
m.records[k] = v | ||
} | ||
} | ||
|
||
// MarshalJSON gives the JSON representation of the records. | ||
func (m *metricsRecorder) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(m.records) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package monitoring | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMetricsRecorder_UpdateMetrics(t *testing.T) { | ||
m := &metricsRecorder{ | ||
records: map[string]interface{}{}, | ||
} | ||
m.UpdateMetrics(map[string]interface{}{ | ||
"max_qty": 100, | ||
"volume": 200000, | ||
"kelp_version": "1.1", | ||
}) | ||
assert.Equal(t, 100, m.records["max_qty"]) | ||
assert.Equal(t, 200000, m.records["volume"]) | ||
assert.Equal(t, "1.1", m.records["kelp_version"]) | ||
assert.Equal(t, nil, m.records["nonexistent"]) | ||
m.UpdateMetrics(map[string]interface{}{ | ||
"max_qty": 200, | ||
}) | ||
assert.Equal(t, 200, m.records["max_qty"]) | ||
} | ||
|
||
func TestMetricsRecorder_MarshalJSON(t *testing.T) { | ||
m := &metricsRecorder{ | ||
records: map[string]interface{}{}, | ||
} | ||
m.UpdateMetrics(map[string]interface{}{ | ||
"statuses": map[string]string{ | ||
"a": "ok", | ||
"b": "error", | ||
}, | ||
"trade_ids": []int64{1, 2, 3, 4, 5}, | ||
"version": "10.0.1", | ||
}) | ||
json, e := m.MarshalJSON() | ||
if !assert.Nil(t, e) { | ||
return | ||
} | ||
assert.Equal(t, `{"statuses":{"a":"ok","b":"error"},"trade_ids":[1,2,3,4,5],"version":"10.0.1"}`, string(json)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package networking | ||
|
||
import "net/http" | ||
|
||
// AuthLevel specifies the level of authentication needed for an endpoint. | ||
type AuthLevel int | ||
|
||
const ( | ||
// NoAuth means that no authentication is required. | ||
NoAuth AuthLevel = iota | ||
// GoogleAuth means that a valid Google email is needed to access the endpoint. | ||
GoogleAuth | ||
) | ||
|
||
// Endpoint represents an API endpoint that implements GetHandlerFunc | ||
// which returns a http.HandlerFunc specifying the behavior when this | ||
// endpoint is hit. It's also required to implement GetAuthLevel, which | ||
// returns the level of authentication that's required to access this endpoint. | ||
// Currently, the values can be NoAuth or GoogleAuth. Lastly, GetPath returns the | ||
// path that routes to this endpoint. | ||
type Endpoint interface { | ||
GetHandlerFunc() http.HandlerFunc | ||
GetAuthLevel() AuthLevel | ||
GetPath() string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package networking | ||
|
||
import ( | ||
"net/http" | ||
"strconv" | ||
) | ||
|
||
// WebServer defines an interface for a generic HTTP/S server with a StartServer function. | ||
// If certFile and certKey are specified, then the server will serve through HTTPS. StartServer | ||
// will run synchronously and return a non-nil error. | ||
type WebServer interface { | ||
StartServer(port uint16, certFile string, keyFile string) error | ||
} | ||
|
||
type server struct { | ||
router *http.ServeMux | ||
} | ||
|
||
// MakeServer creates a WebServer that's responsible for serving all the endpoints passed into it. | ||
func MakeServer(endpoints []Endpoint) (WebServer, error) { | ||
mux := new(http.ServeMux) | ||
s := &server{router: mux} | ||
for _, endpoint := range endpoints { | ||
mux.HandleFunc(endpoint.GetPath(), endpoint.GetHandlerFunc()) | ||
} | ||
return s, nil | ||
} | ||
|
||
// StartServer starts the monitoring server by listening on the specified port and serving requests | ||
// according to its handlers. If certFile and keyFile aren't empty, then the server will use TLS. | ||
// This call will block or return a non-nil error. | ||
func (s *server) StartServer(port uint16, certFile string, keyFile string) error { | ||
addr := ":" + strconv.Itoa(int(port)) | ||
return http.ListenAndServe(addr, s.router) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters