Skip to content
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

Merged
merged 7 commits into from
Nov 24, 2015
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" {
Copy link
Contributor

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?

Copy link
Member Author

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.

t.Fatalf("unexpected regions: %v", out)
}
return true, nil
}, func(err error) {
t.Fatalf("err: %v", err)
})
}
15 changes: 15 additions & 0 deletions nomad/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The 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
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" {
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
})
}
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