Skip to content

Commit

Permalink
Merge pull request #495 from hashicorp/f-regions
Browse files Browse the repository at this point in the history
Add regions endpoint
  • Loading branch information
ryanuber committed Nov 24, 2015
2 parents 0bd0abc + a455c80 commit a96315f
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 0 deletions.
23 changes: 23 additions & 0 deletions api/regions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package api

import "sort"

// Regions is used to query the regions in the cluster.
type Regions struct {
client *Client
}

// Regions returns a handle on the allocs endpoints.
func (c *Client) Regions() *Regions {
return &Regions{client: c}
}

// List returns a list of all of the regions.
func (r *Regions) List() ([]string, error) {
var resp []string
if _, err := r.client.query("/v1/regions", &resp, nil); err != nil {
return nil, err
}
sort.Strings(resp)
return resp, nil
}
42 changes: 42 additions & 0 deletions api/regions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package api

import (
"fmt"
"testing"

"github.com/hashicorp/nomad/testutil"
)

func TestRegionsList(t *testing.T) {
c1, s1 := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.Region = "regionA"
})
defer s1.Stop()

c2, s2 := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.Region = "regionB"
})
defer s2.Stop()

// Join the servers
if _, err := c2.Agent().Join(s1.SerfAddr); err != nil {
t.Fatalf("err: %v", err)
}

// Regions returned and sorted
testutil.WaitForResult(func() (bool, error) {
regions, err := c1.Regions().List()
if err != nil {
return false, err
}
if n := len(regions); n != 2 {
return false, fmt.Errorf("expected 2 regions, got: %d", n)
}
if regions[0] != "regionA" || regions[1] != "regionB" {
return false, fmt.Errorf("bad: %#v", regions)
}
return true, nil
}, func(err error) {
t.Fatalf("err: %v", err)
})
}
2 changes: 2 additions & 0 deletions command/agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
s.mux.HandleFunc("/v1/agent/force-leave", s.wrap(s.AgentForceLeaveRequest))
s.mux.HandleFunc("/v1/agent/servers", s.wrap(s.AgentServersRequest))

s.mux.HandleFunc("/v1/regions", s.wrap(s.RegionListRequest))

s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest))
s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest))

Expand Down
24 changes: 24 additions & 0 deletions command/agent/region_endpoint.go
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, &regions); err != nil {
return nil, err
}
return regions, nil
}
29 changes: 29 additions & 0 deletions command/agent/region_endpoint_test.go
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)
}
})
}
16 changes: 16 additions & 0 deletions nomad/regions_endpoint.go
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
}
46 changes: 46 additions & 0 deletions nomad/regions_endpoint_test.go
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)
})
}
17 changes: 17 additions & 0 deletions nomad/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -134,6 +135,7 @@ type endpoints struct {
Eval *Eval
Plan *Plan
Alloc *Alloc
Region *Region
}

// NewServer is used to construct a new Nomad server from the
Expand Down Expand Up @@ -353,6 +355,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)
Expand All @@ -361,6 +364,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 {
Expand Down Expand Up @@ -612,6 +616,19 @@ 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))
for region, _ := range s.peers {
regions = append(regions, region)
}
sort.Strings(regions)
return regions
}

// inmemCodec is used to do an RPC call without going over a network
type inmemCodec struct {
method string
Expand Down
33 changes: 33 additions & 0 deletions nomad/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sync/atomic"
"testing"
"time"

"github.com/hashicorp/nomad/testutil"
)

var nextPort uint32 = 15000
Expand Down Expand Up @@ -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" {
return false, fmt.Errorf("unexpected regions: %v", out)
}
return true, nil
}, func(err error) {
t.Fatalf("err: %v", err)
})
}
38 changes: 38 additions & 0 deletions website/source/docs/http/regions.html.md
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>
4 changes: 4 additions & 0 deletions website/source/layouts/http.erb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@
</ul>
</li>

<li<%= sidebar_current("docs-http-regions") %>>
<a href="/docs/http/regions.html">Regions</a>
</li>

<li<%= sidebar_current("docs-http-status") %>>
<a href="/docs/http/status.html">Status</a>
</li>
Expand Down

0 comments on commit a96315f

Please sign in to comment.