-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add regions endpoint #495
Add regions endpoint #495
Changes from 5 commits
fa3d347
89a155d
c6d7313
0bee0ea
47f8978
2e009e5
a455c80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package agent | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/hashicorp/nomad/nomad/structs" | ||
) | ||
|
||
func (s *HTTPServer) RegionListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { | ||
if req.Method != "GET" { | ||
return nil, CodedError(405, ErrInvalidMethod) | ||
} | ||
|
||
var args structs.GenericRequest | ||
if s.parse(resp, req, &args.Region, &args.QueryOptions) { | ||
return nil, nil | ||
} | ||
|
||
var regions []string | ||
if err := s.agent.RPC("Region.List", &args, ®ions); err != nil { | ||
return nil, err | ||
} | ||
return regions, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package agent | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
func TestHTTP_RegionList(t *testing.T) { | ||
httpTest(t, nil, func(s *TestServer) { | ||
// Make the HTTP request | ||
req, err := http.NewRequest("GET", "/v1/regions", nil) | ||
if err != nil { | ||
t.Fatalf("err: %v", err) | ||
} | ||
respW := httptest.NewRecorder() | ||
|
||
// Make the request | ||
obj, err := s.Server.RegionListRequest(respW, req) | ||
if err != nil { | ||
t.Fatalf("err: %v", err) | ||
} | ||
|
||
out := obj.([]string) | ||
if len(out) != 1 || out[0] != "global" { | ||
t.Fatalf("unexpected regions: %#v", out) | ||
} | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package nomad | ||
|
||
import "github.com/hashicorp/nomad/nomad/structs" | ||
|
||
// Region is used to query and list the known regions | ||
type Region struct { | ||
srv *Server | ||
} | ||
|
||
// List is used to list all of the known regions. No leader forwarding is | ||
// required for this endpoint because memberlist is used to populate the | ||
// peers list we read from. | ||
func (r *Region) List(args *structs.GenericRequest, reply *[]string) error { | ||
*reply = r.srv.Regions() | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package nomad | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/net-rpc-msgpackrpc" | ||
"github.com/hashicorp/nomad/nomad/structs" | ||
"github.com/hashicorp/nomad/testutil" | ||
) | ||
|
||
func TestRegionList(t *testing.T) { | ||
// Make the servers | ||
s1 := testServer(t, func(c *Config) { | ||
c.Region = "region1" | ||
}) | ||
defer s1.Shutdown() | ||
codec := rpcClient(t, s1) | ||
|
||
s2 := testServer(t, func(c *Config) { | ||
c.Region = "region2" | ||
}) | ||
defer s2.Shutdown() | ||
|
||
// Join the servers | ||
s2Addr := fmt.Sprintf("127.0.0.1:%d", | ||
s2.config.SerfConfig.MemberlistConfig.BindPort) | ||
if n, err := s1.Join([]string{s2Addr}); err != nil || n != 1 { | ||
t.Fatalf("Failed joining: %v (%d joined)", err, n) | ||
} | ||
|
||
// Query the regions list | ||
testutil.WaitForResult(func() (bool, error) { | ||
var arg structs.GenericRequest | ||
var out []string | ||
if err := msgpackrpc.CallWithCodec(codec, "Region.List", &arg, &out); err != nil { | ||
t.Fatalf("err: %v", err) | ||
} | ||
if len(out) != 2 || out[0] != "region1" || out[1] != "region2" { | ||
t.Fatalf("unexpected regions: %v", out) | ||
} | ||
return true, nil | ||
}, func(err error) { | ||
t.Fatalf("err: %v", err) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -134,6 +134,7 @@ type endpoints struct { | |
Eval *Eval | ||
Plan *Plan | ||
Alloc *Alloc | ||
Region *Region | ||
} | ||
|
||
// NewServer is used to construct a new Nomad server from the | ||
|
@@ -353,6 +354,7 @@ func (s *Server) setupRPC(tlsWrap tlsutil.DCWrapper) error { | |
s.endpoints.Eval = &Eval{s} | ||
s.endpoints.Plan = &Plan{s} | ||
s.endpoints.Alloc = &Alloc{s} | ||
s.endpoints.Region = &Region{s} | ||
|
||
// Register the handlers | ||
s.rpcServer.Register(s.endpoints.Status) | ||
|
@@ -361,6 +363,7 @@ func (s *Server) setupRPC(tlsWrap tlsutil.DCWrapper) error { | |
s.rpcServer.Register(s.endpoints.Eval) | ||
s.rpcServer.Register(s.endpoints.Plan) | ||
s.rpcServer.Register(s.endpoints.Alloc) | ||
s.rpcServer.Register(s.endpoints.Region) | ||
|
||
list, err := net.ListenTCP("tcp", s.config.RPCAddr) | ||
if err != nil { | ||
|
@@ -612,6 +615,18 @@ func (s *Server) State() *state.StateStore { | |
return s.fsm.State() | ||
} | ||
|
||
// Regions returns the known regions in the cluster. | ||
func (s *Server) Regions() []string { | ||
s.peerLock.RLock() | ||
defer s.peerLock.RUnlock() | ||
|
||
regions := make([]string, 0, len(s.peers)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if I have multiple servers per region. You probably want to make a map to keep track of unique regions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The peers map key is region ID, which would prevent having any duplicates. There might be multiple servers but they are still all nested under the same map key. |
||
for region, _ := range s.peers { | ||
regions = append(regions, region) | ||
} | ||
return regions | ||
} | ||
|
||
// inmemCodec is used to do an RPC call without going over a network | ||
type inmemCodec struct { | ||
method string | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ import ( | |
"sync/atomic" | ||
"testing" | ||
"time" | ||
|
||
"github.com/hashicorp/nomad/testutil" | ||
) | ||
|
||
var nextPort uint32 = 15000 | ||
|
@@ -86,3 +88,34 @@ func TestServer_RPC(t *testing.T) { | |
t.Fatalf("err: %v", err) | ||
} | ||
} | ||
|
||
func TestServer_Regions(t *testing.T) { | ||
// Make the servers | ||
s1 := testServer(t, func(c *Config) { | ||
c.Region = "region1" | ||
}) | ||
defer s1.Shutdown() | ||
|
||
s2 := testServer(t, func(c *Config) { | ||
c.Region = "region2" | ||
}) | ||
defer s2.Shutdown() | ||
|
||
// Join them together | ||
s2Addr := fmt.Sprintf("127.0.0.1:%d", | ||
s2.config.SerfConfig.MemberlistConfig.BindPort) | ||
if n, err := s1.Join([]string{s2Addr}); err != nil || n != 1 { | ||
t.Fatalf("Failed joining: %v (%d joined)", err, n) | ||
} | ||
|
||
// Try listing the regions | ||
testutil.WaitForResult(func() (bool, error) { | ||
out := s1.Regions() | ||
if len(out) != 2 || out[0] != "region1" || out[1] != "region2" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same |
||
return false, fmt.Errorf("unexpected regions: %v", out) | ||
} | ||
return true, nil | ||
}, func(err error) { | ||
t.Fatalf("err: %v", err) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
--- | ||
layout: "http" | ||
page_title: "HTTP API: /v1/regions" | ||
sidebar_current: "docs-http-regions" | ||
description: > | ||
The '/v1/regions' endpoint lists the known cluster regions. | ||
--- | ||
|
||
# /v1/regions | ||
|
||
## GET | ||
|
||
<dl> | ||
<dt>Description</dt> | ||
<dd> | ||
Returns the known region names. | ||
</dd> | ||
|
||
<dt>Method</dt> | ||
<dd>GET</dd> | ||
|
||
<dt>URL</dt> | ||
<dd>`/v1/regions`</dd> | ||
|
||
<dt>Parameters</dt> | ||
<dd> | ||
None | ||
</dd> | ||
|
||
<dt>Returns</dt> | ||
<dd> | ||
|
||
```javascript | ||
["region1","region2"] | ||
``` | ||
|
||
</dd> | ||
</dl> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this going to be flaky? Is there any guarantee on order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, tests didn't indicate anything but a quick
sort.Strings()
should fix this up.