Skip to content

Commit

Permalink
Merge pull request #54 from miguelff/memcache-config-endpoint
Browse files Browse the repository at this point in the history
`/config/memcache` endpoint
  • Loading branch information
miguelff authored Sep 11, 2017
2 parents 0a3a01b + 6bbc025 commit 2a98f7f
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 5 deletions.
2 changes: 2 additions & 0 deletions doc/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Notes:

- `/help`: show all supported request paths

- `/config/memcache`: show the [memcache](memcache.md) configuration used, so freno clients can use it to implement more efficient read strategies.

# GET method

`GET` and `HEAD` respond with same status codes. But `GET` requests compute and return additional data. Automated requests should not be interested in this data; the status code is what should guide the clients. However humans or manual requests may benefit from extra information supplied by the `GET` request.
Expand Down
24 changes: 24 additions & 0 deletions doc/memcache.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,27 @@ Optionally set `MemcachePath` (default is `"freno"`):
- The value will be of the form `<epochmillis>:<aggregated-value>`.
- As example, it might be `1497418678836:0.54` where `1497418678836` is the unix epoch in milliseconds, and `0.54` is the aggregated value.
- Embedding the epoch within the value allows the app to double-check the validity of the value, or go into more granular validation.

### Runtime access to memcache configuration

The [HTTP API](http.md) provides the `GET /config/memcache` service, which returns the json representation of the memcache configuration in use:

```json
{
"MemcacheServers": [
"memcache.server.one:11211",
"memcache.server.two:11211",
"memcache.server.three:11211"
],
"MemcachePath": "freno-production",
}
```

If freno is not configured to publish metrics to memcache, the following defaults will be returned:

```json
{
"MemcacheServers": [],
"MemcachePath": "freno",
}
```
7 changes: 7 additions & 0 deletions go/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ func Settings() *ConfigurationSettings {
return Instance().settings
}

// Reset sets the initial state of the configuration instance
func Reset() {
instance = newConfiguration()
}

// Configuration struct stores the readFileNames and points to the settings
// which are the configuration parameters used in the application.
// see ConfigurationSettings for the available settings.
Expand Down Expand Up @@ -104,6 +109,8 @@ func newConfigurationSettings() *ConfigurationSettings {
RaftDataDir: "",
DefaultRaftPort: 0,
RaftNodes: []string{},
MemcacheServers: []string{},
MemcachePath: "freno",
//Debug: false,
//ListenSocket: "",
//AnExampleListOfStrings: []string{"*"},
Expand Down
19 changes: 19 additions & 0 deletions go/http/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/github/freno/go/config"
"github.com/github/freno/go/group"
"github.com/github/freno/go/throttle"
metrics "github.com/rcrowley/go-metrics"
Expand All @@ -33,6 +34,7 @@ type API interface {
ThrottledApps(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
RecentApps(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
Help(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
MemcacheConfig(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
}

var endpoints = []string{} // known API URIs
Expand Down Expand Up @@ -263,6 +265,21 @@ func (api *APIImpl) Help(w http.ResponseWriter, r *http.Request, ps httprouter.P
json.NewEncoder(w).Encode(endpoints)
}

// MemcacheConfig outputs the memcache configuration being used, so clients can
// implement more efficient access strategies
func (api *APIImpl) MemcacheConfig(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
memcacheConfig := struct {
MemcacheServers []string
MemcachePath string
}{
config.Settings().MemcacheServers,
config.Settings().MemcachePath,
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(memcacheConfig)
}

// register is a wrapper function for accepting both GET and HEAD requests
func register(router *httprouter.Router, path string, f httprouter.Handle) {
router.HEAD(path, f)
Expand Down Expand Up @@ -309,5 +326,7 @@ func ConfigureRoutes(api API) *httprouter.Router {

register(router, "/help", api.Help)

router.GET("/config/memcache", api.MemcacheConfig)

return router
}
59 changes: 59 additions & 0 deletions go/http/api_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package http

import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"testing"

"github.com/github/freno/go/config"
)

func TestLbCheck(t *testing.T) {
Expand Down Expand Up @@ -32,6 +36,7 @@ func TestRoutes(t *testing.T) {
code int
}{
{http.MethodGet, "/lb-check", http.StatusOK},
{http.MethodGet, "/config/memcache", http.StatusOK},
}
for _, route := range expectedRoutes {
r, _ := http.NewRequest(route.verb, route.path, nil)
Expand All @@ -42,3 +47,57 @@ func TestRoutes(t *testing.T) {
}
}
}

func TestMemcacheConfigWhenProvided(t *testing.T) {
defer config.Reset()

api := NewAPIImpl(nil, nil)
recorder := httptest.NewRecorder()
settings := config.Settings()
settings.MemcacheServers = []string{"memcache.server.one:11211", "memcache.server.two:11211"}
settings.MemcachePath = "myCacheNamespace"

api.MemcacheConfig(recorder, &http.Request{}, nil)

code, body := recorder.Code, recorder.Body.String()
if code != http.StatusOK {
t.Errorf("Expected MemcacheConfig to respond with %d status code, but responded with %d", http.StatusOK, code)
}

type memcacheSettings struct {
MemcacheServers []string
MemcachePath string
}

var expected, actual memcacheSettings
json.Unmarshal([]byte(`{"MemcacheServers":["memcache.server.one:11211","memcache.server.two:11211"],"MemcachePath":"myCacheNamespace"}`), &expected)
json.Unmarshal([]byte(body), &actual)

if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected MemcacheConfig body to be %s, but it's %s", expected, body)
}
}

func TestMemcacheConfigWhenDefault(t *testing.T) {
api := NewAPIImpl(nil, nil)
recorder := httptest.NewRecorder()
api.MemcacheConfig(recorder, &http.Request{}, nil)

code, body := recorder.Code, recorder.Body.String()
if code != http.StatusOK {
t.Errorf("Expected MemcacheConfig to respond with %d status code, but responded with %d", http.StatusOK, code)
}

type memcacheSettings struct {
MemcacheServers []string
MemcachePath string
}

var expected, actual memcacheSettings
json.Unmarshal([]byte(`{"MemcacheServers":[],"MemcachePath":"freno"}`), &expected)
json.Unmarshal([]byte(body), &actual)

if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected MemcacheConfig body to be %s, but it's %s", expected, body)
}
}
7 changes: 2 additions & 5 deletions go/throttle/throttler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ const recentAppsExpiration = time.Hour * 24
const defaultThrottleTTL = 60 * time.Minute
const DefaultThrottleRatio = 1.0

const defaultMemcachePath = "freno"

func init() {
rand.Seed(time.Now().UnixNano())
}
Expand Down Expand Up @@ -82,9 +80,8 @@ func NewThrottler(isLeaderFunc func() bool) *Throttler {
if memcacheServers := config.Settings().MemcacheServers; len(memcacheServers) > 0 {
throttler.memcacheClient = memcache.New(memcacheServers...)
}
if throttler.memcachePath = config.Settings().MemcachePath; throttler.memcachePath == "" {
throttler.memcachePath = defaultMemcachePath
}
throttler.memcachePath = config.Settings().MemcachePath

return throttler
}

Expand Down

0 comments on commit 2a98f7f

Please sign in to comment.