Repository: coreos/fleet
Pull request #1265 (2015-06-24 18:03:50)

Add flag to override tokenLimit
Make tokenLimit in api public and add a flag to fleet.conf (token_limit)
to override the built-in token limit of 100.

Note: this PR is rather *hackish*, it would be nice to know if something like will be considered for merging (if cleaned up, etc .etc.). IOW: comments welcome!

bcwaldon: @miekg I'm on board with this change, but It would be cleaner to pass token limit through NewServeMux and hand it to the paginated resources directly rather than pulling it out of a package-level variable. 
miekg: [ Quoting <notifications@github.com> in "Re: [fleet] Add flag to override to..." ]
>@miekg I'm on board with this change, but It would be cleaner to pass token limit through NewServeMux and hand it to the paginated resources directly rather than pulling it out of a package-level variable.

Ack and ack. I hoping I have some time tomorrow to work on this.

/Miek

--
Miek Gieben

miekg: Friendly ping @bcwaldon 
mwitkow-io: @mischief, can you please take at this PR reducing the size of tokenLimits. We use Fleet API from our services and the current 100 limit causes excessive load on etcd (each page is an etcd re-query).
wuqixuan: LGTM
miekg: @wuqixuan can you merge? Thanks.
wuqixuan: @miekg , I have no permission to merge currently. Need @jonboulle @mischief  help. 
miekg: Ack, and I made that little change.
jonboulle: @miekg could you please rebase + squash your commits into a single one (perhaps with the commit message from https://github.com/miekg/fleet/commit/3d3421d8b1f0080f41869988f18dc6a49b37a45a), then I will merge. thanks!

Patch:
diff --git a/.travis.yml b/.travis.yml
index 8c44610c..cc33ebfd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,15 @@
 language: go
 matrix:
   include:
-    - go: 1.4.2
-      install:
-        - go get golang.org/x/tools/cmd/cover
-        - go get golang.org/x/tools/cmd/vet
-    - go: 1.5.1
+    - go: 1.4
+      env: TOOLS_CMD=golang.org/x/tools/cmd
+
+install:
+ - go get ${TOOLS_CMD}/cover
+ - go get ${TOOLS_CMD}/vet
 
 script:
  - ./test
 
 notifications:
   email: false
-
-sudo: false
diff --git a/Documentation/api-v1.md b/Documentation/api-v1.md
index ce9a6605..996acdfd 100644
--- a/Documentation/api-v1.md
+++ b/Documentation/api-v1.md
@@ -32,7 +32,7 @@ The options and desiredState fields are required, and all other Unit fields will
 The base request looks like this:
 
 ```
-PUT /fleet/v1/units/<name> HTTP/1.1
+PUT /units/<name> HTTP/1.1
 
 {"desiredState": <state>, "options": [<option>, ...]}
 ```
@@ -40,7 +40,7 @@ PUT /fleet/v1/units/<name> HTTP/1.1
 For example, creating and launching a new unit "foo.service" could be done like so:
 
 ```
-PUT /fleet/v1/units/foo.service HTTP/1.1
+PUT /units/foo.service HTTP/1.1
 
 {
   "desiredState": "launched",
@@ -68,7 +68,7 @@ Modify the desired state of an existing Unit by providing a partial entity.
 The only required field is desiredState:
 
 ```
-PUT /fleet/v1/units/<name> HTTP/1.1
+PUT /units/<name> HTTP/1.1
 
 {"desiredState": <state>}
 ```
@@ -76,7 +76,7 @@ PUT /fleet/v1/units/<name> HTTP/1.1
 For example, unloading an existing Unit called "bar.service" could look like this:
 
 ```
-PUT /fleet/v1/units/bar.service HTTP/1.1
+PUT /units/bar.service HTTP/1.1
 
 {
   "desiredState": "inactive"
@@ -98,7 +98,7 @@ Explore a paginated collection of Unit entities.
 #### Request
 
 ```
-GET /fleet/v1/units HTTP/1.1
+GET /units HTTP/1.1
 ```
 
 The request must not have a body.
@@ -114,7 +114,7 @@ View a particular Unit entity.
 #### Request
 
 ```
-GET /fleet/v1/units/<name> HTTP/1.1
+GET /units/<name> HTTP/1.1
 ```
 
 The request must not have a body.
@@ -132,7 +132,7 @@ Completely remove a Unit from fleet.
 #### Request
 
 ```
-DELETE /fleet/v1/units/<name> HTTP/1.1
+DELETE /units/<name> HTTP/1.1
 ```
 
 #### Response
@@ -162,7 +162,7 @@ Explore a paginated collection of UnitState entities.
 #### Request
 
 ```
-GET /fleet/v1/state HTTP/1.1
+GET /state HTTP/1.1
 ```
 
 The request must not have a body.
@@ -195,7 +195,7 @@ Explore a paginated collection of Machine entities.
 #### Request
 
 ```
-GET /fleet/v1/machines HTTP/1.1
+GET /machines HTTP/1.1
 ```
 
 The request must not have a body.
@@ -232,7 +232,7 @@ If a paginated response does not contain a `nextPageToken` field, a client may s
 The following series of HTTP request/response pairs demonstrates how pagination works against a fictional resource:
 
 ```
-GET /fleet/v1/cats HTTP/1.1
+GET /cats HTTP/1.1
 
 
 HTTP/1.1 200 OK
@@ -241,7 +241,7 @@ HTTP/1.1 200 OK
 ```
 
 ```
-GET /fleet/v1/cats?nextPageToken=8fefec2c HTTP/1.1
+GET /cats?nextPageToken=8fefec2c HTTP/1.1
 
 
 HTTP/1.1 200 OK
@@ -250,7 +250,7 @@ HTTP/1.1 200 OK
 ```
 
 ```
-GET /fleet/v1/cats?nextPageToken=cbb06916 HTTP/1.1
+GET /cats?nextPageToken=cbb06916 HTTP/1.1
 
 
 HTTP/1.1 200 OK
diff --git a/Documentation/deployment-and-configuration.md b/Documentation/deployment-and-configuration.md
index fb9ee8e3..2e6fd535 100644
--- a/Documentation/deployment-and-configuration.md
+++ b/Documentation/deployment-and-configuration.md
@@ -103,12 +103,6 @@ Provide TLS configuration when SSL certificate authentication is enabled in etcd
 
 Default: ""
 
-#### etcd_key_prefix
-
-Keyspace path for fleet data in etcd.
-
-Default: "/_coreos.com/fleet/"
-
 #### public_ip
 
 IP address that should be published with the local Machine's state and any socket information.
@@ -121,24 +115,6 @@ Default: ""
 Comma-delimited key/value pairs that are published with the local to the fleet registry. This data can be used directly by a client of fleet to make scheduling decisions. An example set of metadata could look like:  
 
 	metadata="region=us-west,az=us-west-1"
-	metadata='region=us-west,az=us-west-1'
-	metadata=region=us-west,az=us-west-1
-
-The value of the metadata option should conform to one of these three forms:
-    
-	metadata="STRING"
-	metadata='STRING'
-	metadata=STRING
-
-...while STRING is one of:
-
-	yyy[,yyy[,yyy...]]
-
-...and yyy is one of:
-
-	key=value
-
-Space and tab characters will be stripped around the equals sign and around each comma. If the same key is defined more than once, the last value overwrites the previous value(s).
 
 Default: ""
 
diff --git a/Documentation/fleet-scaling.md b/Documentation/fleet-scaling.md
deleted file mode 100644
index 1738c87e..00000000
--- a/Documentation/fleet-scaling.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# fleet and scaling
-
-As fleet uses etcd for cluster wide coordination, making it scale requires
-minimizing the load it puts on etcd. This is true for reads, writes, and
-watches.
-
-## Known issues
-Currently when fleet schedules a job *all* `fleetd`s are woken up (via a watch)
-and then do a recursive GET on the Unit file in etcd to figure out if it should
-schedule a job. This is a very expensive operation.
-
-## Improvements
-
-To make fleet scale further in the future we should consider rearchitecting
-fleet to provide per-machine schedules (so that only those `fleetd`s are woken
-up that actually have work for them). This is akin to the [Thundering herd
-problem](https://en.wikipedia.org/wiki/Thundering_herd_problem), but in a
-distributed fashion. Once such a change is in we can also drop the periodic
-wakeups (agent TTLs) that cause fleet wide wake ups on a regular clock.
-
-## Quick wins
-
-The above proposed change is a large architectural change. However, scaling
-fleet to a couple of thousand machine is already possible with a few quick
-wins:
-
-* Removing watches from fleet: By removing the watches from fleet we stop
-    the entire cluster from walking up whenever a new job is to be scheduled.
-    The downside of this change is that fleet's responsiveness is lower.
-    Proposal: https://github.com/coreos/fleet/pull/1264
-
-* Disallowing (some) nodes to partake in the fleet leadership election. Again
-    this is an expensive operation. The fewer nodes that are engaged in this
-    election, the better. Possible downside is that if there isn't a leader at
-    all, the cluster is inoperable. However the (usually) 5 machines running
-    etcd are also a single point of failure. Proposal:
-    https://github.com/coreos/fleet/pull/1263
-
-* Making some defaults exported and allow them to be overridden. For instance
-    fleet's tokenLimit controls how many Units are listed per "page". Proposal:
-    https://github.com/coreos/fleet/pull/1265
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 619efbdc..fa2e1679 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -1,6 +1,6 @@
 {
 	"ImportPath": "github.com/coreos/fleet",
-	"GoVersion": "go1.4.2",
+	"GoVersion": "go1.3.3",
 	"Packages": [
 		"./..."
 	],
@@ -12,18 +12,13 @@
 		},
 		{
 			"ImportPath": "github.com/coreos/etcd/client",
-			"Comment": "v2.2.0-alpha.1-138-gb2d33e6",
-			"Rev": "b2d33e6dcb24cddc0395f1bbe2213a27aef0fb46"
-		},
-		{
-			"ImportPath": "github.com/coreos/etcd/pkg/pathutil",
-			"Comment": "v2.2.0-alpha.1-138-gb2d33e6",
-			"Rev": "b2d33e6dcb24cddc0395f1bbe2213a27aef0fb46"
+			"Comment": "v2.1.1-31-g93002ca",
+			"Rev": "93002caca591fb51e0796813903c4fc1239f3ce7"
 		},
 		{
 			"ImportPath": "github.com/coreos/etcd/pkg/types",
-			"Comment": "v2.2.0-alpha.1-138-gb2d33e6",
-			"Rev": "b2d33e6dcb24cddc0395f1bbe2213a27aef0fb46"
+			"Comment": "v2.1.1-31-g93002ca",
+			"Rev": "93002caca591fb51e0796813903c4fc1239f3ce7"
 		},
 		{
 			"ImportPath": "github.com/coreos/go-semver/semver",
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/README.md b/Godeps/_workspace/src/github.com/coreos/etcd/client/README.md
deleted file mode 100644
index 672b8c13..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# etcd/client
-
-etcd/client is the Go client library for etcd.
-
-[![GoDoc](https://godoc.org/github.com/coreos/etcd/client?status.png)](https://godoc.org/github.com/coreos/etcd/client)
-
-## Install
-
-```bash
-go get github.com/coreos/etcd/client
-```
-
-## Usage
-
-```go
-package main
-
-import (
-	"log"
-	"time"
-
-	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/client"
-)
-
-func main() {
-	cfg := client.Config{
-		Endpoints:               []string{"http://127.0.0.1:2379"},
-		Transport:               client.DefaultTransport,
-		// set timeout per request to fail fast when the target endpoint is unavailable
-		HeaderTimeoutPerRequest: time.Second,
-	}
-	c, err := client.New(cfg)
-	if err != nil {
-		log.Fatal(err)
-	}
-	kapi := client.NewKeysAPI(c)
-	resp, err := kapi.Set(context.Background(), "foo", "bar", nil)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-```
-
-## Error Handling
-
-etcd client might return three types of errors.
-
-- context error
-
-Each API call has its first parameter as `context`. A context can be canceled or have an attached deadline. If the context is canceled or reaches its deadline, the responding context error will be returned no matter what internal errors the API call has already encountered.
-
-- cluster error
-
-Each API call tries to send request to the cluster endpoints one by one until it successfully gets a response. If a requests to an endpoint fails, due to exceeding per request timeout or connection issues, the error will be added into a list of errors. If all possible endpoints fail, a cluster error that includes all encountered errors will be returned.
-
-- response error
-
-If the response gets from the cluster is invalid, a plain string error will be returned. For example, it might be a invalid JSON error.
-
-Here is the example code to handle client errors:
-
-```go
-cfg := client.Config{Endpoints: []string{"http://etcd1:2379,http://etcd2:2379,http://etcd3:2379"}}
-c, err := client.New(cfg)
-if err != nil {
-	log.Fatal(err)
-}
-
-kapi := client.NewKeysAPI(c)
-resp, err := kapi.Set(ctx, "test", "bar", nil)
-if err != nil {
-	if err == context.Canceled {
-		// ctx is canceled by another routine
-	} else if err == context.DeadlineExceeded {
-		// ctx is attached with a deadline and it exceeded
-	} else if cerr, ok := err.(*client.ClusterError); ok {
-		// process (cerr.Errors)
-	} else {
-		// bad cluster endpoints, which are not etcd servers
-	}
-}
-```
-
-
-## Caveat
-
-1. etcd/client prefers to use the same endpoint as long as the endpoint continues to work well. This saves socket resources, and improves efficiency for both client and server side. This preference doesn't remove consistency from the data consumed by the client because data replicated to each etcd member has already passed through the consensus process.
-
-2. etcd/client does round-robin rotation on other available endpoints if the preferred endpoint isn't functioning properly. For example, if the member that etcd/client connects to is hard killed, etcd/client will fail on the first attempt with the killed member, and succeed on the second attempt with another member. If it fails to talk to all available endpoints, it will return all errors happened.
-
-3. Default etcd/client cannot handle the case that the remote server is SIGSTOPed now. TCP keepalive mechanism doesn't help in this scenario because operating system may still send TCP keep-alive packets. Over time we'd like to improve this functionality, but solving this issue isn't high priority because a real-life case in which a server is stopped, but the connection is kept alive, hasn't been brought to our attention.
-
-4. etcd/client cannot detect whether the member in use is healthy when doing read requests. If the member is isolated from the cluster, etcd/client may retrieve outdated data. As a workaround, users could monitor experimental /health endpoint for member healthy information. We are improving it at [#3265](https://github.com/coreos/etcd/issues/3265).
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/cancelreq.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/cancelreq.go
deleted file mode 100644
index fefdb40e..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/cancelreq.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// borrowed from golang/net/context/ctxhttp/cancelreq.go
-
-// +build go1.5
-
-package client
-
-import "net/http"
-
-func requestCanceler(tr CancelableTransport, req *http.Request) func() {
-	ch := make(chan struct{})
-	req.Cancel = ch
-
-	return func() {
-		close(ch)
-	}
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/cancelreq_go14.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/cancelreq_go14.go
deleted file mode 100644
index 2bed38a4..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/cancelreq_go14.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// borrowed from golang/net/context/ctxhttp/cancelreq_go14.go
-
-// +build !go1.5
-
-package client
-
-import "net/http"
-
-func requestCanceler(tr CancelableTransport, req *http.Request) func() {
-	return func() {
-		tr.CancelRequest(req)
-	}
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/client.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/client.go
index 9e4871a3..05968714 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/client.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/client/client.go
@@ -18,12 +18,9 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
-	"math/rand"
 	"net"
 	"net/http"
 	"net/url"
-	"reflect"
-	"sort"
 	"sync"
 	"time"
 
@@ -33,7 +30,6 @@ import (
 var (
 	ErrNoEndpoints           = errors.New("client: no endpoints available")
 	ErrTooManyRedirects      = errors.New("client: too many redirects")
-	ErrClusterUnavailable    = errors.New("client: etcd cluster is unavailable or misconfigured")
 	errTooManyRedirectChecks = errors.New("client: too many redirect checks")
 )
 
@@ -87,23 +83,6 @@ type Config struct {
 	// Password is the password for the specified user to add as an authorization header
 	// to the request.
 	Password string
-
-	// HeaderTimeoutPerRequest specifies the time limit to wait for response
-	// header in a single request made by the Client. The timeout includes
-	// connection time, any redirects, and header wait time.
-	//
-	// For non-watch GET request, server returns the response body immediately.
-	// For PUT/POST/DELETE request, server will attempt to commit request
-	// before responding, which is expected to take `100ms + 2 * RTT`.
-	// For watch request, server returns the header immediately to notify Client
-	// watch start. But if server is behind some kind of proxy, the response
-	// header may be cached at proxy, and Client cannot rely on this behavior.
-	//
-	// One API call may send multiple requests to different etcd servers until it
-	// succeeds. Use context of the API to specify the overall timeout.
-	//
-	// A HeaderTimeoutPerRequest of zero means no timeout.
-	HeaderTimeoutPerRequest time.Duration
 }
 
 func (cfg *Config) transport() CancelableTransport {
@@ -141,22 +120,6 @@ type Client interface {
 	// Sync updates the internal cache of the etcd cluster's membership.
 	Sync(context.Context) error
 
-	// AutoSync periodically calls Sync() every given interval.
-	// The recommended sync interval is 10 seconds to 1 minute, which does
-	// not bring too much overhead to server and makes client catch up the
-	// cluster change in time.
-	//
-	// The example to use it:
-	//
-	//  for {
-	//      err := client.AutoSync(ctx, 10*time.Second)
-	//      if err == context.DeadlineExceeded || err == context.Canceled {
-	//          break
-	//      }
-	//      log.Print(err)
-	//  }
-	AutoSync(context.Context, time.Duration) error
-
 	// Endpoints returns a copy of the current set of API endpoints used
 	// by Client to resolve HTTP requests. If Sync has ever been called,
 	// this may differ from the initial Endpoints provided in the Config.
@@ -167,8 +130,7 @@ type Client interface {
 
 func New(cfg Config) (Client, error) {
 	c := &httpClusterClient{
-		clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
-		rand:          rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
+		clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect()),
 	}
 	if cfg.Username != "" {
 		c.credentials = &credentials{
@@ -186,14 +148,13 @@ type httpClient interface {
 	Do(context.Context, httpAction) (*http.Response, []byte, error)
 }
 
-func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
+func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc) httpClientFactory {
 	return func(ep url.URL) httpClient {
 		return &redirectFollowingHTTPClient{
 			checkRedirect: cr,
 			client: &simpleHTTPClient{
-				transport:     tr,
-				endpoint:      ep,
-				headerTimeout: headerTimeout,
+				transport: tr,
+				endpoint:  ep,
 			},
 		}
 	}
@@ -213,10 +174,8 @@ type httpAction interface {
 type httpClusterClient struct {
 	clientFactory httpClientFactory
 	endpoints     []url.URL
-	pinned        int
 	credentials   *credentials
 	sync.RWMutex
-	rand *rand.Rand
 }
 
 func (c *httpClusterClient) reset(eps []string) error {
@@ -233,9 +192,7 @@ func (c *httpClusterClient) reset(eps []string) error {
 		neps[i] = *u
 	}
 
-	c.endpoints = shuffleEndpoints(c.rand, neps)
-	// TODO: pin old endpoint if possible, and rebalance when new endpoint appears
-	c.pinned = 0
+	c.endpoints = neps
 
 	return nil
 }
@@ -246,7 +203,6 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
 	leps := len(c.endpoints)
 	eps := make([]url.URL, leps)
 	n := copy(eps, c.endpoints)
-	pinned := c.pinned
 
 	if c.credentials != nil {
 		action = &authedAction{
@@ -267,39 +223,23 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
 	var resp *http.Response
 	var body []byte
 	var err error
-	cerr := &ClusterError{}
 
-	for i := pinned; i < leps+pinned; i++ {
-		k := i % leps
-		hc := c.clientFactory(eps[k])
+	for _, ep := range eps {
+		hc := c.clientFactory(ep)
 		resp, body, err = hc.Do(ctx, action)
 		if err != nil {
-			cerr.Errors = append(cerr.Errors, err)
-			// mask previous errors with context error, which is controlled by user
-			if err == context.Canceled || err == context.DeadlineExceeded {
+			if err == context.DeadlineExceeded || err == context.Canceled {
 				return nil, nil, err
 			}
 			continue
 		}
 		if resp.StatusCode/100 == 5 {
-			switch resp.StatusCode {
-			case http.StatusInternalServerError, http.StatusServiceUnavailable:
-				// TODO: make sure this is a no leader response
-				cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s has no leader", eps[k].String()))
-			default:
-				cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
-			}
 			continue
 		}
-		if k != pinned {
-			c.Lock()
-			c.pinned = k
-			c.Unlock()
-		}
-		return resp, body, nil
+		break
 	}
 
-	return nil, nil, cerr
+	return resp, body, err
 }
 
 func (c *httpClusterClient) Endpoints() []string {
@@ -328,47 +268,18 @@ func (c *httpClusterClient) Sync(ctx context.Context) error {
 	for _, m := range ms {
 		eps = append(eps, m.ClientURLs...)
 	}
-	sort.Sort(sort.StringSlice(eps))
-
-	ceps := make([]string, len(c.endpoints))
-	for i, cep := range c.endpoints {
-		ceps[i] = cep.String()
-	}
-	sort.Sort(sort.StringSlice(ceps))
-	// fast path if no change happens
-	// this helps client to pin the endpoint when no cluster change
-	if reflect.DeepEqual(eps, ceps) {
-		return nil
-	}
 
 	return c.reset(eps)
 }
 
-func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
-	ticker := time.NewTicker(interval)
-	defer ticker.Stop()
-	for {
-		err := c.Sync(ctx)
-		if err != nil {
-			return err
-		}
-		select {
-		case <-ctx.Done():
-			return ctx.Err()
-		case <-ticker.C:
-		}
-	}
-}
-
 type roundTripResponse struct {
 	resp *http.Response
 	err  error
 }
 
 type simpleHTTPClient struct {
-	transport     CancelableTransport
-	endpoint      url.URL
-	headerTimeout time.Duration
+	transport CancelableTransport
+	endpoint  url.URL
 }
 
 func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
@@ -378,14 +289,6 @@ func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Respon
 		return nil, nil, err
 	}
 
-	hctx, hcancel := context.WithCancel(ctx)
-	if c.headerTimeout > 0 {
-		hctx, hcancel = context.WithTimeout(ctx, c.headerTimeout)
-	}
-	defer hcancel()
-
-	reqcancel := requestCanceler(c.transport, req)
-
 	rtchan := make(chan roundTripResponse, 1)
 	go func() {
 		resp, err := c.transport.RoundTrip(req)
@@ -399,19 +302,12 @@ func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Respon
 	select {
 	case rtresp := <-rtchan:
 		resp, err = rtresp.resp, rtresp.err
-	case <-hctx.Done():
+	case <-ctx.Done():
 		// cancel and wait for request to actually exit before continuing
-		reqcancel()
+		c.transport.CancelRequest(req)
 		rtresp := <-rtchan
 		resp = rtresp.resp
-		switch {
-		case ctx.Err() != nil:
-			err = ctx.Err()
-		case hctx.Err() != nil:
-			err = fmt.Errorf("client: endpoint %s exceeded header timeout", c.endpoint.String())
-		default:
-			panic("failed to get error from context")
-		}
+		err = ctx.Err()
 	}
 
 	// always check for resp nil-ness to deal with possible
@@ -435,9 +331,11 @@ func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Respon
 
 	select {
 	case <-ctx.Done():
-		resp.Body.Close()
+		err = resp.Body.Close()
 		<-done
-		return nil, nil, ctx.Err()
+		if err == nil {
+			err = ctx.Err()
+		}
 	case <-done:
 	}
 
@@ -503,12 +401,3 @@ func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request {
 	orig.URL = &r.location
 	return orig
 }
-
-func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL {
-	p := r.Perm(len(eps))
-	neps := make([]url.URL, len(eps))
-	for i, k := range p {
-		neps[i] = eps[k]
-	}
-	return neps
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/cluster_error.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/cluster_error.go
deleted file mode 100644
index 957ed462..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/cluster_error.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2015 CoreOS, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package client
-
-import "fmt"
-
-type ClusterError struct {
-	Errors []error
-}
-
-func (ce *ClusterError) Error() string {
-	return ErrClusterUnavailable.Error()
-}
-
-func (ce *ClusterError) Detail() string {
-	s := ""
-	for i, e := range ce.Errors {
-		s += fmt.Sprintf("error #%d: %s\n", i, e)
-	}
-	return s
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/keys.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/keys.go
index 05cfa922..e1f5326d 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/keys.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/client/keys.go
@@ -20,23 +20,22 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
+	"path"
 	"strconv"
 	"strings"
 	"time"
 
-	"github.com/coreos/fleet/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil"
 	"github.com/coreos/fleet/Godeps/_workspace/src/golang.org/x/net/context"
 )
 
 const (
-	ErrorCodeKeyNotFound  = 100
-	ErrorCodeTestFailed   = 101
-	ErrorCodeNotFile      = 102
-	ErrorCodeNotDir       = 104
-	ErrorCodeNodeExist    = 105
-	ErrorCodeRootROnly    = 107
-	ErrorCodeDirNotEmpty  = 108
-	ErrorCodeUnauthorized = 110
+	ErrorCodeKeyNotFound = 100
+	ErrorCodeTestFailed  = 101
+	ErrorCodeNotFile     = 102
+	ErrorCodeNotDir      = 104
+	ErrorCodeNodeExist   = 105
+	ErrorCodeRootROnly   = 107
+	ErrorCodeDirNotEmpty = 108
 
 	ErrorCodePrevValueRequired = 201
 	ErrorCodeTTLNaN            = 202
@@ -64,7 +63,6 @@ func (e Error) Error() string {
 
 var (
 	ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint.")
-	ErrEmptyBody   = errors.New("client: response body is empty")
 )
 
 // PrevExistType is used to define an existence condition when setting
@@ -137,7 +135,7 @@ type WatcherOptions struct {
 	// index, whatever that may be.
 	AfterIndex uint64
 
-	// Recursive specifies whether or not the Watcher should emit
+	// Recursive specifices whether or not the Watcher should emit
 	// events that occur in children of the given keyspace. If set
 	// to false (default), events will be limited to those that
 	// occur for the exact key.
@@ -253,7 +251,7 @@ type Response struct {
 	Node *Node `json:"node"`
 
 	// PrevNode represents the previous state of the Node. PrevNode is non-nil
-	// only if the Node existed before the action occurred and the action
+	// only if the Node existed before the action occured and the action
 	// caused a change to the Node.
 	PrevNode *Node `json:"prevNode"`
 
@@ -421,23 +419,18 @@ type httpWatcher struct {
 }
 
 func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
-	for {
-		httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
-		if err != nil {
-			return nil, err
-		}
-
-		resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
-		if err != nil {
-			if err == ErrEmptyBody {
-				continue
-			}
-			return nil, err
-		}
+	httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
+	if err != nil {
+		return nil, err
+	}
 
-		hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
-		return resp, nil
+	resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
+	if err != nil {
+		return nil, err
 	}
+
+	hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
+	return resp, nil
 }
 
 // v2KeysURL forms a URL representing the location of a key.
@@ -446,16 +439,7 @@ func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
 // provided endpoint's path to the root of the keys API
 // (typically "/v2/keys").
 func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
-	// We concatenate all parts together manually. We cannot use
-	// path.Join because it does not reserve trailing slash.
-	// We call CanonicalURLPath to further cleanup the path.
-	if prefix != "" && prefix[0] != '/' {
-		prefix = "/" + prefix
-	}
-	if key != "" && key[0] != '/' {
-		key = "/" + key
-	}
-	ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key)
+	ep.Path = path.Join(ep.Path, prefix, key)
 	return &ep
 }
 
@@ -606,9 +590,6 @@ func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
 func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
 	switch code {
 	case http.StatusOK, http.StatusCreated:
-		if len(body) == 0 {
-			return nil, ErrEmptyBody
-		}
 		res, err = unmarshalSuccessfulKeysResponse(header, body)
 	default:
 		err = unmarshalFailedKeysResponse(body)
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path.go
deleted file mode 100644
index 82fd1db3..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package pathutil
-
-import "path"
-
-// CanonicalURLPath returns the canonical url path for p, which follows the rules:
-// 1. the path always starts with "/"
-// 2. replace multiple slashes with a single slash
-// 3. replace each '.' '..' path name element with equivalent one
-// 4. keep the trailing slash
-// The function is borrowed from stdlib http.cleanPath in server.go.
-func CanonicalURLPath(p string) string {
-	if p == "" {
-		return "/"
-	}
-	if p[0] != '/' {
-		p = "/" + p
-	}
-	np := path.Clean(p)
-	// path.Clean removes trailing slash except for root,
-	// put the trailing slash back if necessary.
-	if p[len(p)-1] == '/' && np != "/" {
-		np += "/"
-	}
-	return np
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go
index bb997174..32287522 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go
@@ -91,7 +91,7 @@ func (us *unsafeSet) Length() int {
 // Values returns the values of the Set in an unspecified order.
 func (us *unsafeSet) Values() (values []string) {
 	values = make([]string, 0)
-	for val := range us.d {
+	for val, _ := range us.d {
 		values = append(values, val)
 	}
 	return
@@ -100,7 +100,7 @@ func (us *unsafeSet) Values() (values []string) {
 // Copy creates a new Set containing the values of the first
 func (us *unsafeSet) Copy() Set {
 	cp := NewUnsafeSet()
-	for val := range us.d {
+	for val, _ := range us.d {
 		cp.Add(val)
 	}
 
diff --git a/README.md b/README.md
index 4ec0fdd8..bd559397 100644
--- a/README.md
+++ b/README.md
@@ -2,18 +2,9 @@
 
 [![Build Status](https://travis-ci.org/coreos/fleet.png?branch=master)](https://travis-ci.org/coreos/fleet)
 
-fleet ties together [systemd](http://coreos.com/using-coreos/systemd) and [etcd](https://github.com/coreos/etcd) into a simple distributed init system. Think of it as an extension of systemd that operates at the cluster level instead of the machine level.
+fleet ties together [systemd](http://coreos.com/using-coreos/systemd) and [etcd](https://github.com/coreos/etcd) into a simple distributed init system. Think of it as an extension of systemd that operates at the cluster level instead of the machine level. **This project is very low level and is designed as a foundation for higher order orchestration.**
 
-**This project is very low level and is designed as a foundation for higher order orchestration.** fleet is oriented around systemd units and is not a container manager or orchestration system. fleet supports very basic scheduling of systemd units in a cluster. Those looking for more complex scheduling requirements or a first-class container orchestration system should check out [Kubernetes](http://kubernetes.io).
-
-## Current status
-
-fleet has seen production use for some time and is largely considered stable. 
-However, there are known [scalability limitations](Documentation/fleet-scaling.md) with its architecture.
-As such, it is not recommended to run fleet clusters larger than 100 nodes or with more than 1000 services.
-The fleet project is being maintained for bug fixes but the existing maintainers do not intend to add additional major features or significantly rework fleet to address these limitations.
-
-If you are a developer and wish to help maintain fleet and improve its scalability, please email the [maintainers](MAINTAINERS).
+fleet is oriented around systemd units and is not a container manager or orchestration system. fleet supports very basic scheduling of systemd units in a cluster. Those looking for more complex scheduling requirements or a first-class container orchestration system should check out [Kubernetes](http://kubernetes.io).
 
 ## Using fleet
 
@@ -43,8 +34,6 @@ hello.service   113f16a7.../172.17.8.103   active   running
 
 ## Supported Deployment Patterns
 
-fleet is not intended to be an all-purpose orchestration system, and as such supports only a few simple deployment patterns:
-
 * Deploy a single unit anywhere on the cluster
 * Deploy a unit globally everywhere in the cluster
 * Automatic rescheduling of units on machine failure
diff --git a/api/machines.go b/api/machines.go
index 990d7d80..96087060 100644
--- a/api/machines.go
+++ b/api/machines.go
@@ -25,14 +25,15 @@ import (
 	"github.com/coreos/fleet/schema"
 )
 
-func wireUpMachinesResource(mux *http.ServeMux, prefix string, cAPI client.API) {
+func wireUpMachinesResource(mux *http.ServeMux, prefix string, tokenLimit int, cAPI client.API) {
 	res := path.Join(prefix, "machines")
-	mr := machinesResource{cAPI}
+	mr := machinesResource{cAPI, uint16(tokenLimit)}
 	mux.Handle(res, &mr)
 }
 
 type machinesResource struct {
-	cAPI client.API
+	cAPI       client.API
+	tokenLimit uint16
 }
 
 func (mr *machinesResource) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@@ -41,14 +42,14 @@ func (mr *machinesResource) ServeHTTP(rw http.ResponseWriter, req *http.Request)
 		return
 	}
 
-	token, err := findNextPageToken(req.URL)
+	token, err := findNextPageToken(req.URL, mr.tokenLimit)
 	if err != nil {
 		sendError(rw, http.StatusBadRequest, err)
 		return
 	}
 
 	if token == nil {
-		def := DefaultPageToken()
+		def := DefaultPageToken(mr.tokenLimit)
 		token = &def
 	}
 
diff --git a/api/mux.go b/api/mux.go
index 659f251a..74e9d68b 100644
--- a/api/mux.go
+++ b/api/mux.go
@@ -23,15 +23,16 @@ import (
 	"github.com/coreos/fleet/version"
 )
 
-func NewServeMux(reg registry.Registry) http.Handler {
+func NewServeMux(reg registry.Registry, tokenLimit int) http.Handler {
 	sm := http.NewServeMux()
 	cAPI := &client.RegistryClient{Registry: reg}
 
 	for _, prefix := range []string{"/v1-alpha", "/fleet/v1"} {
 		wireUpDiscoveryResource(sm, prefix)
-		wireUpMachinesResource(sm, prefix, cAPI)
-		wireUpStateResource(sm, prefix, cAPI)
-		wireUpUnitsResource(sm, prefix, cAPI)
+
+		wireUpMachinesResource(sm, prefix, tokenLimit, cAPI)
+		wireUpStateResource(sm, prefix, tokenLimit, cAPI)
+		wireUpUnitsResource(sm, prefix, tokenLimit, cAPI)
 		sm.HandleFunc(prefix, methodNotAllowedHandler)
 	}
 
diff --git a/api/pagination.go b/api/pagination.go
index 111af990..3bd0baa4 100644
--- a/api/pagination.go
+++ b/api/pagination.go
@@ -23,18 +23,13 @@ import (
 	"net/url"
 )
 
-const (
-	// Support a single value for PageToken.Limit to make life easy
-	tokenLimit = 100
-)
-
 type PageToken struct {
 	Limit uint16
 	Page  uint16
 }
 
-func DefaultPageToken() PageToken {
-	return PageToken{Limit: tokenLimit, Page: 1}
+func DefaultPageToken(limit uint16) PageToken {
+	return PageToken{Limit: limit, Page: 1}
 }
 
 func (tok PageToken) Next() PageToken {
@@ -63,7 +58,7 @@ func decodePageToken(value string) (*PageToken, error) {
 	return &tok, nil
 }
 
-func findNextPageToken(u *url.URL) (*PageToken, error) {
+func findNextPageToken(u *url.URL, limit uint16) (*PageToken, error) {
 	values := u.Query()["nextPageToken"]
 
 	if len(values) > 1 {
@@ -80,7 +75,7 @@ func findNextPageToken(u *url.URL) (*PageToken, error) {
 		return nil, err
 	}
 
-	err = validatePageToken(tok)
+	err = validatePageToken(tok, limit)
 	if err != nil {
 		return nil, err
 	}
@@ -88,9 +83,9 @@ func findNextPageToken(u *url.URL) (*PageToken, error) {
 	return tok, nil
 }
 
-func validatePageToken(tok *PageToken) error {
-	if tok.Limit != tokenLimit {
-		return fmt.Errorf("token limit must be %d", tokenLimit)
+func validatePageToken(tok *PageToken, limit uint16) error {
+	if tok.Limit != limit {
+		return fmt.Errorf("token limit must be %d", limit)
 	}
 
 	if tok.Page == 0 {
diff --git a/api/state.go b/api/state.go
index b641fa17..8df42b26 100644
--- a/api/state.go
+++ b/api/state.go
@@ -24,15 +24,16 @@ import (
 	"github.com/coreos/fleet/schema"
 )
 
-func wireUpStateResource(mux *http.ServeMux, prefix string, cAPI client.API) {
+func wireUpStateResource(mux *http.ServeMux, prefix string, tokenLimit int, cAPI client.API) {
 	base := path.Join(prefix, "state")
-	sr := stateResource{cAPI, base}
+	sr := stateResource{cAPI, base, uint16(tokenLimit)}
 	mux.Handle(base, &sr)
 }
 
 type stateResource struct {
-	cAPI     client.API
-	basePath string
+	cAPI       client.API
+	basePath   string
+	tokenLimit uint16
 }
 
 func (sr *stateResource) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@@ -45,14 +46,14 @@ func (sr *stateResource) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 }
 
 func (sr *stateResource) list(rw http.ResponseWriter, req *http.Request) {
-	token, err := findNextPageToken(req.URL)
+	token, err := findNextPageToken(req.URL, sr.tokenLimit)
 	if err != nil {
 		sendError(rw, http.StatusBadRequest, err)
 		return
 	}
 
 	if token == nil {
-		def := DefaultPageToken()
+		def := DefaultPageToken(sr.tokenLimit)
 		token = &def
 	}
 
diff --git a/api/units.go b/api/units.go
index 10070d93..2765495d 100644
--- a/api/units.go
+++ b/api/units.go
@@ -30,16 +30,17 @@ import (
 	"github.com/coreos/fleet/unit"
 )
 
-func wireUpUnitsResource(mux *http.ServeMux, prefix string, cAPI client.API) {
+func wireUpUnitsResource(mux *http.ServeMux, prefix string, tokenLimit int, cAPI client.API) {
 	base := path.Join(prefix, "units")
-	ur := unitsResource{cAPI, base}
+	ur := unitsResource{cAPI, base, uint16(tokenLimit)}
 	mux.Handle(base, &ur)
 	mux.Handle(base+"/", &ur)
 }
 
 type unitsResource struct {
-	cAPI     client.API
-	basePath string
+	cAPI       client.API
+	basePath   string
+	tokenLimit uint16
 }
 
 func (ur *unitsResource) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@@ -286,14 +287,14 @@ func (ur *unitsResource) get(rw http.ResponseWriter, req *http.Request, item str
 }
 
 func (ur *unitsResource) list(rw http.ResponseWriter, req *http.Request) {
-	token, err := findNextPageToken(req.URL)
+	token, err := findNextPageToken(req.URL, ur.tokenLimit)
 	if err != nil {
 		sendError(rw, http.StatusBadRequest, err)
 		return
 	}
 
 	if token == nil {
-		def := DefaultPageToken()
+		def := DefaultPageToken(ur.tokenLimit)
 		token = &def
 	}
 
diff --git a/config/config.go b/config/config.go
index 5945bab3..fd15ce4a 100644
--- a/config/config.go
+++ b/config/config.go
@@ -30,6 +30,7 @@ type Config struct {
 	Verbosity               int
 	RawMetadata             string
 	AgentTTL                string
+	TokenLimit              int
 	VerifyUnits             bool
 	AuthorizedKeysFile      string
 }
diff --git a/fleetctl/fleetctl.go b/fleetctl/fleetctl.go
index 951536f2..7a3c8cd0 100644
--- a/fleetctl/fleetctl.go
+++ b/fleetctl/fleetctl.go
@@ -792,8 +792,6 @@ func checkUnitState(name string, js job.JobState, maxAttempts int, out io.Writer
 }
 
 func assertUnitState(name string, js job.JobState, out io.Writer) (ret bool) {
-	var state string
-
 	u, err := cAPI.Unit(name)
 	if err != nil {
 		log.Warningf("Error retrieving Unit(%s) from Registry: %v", name, err)
@@ -803,16 +801,8 @@ func assertUnitState(name string, js job.JobState, out io.Writer) (ret bool) {
 		log.Warningf("Unit %s not found", name)
 		return
 	}
-
-	// If this is a global unit, CurrentState will never be set. Instead, wait for DesiredState.
-	if suToGlobal(*u) {
-		state = u.DesiredState
-	} else {
-		state = u.CurrentState
-	}
-
-	if job.JobState(state) != js {
-		log.Debugf("Waiting for Unit(%s) state(%s) to be %s", name, job.JobState(state), js)
+	if job.JobState(u.CurrentState) != js {
+		log.Debugf("Waiting for Unit(%s) state(%s) to be %s", name, job.JobState(u.CurrentState), js)
 		return
 	}
 
diff --git a/fleetctl/status.go b/fleetctl/status.go
index cfbb3447..2b0524a2 100644
--- a/fleetctl/status.go
+++ b/fleetctl/status.go
@@ -18,6 +18,7 @@ import (
 	"fmt"
 
 	"github.com/coreos/fleet/job"
+	"github.com/coreos/fleet/schema"
 )
 
 var cmdStatusUnits = &Command{
@@ -43,34 +44,47 @@ func init() {
 }
 
 func runStatusUnits(args []string) (exit int) {
+	units, err := cAPI.Units()
+	if err != nil {
+		stderr("Error retrieving unit: %v", err)
+		return 1
+	}
+
+	uMap := make(map[string]*schema.Unit, len(args))
+	for _, u := range units {
+		if u != nil {
+			u := u
+			uMap[u.Name] = u
+		}
+	}
+
+	names := make([]string, len(args))
 	for i, arg := range args {
 		name := unitNameMangle(arg)
-		unit, err := cAPI.Unit(name)
-		if err != nil {
-			stderr("Error retrieving unit: %v", err)
-			return 1
-		}
+		names[i] = name
 
-		if unit == nil {
+		u, ok := uMap[name]
+		if !ok {
 			stderr("Unit %s does not exist.", name)
 			return 1
-		} else if suToGlobal(*unit) {
-			stderr("Unable to determine status of global unit %s.", unit.Name)
+		} else if suToGlobal(*u) {
+			stderr("Unable to determine status of global unit %s.", name)
 			return 1
-		} else if job.JobState(unit.CurrentState) == job.JobStateInactive {
-			stderr("Unit %s does not appear to be loaded.", unit.Name)
+		} else if job.JobState(u.CurrentState) == job.JobStateInactive {
+			stderr("Unit %s does not appear to be loaded.", name)
 			return 1
 		}
+	}
 
+	for i, name := range names {
 		// This extra newline is here to match systemctl status output
 		if i != 0 {
 			fmt.Printf("\n")
 		}
 
-		if exit = runCommand(unit.MachineID, "systemctl", "status", "-l", unit.Name); exit != 0 {
+		if exit = runCommand(uMap[name].MachineID, "systemctl", "status", "-l", name); exit != 0 {
 			break
 		}
 	}
-
 	return
 }
diff --git a/fleetd/fleetd.go b/fleetd/fleetd.go
index aee7a599..0b9e0161 100644
--- a/fleetd/fleetd.go
+++ b/fleetd/fleetd.go
@@ -75,6 +75,7 @@ func main() {
 	cfgset.String("public_ip", "", "IP address that fleet machine should publish")
 	cfgset.String("metadata", "", "List of key-value metadata to assign to the fleet machine")
 	cfgset.String("agent_ttl", agent.DefaultTTL, "TTL in seconds of fleet machine state in etcd")
+	cfgset.Int("token_limit", 100, "Maximum number of entries per page returned from API requests")
 	cfgset.Bool("verify_units", false, "DEPRECATED - This option is ignored")
 	cfgset.String("authorized_keys_file", "", "DEPRECATED - This option is ignored")
 
@@ -188,6 +189,7 @@ func getConfig(flagset *flag.FlagSet, userCfgFile string) (*config.Config, error
 		RawMetadata:             (*flagset.Lookup("metadata")).Value.(flag.Getter).Get().(string),
 		AgentTTL:                (*flagset.Lookup("agent_ttl")).Value.(flag.Getter).Get().(string),
 		VerifyUnits:             (*flagset.Lookup("verify_units")).Value.(flag.Getter).Get().(bool),
+		TokenLimit:              (*flagset.Lookup("token_limit")).Value.(flag.Getter).Get().(int),
 		AuthorizedKeysFile:      (*flagset.Lookup("authorized_keys_file")).Value.(flag.Getter).Get().(string),
 	}
 
diff --git a/functional/doc.go b/functional/doc.go
deleted file mode 100644
index a1e10b1d..00000000
--- a/functional/doc.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2015 CoreOS, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Fleet functional test suite
-//
-// These test files must be executed as described in README.md, they are not
-// plain old Go unit tests. ;-)
-package functional
diff --git a/server/server.go b/server/server.go
index 39a5006a..7102fb85 100644
--- a/server/server.go
+++ b/server/server.go
@@ -114,7 +114,7 @@ func New(cfg config.Config) (*Server, error) {
 	hrt := heart.New(reg, mach)
 	mon := heart.NewMonitor(agentTTL)
 
-	apiServer := api.NewServer(listeners, api.NewServeMux(reg))
+	apiServer := api.NewServer(listeners, api.NewServeMux(reg, cfg.TokenLimit))
 	apiServer.Serve()
 
 	eIval := time.Duration(cfg.EngineReconcileInterval*1000) * time.Millisecond
diff --git a/version/version.go b/version/version.go
index 13f0f4f9..71b377e5 100644
--- a/version/version.go
+++ b/version/version.go
@@ -18,7 +18,7 @@ import (
 	"github.com/coreos/fleet/Godeps/_workspace/src/github.com/coreos/go-semver/semver"
 )
 
-const Version = "0.11.5+git"
+const Version = "0.11.4+git"
 
 var SemVersion semver.Version
 


Test patch:
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/client_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/client_test.go
index 2a509fb0..773fba58 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/client_test.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/client/client_test.go
@@ -18,11 +18,9 @@ import (
 	"errors"
 	"io"
 	"io/ioutil"
-	"math/rand"
 	"net/http"
 	"net/url"
 	"reflect"
-	"sort"
 	"strings"
 	"testing"
 	"time"
@@ -109,6 +107,25 @@ func newFakeTransport() *fakeTransport {
 	}
 }
 
+func (t *fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
+	select {
+	case resp := <-t.respchan:
+		return resp, nil
+	case err := <-t.errchan:
+		return nil, err
+	case <-t.startCancel:
+		select {
+		// this simulates that the request is finished before cancel effects
+		case resp := <-t.respchan:
+			return resp, nil
+		// wait on finishCancel to simulate taking some amount of
+		// time while calling CancelRequest
+		case <-t.finishCancel:
+			return nil, errors.New("cancelled")
+		}
+	}
+}
+
 func (t *fakeTransport) CancelRequest(*http.Request) {
 	t.startCancel <- struct{}{}
 }
@@ -238,8 +255,8 @@ func TestSimpleHTTPClientDoCancelContextResponseBodyClosedWithBlockingBody(t *te
 	}()
 
 	_, _, err := c.Do(ctx, &fakeAction{})
-	if err != context.Canceled {
-		t.Fatalf("expected %+v, got %+v", context.Canceled, err)
+	if err == nil {
+		t.Fatalf("expected non-nil error, got nil")
 	}
 
 	if !body.closed {
@@ -278,35 +295,13 @@ func TestSimpleHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) {
 	}
 }
 
-func TestSimpleHTTPClientDoHeaderTimeout(t *testing.T) {
-	tr := newFakeTransport()
-	tr.finishCancel <- struct{}{}
-	c := &simpleHTTPClient{transport: tr, headerTimeout: time.Millisecond}
-
-	errc := make(chan error)
-	go func() {
-		_, _, err := c.Do(context.Background(), &fakeAction{})
-		errc <- err
-	}()
-
-	select {
-	case err := <-errc:
-		if err == nil {
-			t.Fatalf("expected non-nil error, got nil")
-		}
-	case <-time.After(time.Second):
-		t.Fatalf("unexpected timeout when waitting for the test to finish")
-	}
-}
-
 func TestHTTPClusterClientDo(t *testing.T) {
 	fakeErr := errors.New("fake!")
 	fakeURL := url.URL{}
 	tests := []struct {
-		client     *httpClusterClient
-		wantCode   int
-		wantErr    error
-		wantPinned int
+		client   *httpClusterClient
+		wantCode int
+		wantErr  error
 	}{
 		// first good response short-circuits Do
 		{
@@ -314,11 +309,10 @@ func TestHTTPClusterClientDo(t *testing.T) {
 				endpoints: []url.URL{fakeURL, fakeURL},
 				clientFactory: newStaticHTTPClientFactory(
 					[]staticHTTPResponse{
-						{resp: http.Response{StatusCode: http.StatusTeapot}},
-						{err: fakeErr},
+						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
+						staticHTTPResponse{err: fakeErr},
 					},
 				),
-				rand: rand.New(rand.NewSource(0)),
 			},
 			wantCode: http.StatusTeapot,
 		},
@@ -329,14 +323,26 @@ func TestHTTPClusterClientDo(t *testing.T) {
 				endpoints: []url.URL{fakeURL, fakeURL},
 				clientFactory: newStaticHTTPClientFactory(
 					[]staticHTTPResponse{
-						{err: fakeErr},
-						{resp: http.Response{StatusCode: http.StatusTeapot}},
+						staticHTTPResponse{err: fakeErr},
+						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
 					},
 				),
-				rand: rand.New(rand.NewSource(0)),
 			},
-			wantCode:   http.StatusTeapot,
-			wantPinned: 1,
+			wantCode: http.StatusTeapot,
+		},
+
+		// context.DeadlineExceeded short-circuits Do
+		{
+			client: &httpClusterClient{
+				endpoints: []url.URL{fakeURL, fakeURL},
+				clientFactory: newStaticHTTPClientFactory(
+					[]staticHTTPResponse{
+						staticHTTPResponse{err: context.DeadlineExceeded},
+						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
+					},
+				),
+			},
+			wantErr: context.DeadlineExceeded,
 		},
 
 		// context.Canceled short-circuits Do
@@ -345,11 +351,10 @@ func TestHTTPClusterClientDo(t *testing.T) {
 				endpoints: []url.URL{fakeURL, fakeURL},
 				clientFactory: newStaticHTTPClientFactory(
 					[]staticHTTPResponse{
-						{err: context.Canceled},
-						{resp: http.Response{StatusCode: http.StatusTeapot}},
+						staticHTTPResponse{err: context.Canceled},
+						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
 					},
 				),
-				rand: rand.New(rand.NewSource(0)),
 			},
 			wantErr: context.Canceled,
 		},
@@ -358,8 +363,7 @@ func TestHTTPClusterClientDo(t *testing.T) {
 		{
 			client: &httpClusterClient{
 				endpoints:     []url.URL{},
-				clientFactory: newHTTPClientFactory(nil, nil, 0),
-				rand:          rand.New(rand.NewSource(0)),
+				clientFactory: newHTTPClientFactory(nil, nil),
 			},
 			wantErr: ErrNoEndpoints,
 		},
@@ -370,13 +374,12 @@ func TestHTTPClusterClientDo(t *testing.T) {
 				endpoints: []url.URL{fakeURL, fakeURL},
 				clientFactory: newStaticHTTPClientFactory(
 					[]staticHTTPResponse{
-						{err: fakeErr},
-						{err: fakeErr},
+						staticHTTPResponse{err: fakeErr},
+						staticHTTPResponse{err: fakeErr},
 					},
 				),
-				rand: rand.New(rand.NewSource(0)),
 			},
-			wantErr: &ClusterError{Errors: []error{fakeErr, fakeErr}},
+			wantErr: fakeErr,
 		},
 
 		// 500-level errors cause Do to fallthrough to next endpoint
@@ -385,14 +388,12 @@ func TestHTTPClusterClientDo(t *testing.T) {
 				endpoints: []url.URL{fakeURL, fakeURL},
 				clientFactory: newStaticHTTPClientFactory(
 					[]staticHTTPResponse{
-						{resp: http.Response{StatusCode: http.StatusBadGateway}},
-						{resp: http.Response{StatusCode: http.StatusTeapot}},
+						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusBadGateway}},
+						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
 					},
 				),
-				rand: rand.New(rand.NewSource(0)),
 			},
-			wantCode:   http.StatusTeapot,
-			wantPinned: 1,
+			wantCode: http.StatusTeapot,
 		},
 	}
 
@@ -414,37 +415,6 @@ func TestHTTPClusterClientDo(t *testing.T) {
 			t.Errorf("#%d: resp code=%d, want=%d", i, resp.StatusCode, tt.wantCode)
 			continue
 		}
-
-		if tt.client.pinned != tt.wantPinned {
-			t.Errorf("#%d: pinned=%d, want=%d", i, tt.client.pinned, tt.wantPinned)
-		}
-	}
-}
-
-func TestHTTPClusterClientDoDeadlineExceedContext(t *testing.T) {
-	fakeURL := url.URL{}
-	tr := newFakeTransport()
-	tr.finishCancel <- struct{}{}
-	c := &httpClusterClient{
-		clientFactory: newHTTPClientFactory(tr, DefaultCheckRedirect, 0),
-		endpoints:     []url.URL{fakeURL},
-	}
-
-	errc := make(chan error)
-	go func() {
-		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
-		defer cancel()
-		_, _, err := c.Do(ctx, &fakeAction{})
-		errc <- err
-	}()
-
-	select {
-	case err := <-errc:
-		if err != context.DeadlineExceeded {
-			t.Errorf("err = %+v, want %+v", err, context.DeadlineExceeded)
-		}
-	case <-time.After(time.Second):
-		t.Fatalf("unexpected timeout when waitting for request to deadline exceed")
 	}
 }
 
@@ -494,7 +464,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			checkRedirect: func(int) error { return ErrTooManyRedirects },
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						err: errors.New("fail!"),
 					},
 				},
@@ -507,7 +477,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			checkRedirect: func(int) error { return ErrTooManyRedirects },
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTeapot,
 						},
@@ -527,13 +497,13 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			},
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 							Header:     http.Header{"Location": []string{"http://example.com"}},
 						},
 					},
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTeapot,
 						},
@@ -553,19 +523,19 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			},
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 							Header:     http.Header{"Location": []string{"http://example.com"}},
 						},
 					},
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 							Header:     http.Header{"Location": []string{"http://example.com"}},
 						},
 					},
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTeapot,
 						},
@@ -585,19 +555,19 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			},
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 							Header:     http.Header{"Location": []string{"http://example.com"}},
 						},
 					},
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 							Header:     http.Header{"Location": []string{"http://example.com"}},
 						},
 					},
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTeapot,
 						},
@@ -612,7 +582,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			checkRedirect: func(int) error { return ErrTooManyRedirects },
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 						},
@@ -627,7 +597,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
 			checkRedirect: func(int) error { return ErrTooManyRedirects },
 			client: &multiStaticHTTPClient{
 				responses: []staticHTTPResponse{
-					{
+					staticHTTPResponse{
 						resp: http.Response{
 							StatusCode: http.StatusTemporaryRedirect,
 							Header:     http.Header{"Location": []string{":"}},
@@ -695,16 +665,13 @@ func TestDefaultCheckRedirect(t *testing.T) {
 
 func TestHTTPClusterClientSync(t *testing.T) {
 	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
-		{
+		staticHTTPResponse{
 			resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
 			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
 		},
 	})
 
-	hc := &httpClusterClient{
-		clientFactory: cf,
-		rand:          rand.New(rand.NewSource(0)),
-	}
+	hc := &httpClusterClient{clientFactory: cf}
 	err := hc.reset([]string{"http://127.0.0.1:2379"})
 	if err != nil {
 		t.Fatalf("unexpected error during setup: %#v", err)
@@ -721,9 +688,8 @@ func TestHTTPClusterClientSync(t *testing.T) {
 		t.Fatalf("unexpected error during Sync: %#v", err)
 	}
 
-	want = []string{"http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002", "http://127.0.0.1:4003"}
+	want = []string{"http://127.0.0.1:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"}
 	got = hc.Endpoints()
-	sort.Sort(sort.StringSlice(got))
 	if !reflect.DeepEqual(want, got) {
 		t.Fatalf("incorrect endpoints post-Sync: want=%#v got=%#v", want, got)
 	}
@@ -742,13 +708,10 @@ func TestHTTPClusterClientSync(t *testing.T) {
 
 func TestHTTPClusterClientSyncFail(t *testing.T) {
 	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
-		{err: errors.New("fail!")},
+		staticHTTPResponse{err: errors.New("fail!")},
 	})
 
-	hc := &httpClusterClient{
-		clientFactory: cf,
-		rand:          rand.New(rand.NewSource(0)),
-	}
+	hc := &httpClusterClient{clientFactory: cf}
 	err := hc.reset([]string{"http://127.0.0.1:2379"})
 	if err != nil {
 		t.Fatalf("unexpected error during setup: %#v", err)
@@ -771,126 +734,20 @@ func TestHTTPClusterClientSyncFail(t *testing.T) {
 	}
 }
 
-func TestHTTPClusterClientAutoSyncCancelContext(t *testing.T) {
-	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
-		{
-			resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
-			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
-		},
-	})
-
-	hc := &httpClusterClient{
-		clientFactory: cf,
-		rand:          rand.New(rand.NewSource(0)),
-	}
-	err := hc.reset([]string{"http://127.0.0.1:2379"})
-	if err != nil {
-		t.Fatalf("unexpected error during setup: %#v", err)
-	}
-	ctx, cancel := context.WithCancel(context.Background())
-	cancel()
-
-	err = hc.AutoSync(ctx, time.Hour)
-	if err != context.Canceled {
-		t.Fatalf("incorrect error value: want=%v got=%v", context.Canceled, err)
-	}
-}
-
-func TestHTTPClusterClientAutoSyncFail(t *testing.T) {
-	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
-		{err: errors.New("fail!")},
-	})
-
-	hc := &httpClusterClient{
-		clientFactory: cf,
-		rand:          rand.New(rand.NewSource(0)),
-	}
-	err := hc.reset([]string{"http://127.0.0.1:2379"})
-	if err != nil {
-		t.Fatalf("unexpected error during setup: %#v", err)
-	}
-
-	err = hc.AutoSync(context.Background(), time.Hour)
-	if err.Error() != ErrClusterUnavailable.Error() {
-		t.Fatalf("incorrect error value: want=%v got=%v", ErrClusterUnavailable, err)
-	}
-}
-
-// TestHTTPClusterClientSyncPinEndpoint tests that Sync() pins the endpoint when
-// it gets the exactly same member list as before.
-func TestHTTPClusterClientSyncPinEndpoint(t *testing.T) {
-	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
-		{
-			resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
-			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
-		},
-		{
-			resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
-			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
-		},
-		{
-			resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
-			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
-		},
-	})
-
-	hc := &httpClusterClient{
-		clientFactory: cf,
-		rand:          rand.New(rand.NewSource(0)),
-	}
-	err := hc.reset([]string{"http://127.0.0.1:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"})
-	if err != nil {
-		t.Fatalf("unexpected error during setup: %#v", err)
-	}
-	pinnedEndpoint := hc.endpoints[hc.pinned]
-
-	for i := 0; i < 3; i++ {
-		err = hc.Sync(context.Background())
-		if err != nil {
-			t.Fatalf("#%d: unexpected error during Sync: %#v", i, err)
-		}
-
-		if g := hc.endpoints[hc.pinned]; g != pinnedEndpoint {
-			t.Errorf("#%d: pinned endpoint = %s, want %s", i, g, pinnedEndpoint)
-		}
-	}
-}
-
 func TestHTTPClusterClientResetFail(t *testing.T) {
 	tests := [][]string{
 		// need at least one endpoint
-		{},
+		[]string{},
 
 		// urls must be valid
-		{":"},
+		[]string{":"},
 	}
 
 	for i, tt := range tests {
-		hc := &httpClusterClient{rand: rand.New(rand.NewSource(0))}
+		hc := &httpClusterClient{}
 		err := hc.reset(tt)
 		if err == nil {
 			t.Errorf("#%d: expected non-nil error", i)
 		}
 	}
 }
-
-func TestHTTPClusterClientResetPinRandom(t *testing.T) {
-	round := 2000
-	pinNum := 0
-	for i := 0; i < round; i++ {
-		hc := &httpClusterClient{rand: rand.New(rand.NewSource(int64(i)))}
-		err := hc.reset([]string{"http://127.0.0.1:4001", "http://127.0.0.1:4002", "http://127.0.0.1:4003"})
-		if err != nil {
-			t.Fatalf("#%d: reset error (%v)", i, err)
-		}
-		if hc.endpoints[hc.pinned].String() == "http://127.0.0.1:4001" {
-			pinNum++
-		}
-	}
-
-	min := 1.0/3.0 - 0.05
-	max := 1.0/3.0 + 0.05
-	if ratio := float64(pinNum) / float64(round); ratio > max || ratio < min {
-		t.Errorf("pinned ratio = %v, want [%v, %v]", ratio, min, max)
-	}
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/fake_transport_go14_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/fake_transport_go14_test.go
deleted file mode 100644
index 4a99a7d3..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/fake_transport_go14_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2015 CoreOS, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build !go1.5
-
-package client
-
-import (
-	"errors"
-	"net/http"
-)
-
-func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	select {
-	case resp := <-t.respchan:
-		return resp, nil
-	case err := <-t.errchan:
-		return nil, err
-	case <-t.startCancel:
-		select {
-		// this simulates that the request is finished before cancel effects
-		case resp := <-t.respchan:
-			return resp, nil
-		// wait on finishCancel to simulate taking some amount of
-		// time while calling CancelRequest
-		case <-t.finishCancel:
-			return nil, errors.New("cancelled")
-		}
-	}
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/fake_transport_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/fake_transport_test.go
deleted file mode 100644
index 06761e26..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/fake_transport_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2015 CoreOS, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build go1.5
-
-package client
-
-import (
-	"errors"
-	"net/http"
-)
-
-func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	select {
-	case resp := <-t.respchan:
-		return resp, nil
-	case err := <-t.errchan:
-		return nil, err
-	case <-t.startCancel:
-	case <-req.Cancel:
-	}
-	select {
-	// this simulates that the request is finished before cancel effects
-	case resp := <-t.respchan:
-		return resp, nil
-	// wait on finishCancel to simulate taking some amount of
-	// time while calling CancelRequest
-	case <-t.finishCancel:
-		return nil, errors.New("cancelled")
-	}
-}
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/keys_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/keys_test.go
index 5093f5e9..067b4aba 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/keys_test.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/client/keys_test.go
@@ -80,20 +80,6 @@ func TestV2KeysURLHelper(t *testing.T) {
 			key:      "/baz",
 			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"},
 		},
-		// Prefix is joined to path
-		{
-			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
-			prefix:   "/bar",
-			key:      "",
-			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar"},
-		},
-		// Keep trailing slash
-		{
-			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
-			prefix:   "/bar",
-			key:      "/baz/",
-			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz/"},
-		},
 	}
 
 	for i, tt := range tests {
@@ -105,7 +91,7 @@ func TestV2KeysURLHelper(t *testing.T) {
 }
 
 func TestGetAction(t *testing.T) {
-	ep := url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}
+	ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
 	baseWantURL := &url.URL{
 		Scheme: "http",
 		Host:   "example.com",
@@ -171,7 +157,7 @@ func TestGetAction(t *testing.T) {
 }
 
 func TestWaitAction(t *testing.T) {
-	ep := url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}
+	ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
 	baseWantURL := &url.URL{
 		Scheme: "http",
 		Host:   "example.com",
@@ -221,7 +207,7 @@ func TestWaitAction(t *testing.T) {
 
 func TestSetAction(t *testing.T) {
 	wantHeader := http.Header(map[string][]string{
-		"Content-Type": {"application/x-www-form-urlencoded"},
+		"Content-Type": []string{"application/x-www-form-urlencoded"},
 	})
 
 	tests := []struct {
@@ -283,7 +269,7 @@ func TestSetAction(t *testing.T) {
 			act: setAction{
 				Key: "/foo/",
 			},
-			wantURL:  "http://example.com/foo/",
+			wantURL:  "http://example.com/foo",
 			wantBody: "value=",
 		},
 
@@ -412,7 +398,7 @@ func TestSetAction(t *testing.T) {
 
 func TestCreateInOrderAction(t *testing.T) {
 	wantHeader := http.Header(map[string][]string{
-		"Content-Type": {"application/x-www-form-urlencoded"},
+		"Content-Type": []string{"application/x-www-form-urlencoded"},
 	})
 
 	tests := []struct {
@@ -474,7 +460,7 @@ func TestCreateInOrderAction(t *testing.T) {
 			act: createInOrderAction{
 				Dir: "/foo/",
 			},
-			wantURL:  "http://example.com/foo/",
+			wantURL:  "http://example.com/foo",
 			wantBody: "value=",
 		},
 
@@ -513,7 +499,7 @@ func TestCreateInOrderAction(t *testing.T) {
 
 func TestDeleteAction(t *testing.T) {
 	wantHeader := http.Header(map[string][]string{
-		"Content-Type": {"application/x-www-form-urlencoded"},
+		"Content-Type": []string{"application/x-www-form-urlencoded"},
 	})
 
 	tests := []struct {
@@ -569,7 +555,7 @@ func TestDeleteAction(t *testing.T) {
 			act: deleteAction{
 				Key: "/foo/",
 			},
-			wantURL: "http://example.com/foo/",
+			wantURL: "http://example.com/foo",
 		},
 
 		// Recursive set to true
@@ -1213,7 +1199,7 @@ func TestHTTPKeysAPIGetResponse(t *testing.T) {
 		Node: &Node{
 			Key: "/pants/foo/bar",
 			Nodes: []*Node{
-				{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: 21, ModifiedIndex: 25},
+				&Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: 21, ModifiedIndex: 25},
 			},
 			CreatedIndex:  uint64(19),
 			ModifiedIndex: uint64(25),
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/members_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/members_test.go
index 75e04676..bbcb954e 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/members_test.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/client/members_test.go
@@ -48,8 +48,8 @@ func TestMembersAPIActionAdd(t *testing.T) {
 	ep := url.URL{Scheme: "http", Host: "example.com"}
 	act := &membersAPIActionAdd{
 		peerURLs: types.URLs([]url.URL{
-			{Scheme: "https", Host: "127.0.0.1:8081"},
-			{Scheme: "http", Host: "127.0.0.1:8080"},
+			url.URL{Scheme: "https", Host: "127.0.0.1:8081"},
+			url.URL{Scheme: "http", Host: "127.0.0.1:8080"},
 		}),
 	}
 
@@ -75,8 +75,8 @@ func TestMembersAPIActionUpdate(t *testing.T) {
 	act := &membersAPIActionUpdate{
 		memberID: "0xabcd",
 		peerURLs: types.URLs([]url.URL{
-			{Scheme: "https", Host: "127.0.0.1:8081"},
-			{Scheme: "http", Host: "127.0.0.1:8080"},
+			url.URL{Scheme: "https", Host: "127.0.0.1:8081"},
+			url.URL{Scheme: "http", Host: "127.0.0.1:8080"},
 		}),
 	}
 
@@ -289,8 +289,8 @@ func TestMemberCollectionUnmarshal(t *testing.T) {
 func TestMemberCreateRequestMarshal(t *testing.T) {
 	req := memberCreateOrUpdateRequest{
 		PeerURLs: types.URLs([]url.URL{
-			{Scheme: "http", Host: "127.0.0.1:8081"},
-			{Scheme: "https", Host: "127.0.0.1:8080"},
+			url.URL{Scheme: "http", Host: "127.0.0.1:8081"},
+			url.URL{Scheme: "https", Host: "127.0.0.1:8080"},
 		}),
 	}
 	want := []byte(`{"peerURLs":["http://127.0.0.1:8081","https://127.0.0.1:8080"]}`)
@@ -308,7 +308,7 @@ func TestMemberCreateRequestMarshal(t *testing.T) {
 func TestHTTPMembersAPIAddSuccess(t *testing.T) {
 	wantAction := &membersAPIActionAdd{
 		peerURLs: types.URLs([]url.URL{
-			{Scheme: "http", Host: "127.0.0.1:7002"},
+			url.URL{Scheme: "http", Host: "127.0.0.1:7002"},
 		}),
 	}
 
@@ -473,7 +473,7 @@ func TestHTTPMembersAPIListSuccess(t *testing.T) {
 	}
 
 	wantResponseMembers := []Member{
-		{
+		Member{
 			ID:         "94088180e21eb87b",
 			Name:       "node2",
 			PeerURLs:   []string{"http://127.0.0.1:7002"},
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/client/srv_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/client/srv_test.go
index a4a27f10..72b1c9a4 100644
--- a/Godeps/_workspace/src/github.com/coreos/etcd/client/srv_test.go
+++ b/Godeps/_workspace/src/github.com/coreos/etcd/client/srv_test.go
@@ -36,40 +36,40 @@ func TestSRVDiscover(t *testing.T) {
 		},
 		{
 			[]*net.SRV{
-				{Target: "10.0.0.1", Port: 2480},
-				{Target: "10.0.0.2", Port: 2480},
-				{Target: "10.0.0.3", Port: 2480},
+				&net.SRV{Target: "10.0.0.1", Port: 2480},
+				&net.SRV{Target: "10.0.0.2", Port: 2480},
+				&net.SRV{Target: "10.0.0.3", Port: 2480},
 			},
 			[]*net.SRV{},
 			[]string{"https://10.0.0.1:2480", "https://10.0.0.2:2480", "https://10.0.0.3:2480"},
 		},
 		{
 			[]*net.SRV{
-				{Target: "10.0.0.1", Port: 2480},
-				{Target: "10.0.0.2", Port: 2480},
-				{Target: "10.0.0.3", Port: 2480},
+				&net.SRV{Target: "10.0.0.1", Port: 2480},
+				&net.SRV{Target: "10.0.0.2", Port: 2480},
+				&net.SRV{Target: "10.0.0.3", Port: 2480},
 			},
 			[]*net.SRV{
-				{Target: "10.0.0.1", Port: 7001},
+				&net.SRV{Target: "10.0.0.1", Port: 7001},
 			},
 			[]string{"https://10.0.0.1:2480", "https://10.0.0.2:2480", "https://10.0.0.3:2480", "http://10.0.0.1:7001"},
 		},
 		{
 			[]*net.SRV{
-				{Target: "10.0.0.1", Port: 2480},
-				{Target: "10.0.0.2", Port: 2480},
-				{Target: "10.0.0.3", Port: 2480},
+				&net.SRV{Target: "10.0.0.1", Port: 2480},
+				&net.SRV{Target: "10.0.0.2", Port: 2480},
+				&net.SRV{Target: "10.0.0.3", Port: 2480},
 			},
 			[]*net.SRV{
-				{Target: "10.0.0.1", Port: 7001},
+				&net.SRV{Target: "10.0.0.1", Port: 7001},
 			},
 			[]string{"https://10.0.0.1:2480", "https://10.0.0.2:2480", "https://10.0.0.3:2480", "http://10.0.0.1:7001"},
 		},
 		{
 			[]*net.SRV{
-				{Target: "a.example.com", Port: 2480},
-				{Target: "b.example.com", Port: 2480},
-				{Target: "c.example.com", Port: 2480},
+				&net.SRV{Target: "a.example.com", Port: 2480},
+				&net.SRV{Target: "b.example.com", Port: 2480},
+				&net.SRV{Target: "c.example.com", Port: 2480},
 			},
 			[]*net.SRV{},
 			[]string{"https://a.example.com:2480", "https://b.example.com:2480", "https://c.example.com:2480"},
diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path_test.go
deleted file mode 100644
index 6d3d803c..00000000
--- a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2015 CoreOS, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pathutil
-
-import "testing"
-
-func TestCanonicalURLPath(t *testing.T) {
-	tests := []struct {
-		p  string
-		wp string
-	}{
-		{"/a", "/a"},
-		{"", "/"},
-		{"a", "/a"},
-		{"//a", "/a"},
-		{"/a/.", "/a"},
-		{"/a/..", "/"},
-		{"/a/", "/a/"},
-		{"/a//", "/a/"},
-	}
-	for i, tt := range tests {
-		if g := CanonicalURLPath(tt.p); g != tt.wp {
-			t.Errorf("#%d: canonical path = %s, want %s", i, g, tt.wp)
-		}
-	}
-}
diff --git a/agent/unit_state_test.go b/agent/unit_state_test.go
index 3c58c4d4..f90115ea 100644
--- a/agent/unit_state_test.go
+++ b/agent/unit_state_test.go
@@ -18,7 +18,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"reflect"
-	"runtime"
 	"sort"
 	"sync"
 	"testing"
@@ -569,9 +568,6 @@ func TestUnitStatePublisherRunQueuing(t *testing.T) {
 }
 
 func TestUnitStatePublisherRunWithDelays(t *testing.T) {
-	if runtime.GOMAXPROCS(-1) != 1 {
-		t.Skipf("Broken for GOMAXPROCS != 1, currently %d", runtime.GOMAXPROCS(-1))
-	}
 	states := make([]string, 0)
 	mu := sync.Mutex{} // protects states from concurrent modification
 	fclock := clockwork.NewFakeClock()
@@ -651,15 +647,7 @@ func TestUnitStatePublisherRunWithDelays(t *testing.T) {
 
 	usp.toPublishMutex.RLock()
 	if !reflect.DeepEqual(usp.toPublishStates, wtps) {
-		t.Errorf("bad toPublishStates")
-		t.Logf("got:\n")
-		for k, v := range usp.toPublishStates {
-			t.Logf("  %v = %#v", k, v)
-		}
-		t.Logf("want:\n")
-		for k, v := range wtps {
-			t.Logf("  %v = %#v", k, v)
-		}
+		t.Errorf("bad toPublishStates: got %#v, want %#v", usp.toPublishStates, wtps)
 	}
 	usp.toPublishMutex.RUnlock()
 
diff --git a/api/machines_test.go b/api/machines_test.go
index 969b5c59..cb3aea3e 100644
--- a/api/machines_test.go
+++ b/api/machines_test.go
@@ -33,7 +33,7 @@ func TestMachinesList(t *testing.T) {
 		{ID: "YYY", PublicIP: "1.2.3.4", Metadata: map[string]string{"ping": "pong"}},
 	})
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &machinesResource{cAPI: fAPI}
+	resource := &machinesResource{cAPI: fAPI, tokenLimit: testTokenLimit}
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("GET", "http://example.com", nil)
 	if err != nil {
@@ -66,7 +66,7 @@ func TestMachinesList(t *testing.T) {
 func TestMachinesListBadNextPageToken(t *testing.T) {
 	fr := registry.NewFakeRegistry()
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &machinesResource{fAPI}
+	resource := &machinesResource{fAPI, testTokenLimit}
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("GET", "http://example.com/machines?nextPageToken=EwBMLg==", nil)
 	if err != nil {
diff --git a/api/mux_test.go b/api/mux_test.go
index 3d24ebb9..f504b3e0 100644
--- a/api/mux_test.go
+++ b/api/mux_test.go
@@ -24,6 +24,8 @@ import (
 	"github.com/coreos/fleet/version"
 )
 
+const testTokenLimit = 100
+
 func TestDefaultHandlers(t *testing.T) {
 	tests := []struct {
 		method string
@@ -38,7 +40,7 @@ func TestDefaultHandlers(t *testing.T) {
 
 	for i, tt := range tests {
 		fr := registry.NewFakeRegistry()
-		hdlr := NewServeMux(fr)
+		hdlr := NewServeMux(fr, testTokenLimit)
 		rr := httptest.NewRecorder()
 
 		req, err := http.NewRequest(tt.method, tt.path, nil)
diff --git a/api/pagination_test.go b/api/pagination_test.go
index ab5a9f91..f4917e22 100644
--- a/api/pagination_test.go
+++ b/api/pagination_test.go
@@ -21,7 +21,7 @@ import (
 )
 
 func TestDefaultPageToken(t *testing.T) {
-	tok := DefaultPageToken()
+	tok := DefaultPageToken(testTokenLimit)
 	expect := PageToken{Limit: 100, Page: 1}
 	if !reflect.DeepEqual(expect, tok) {
 		t.Errorf("Unexpected default PageToken: expect=%v, got=%v", expect, tok)
@@ -108,7 +108,7 @@ func TestFindNextPageToken(t *testing.T) {
 	}
 
 	for i, tt := range tests {
-		next, err := findNextPageToken(&tt.input)
+		next, err := findNextPageToken(&tt.input, testTokenLimit)
 
 		if tt.pass != (err == nil) {
 			t.Errorf("case %d: pass=%t, err=%v", i, tt.pass, err)
@@ -135,7 +135,7 @@ func TestValidatePageToken(t *testing.T) {
 	}
 
 	for i, tt := range tests {
-		err := validatePageToken(&tt.input)
+		err := validatePageToken(&tt.input, testTokenLimit)
 
 		if tt.pass != (err == nil) {
 			t.Errorf("case %d: pass=%t, err=%v", i, tt.pass, err)
diff --git a/api/state_test.go b/api/state_test.go
index 43ea67db..525e93d7 100644
--- a/api/state_test.go
+++ b/api/state_test.go
@@ -81,7 +81,7 @@ func TestUnitStateList(t *testing.T) {
 		fr := registry.NewFakeRegistry()
 		fr.SetUnitStates([]unit.UnitState{us1, us2, us3, us4})
 		fAPI := &client.RegistryClient{Registry: fr}
-		resource := &stateResource{fAPI, "/state"}
+		resource := &stateResource{fAPI, "/state", testTokenLimit}
 		rw := httptest.NewRecorder()
 		req, err := http.NewRequest("GET", tt.url, nil)
 		if err != nil {
@@ -130,7 +130,7 @@ func TestUnitStateList(t *testing.T) {
 		unit.UnitState{UnitName: "YYY", ActiveState: "inactive"},
 	})
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &stateResource{fAPI, "/state"}
+	resource := &stateResource{fAPI, "/state", testTokenLimit}
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("GET", "http://example.com/state", nil)
 	if err != nil {
@@ -178,7 +178,7 @@ func TestUnitStateList(t *testing.T) {
 func TestUnitStateListBadNextPageToken(t *testing.T) {
 	fr := registry.NewFakeRegistry()
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &stateResource{fAPI, "/state"}
+	resource := &stateResource{fAPI, "/state", testTokenLimit}
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("GET", "http://example.com/state?nextPageToken=EwBMLg==", nil)
 	if err != nil {
diff --git a/api/units_test.go b/api/units_test.go
index 6a9d37fe..85a341fd 100644
--- a/api/units_test.go
+++ b/api/units_test.go
@@ -43,7 +43,7 @@ func newUnit(t *testing.T, str string) unit.UnitFile {
 func TestUnitsSubResourceNotFound(t *testing.T) {
 	fr := registry.NewFakeRegistry()
 	fAPI := &client.RegistryClient{Registry: fr}
-	ur := &unitsResource{fAPI, "/units"}
+	ur := &unitsResource{fAPI, "/units", testTokenLimit}
 	rr := httptest.NewRecorder()
 
 	req, err := http.NewRequest("GET", "/units/foo/bar", nil)
@@ -66,7 +66,7 @@ func TestUnitsList(t *testing.T) {
 		{Name: "YYY.service"},
 	})
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &unitsResource{fAPI, "/units"}
+	resource := &unitsResource{fAPI, "/units", testTokenLimit}
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("GET", "http://example.com/units", nil)
 	if err != nil {
@@ -103,7 +103,7 @@ func TestUnitsList(t *testing.T) {
 func TestUnitsListBadNextPageToken(t *testing.T) {
 	fr := registry.NewFakeRegistry()
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &unitsResource{fAPI, "/units"}
+	resource := &unitsResource{fAPI, "/units", testTokenLimit}
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("GET", "http://example.com/units?nextPageToken=EwBMLg==", nil)
 	if err != nil {
@@ -184,7 +184,7 @@ func TestUnitGet(t *testing.T) {
 		{Name: "YYY.service"},
 	})
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &unitsResource{fAPI, "/units"}
+	resource := &unitsResource{fAPI, "/units", testTokenLimit}
 
 	for i, tt := range tests {
 		rw := httptest.NewRecorder()
@@ -247,7 +247,7 @@ func TestUnitsDestroy(t *testing.T) {
 		}
 
 		fAPI := &client.RegistryClient{Registry: fr}
-		resource := &unitsResource{fAPI, "/units"}
+		resource := &unitsResource{fAPI, "/units", testTokenLimit}
 		rw := httptest.NewRecorder()
 		resource.destroy(rw, req, tt.arg)
 
@@ -415,7 +415,7 @@ func TestUnitsSetDesiredState(t *testing.T) {
 		req.Header.Set("Content-Type", "application/json")
 
 		fAPI := &client.RegistryClient{Registry: fr}
-		resource := &unitsResource{fAPI, "/units"}
+		resource := &unitsResource{fAPI, "/units", testTokenLimit}
 		rw := httptest.NewRecorder()
 		resource.set(rw, req, tt.item)
 
@@ -718,7 +718,7 @@ func TestValidateName(t *testing.T) {
 func TestUnitsSetDesiredStateBadContentType(t *testing.T) {
 	fr := registry.NewFakeRegistry()
 	fAPI := &client.RegistryClient{Registry: fr}
-	resource := &unitsResource{fAPI, "/units"}
+	resource := &unitsResource{fAPI, "/units", testTokenLimit}
 	rr := httptest.NewRecorder()
 
 	body := ioutil.NopCloser(bytes.NewBuffer([]byte(`{"foo":"bar"}`)))