mirror of
https://github.com/valitydev/registrator.git
synced 2024-11-06 02:45:17 +00:00
overhaul of build/release system, modeled after logspout overhaul
This commit is contained in:
parent
ee8720d50a
commit
e2677212c2
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
release
|
||||
build
|
||||
.git
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
release
|
||||
registrator
|
||||
build
|
||||
|
34
CHANGELOG.md
Normal file
34
CHANGELOG.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased][unreleased]
|
||||
### Fixed
|
||||
|
||||
### Added
|
||||
|
||||
### Removed
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
## [v5] - 2015-02-18
|
||||
### Added
|
||||
- Automated, PR-driven release process
|
||||
- Development Dockerfile and make task
|
||||
- CircleCI artifacts for every build
|
||||
- `--version` flag to see version
|
||||
|
||||
### Changed
|
||||
- Base container is now Alpine
|
||||
- Built entirely in Docker
|
||||
- Moved to gliderlabs organization
|
||||
- New versioning scheme
|
||||
- Release artifact now saved container image
|
||||
|
||||
### Removed
|
||||
- Dropped unnecessary layers in Dockerfile
|
||||
- Dropped Godeps for now
|
||||
|
||||
|
||||
[unreleased]: https://github.com/gliderlabs/registrator/compare/v5...HEAD
|
||||
[v5]: https://github.com/gliderlabs/registrator/compare/v0.4.0...v5
|
17
Dockerfile
17
Dockerfile
@ -1,8 +1,11 @@
|
||||
FROM progrium/busybox
|
||||
MAINTAINER Jeff Lindsay <progrium@gmail.com>
|
||||
FROM gliderlabs/alpine:3.1
|
||||
ENTRYPOINT ["/bin/registrator"]
|
||||
|
||||
ADD https://github.com/progrium/registrator/releases/download/v0.4.0/registrator_0.4.0_linux_x86_64.tgz /tmp/registrator.tgz
|
||||
RUN cd /bin && gzip -dc /tmp/registrator.tgz | tar -xf - && rm /tmp/registrator.tgz
|
||||
|
||||
ENV DOCKER_HOST unix:///tmp/docker.sock
|
||||
ENTRYPOINT ["/bin/registrator"]
|
||||
COPY . /go/src/github.com/gliderlabs/registrator
|
||||
RUN apk-install -t build-deps go git mercurial \
|
||||
&& cd /go/src/github.com/gliderlabs/registrator \
|
||||
&& export GOPATH=/go \
|
||||
&& go get \
|
||||
&& go build -ldflags "-X main.Version $(cat VERSION)" -o /bin/registrator \
|
||||
&& rm -rf /go \
|
||||
&& apk del --purge build-deps
|
||||
|
9
Dockerfile.dev
Normal file
9
Dockerfile.dev
Normal file
@ -0,0 +1,9 @@
|
||||
FROM gliderlabs/alpine:3.1
|
||||
CMD ["/bin/registrator"]
|
||||
|
||||
ENV GOPATH /go
|
||||
RUN apk-install go git mercurial
|
||||
COPY . /go/src/github.com/gliderlabs/registrator
|
||||
RUN cd /go/src/github.com/gliderlabs/registrator \
|
||||
&& go get \
|
||||
&& go build -ldflags "-X main.Version dev" -o /bin/registrator
|
68
Godeps/Godeps.json
generated
68
Godeps/Godeps.json
generated
@ -1,68 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/progrium/registrator",
|
||||
"GoVersion": "go1.4",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.6.2-15-gd2f9ffa",
|
||||
"Rev": "d2f9ffa1d9cf25b25191b221229effac1b6de526"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/armon/consul-api",
|
||||
"Rev": "1b81c8e0c4cbf1d382310e4c0dc11221632e79d1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cenkalti/backoff",
|
||||
"Rev": "6a458ae422d1a8cbfb686ac5f4789bd82956c61b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
||||
"Comment": "v0.2.0-rc1-127-g6fe04d5",
|
||||
"Rev": "6fe04d580dfb71c9e34cbce2f4df9eefd1e1241e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/archive",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/fileutils",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/pools",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/promise",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/system",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/units",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||
"Comment": "v1.4.1-358-gde97839",
|
||||
"Rev": "de9783980be2a7b3ca10eb8183ea5989acbd3e7e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fsouza/go-dockerclient",
|
||||
"Rev": "bc54f3734b9b098e24680d5ad686f602bc152df4"
|
||||
}
|
||||
]
|
||||
}
|
5
Godeps/Readme
generated
5
Godeps/Readme
generated
@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
2
Godeps/_workspace/.gitignore
generated
vendored
@ -1,2 +0,0 @@
|
||||
/pkg
|
||||
/bin
|
1
Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore
generated
vendored
1
Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore
generated
vendored
@ -1 +0,0 @@
|
||||
logrus
|
10
Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
10
Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
@ -1,10 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
install:
|
||||
- go get github.com/stretchr/testify
|
||||
- go get github.com/stvp/go-udp-testing
|
||||
- go get github.com/tobi/airbrake-go
|
||||
- go get github.com/getsentry/raven-go
|
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE
generated
vendored
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
355
Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md
generated
vendored
355
Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md
generated
vendored
@ -1,355 +0,0 @@
|
||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc]
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||
yet stable (pre 1.0), the core API is unlikely change much but please version
|
||||
control your Logrus to make sure you aren't fetching latest `master` on every
|
||||
build.**
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||
![Colored](http://i.imgur.com/PY7qMwd.png)
|
||||
|
||||
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[l2met](http://r.32k.io/l2met-introduction) format:
|
||||
|
||||
```text
|
||||
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
|
||||
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
|
||||
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
|
||||
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
|
||||
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(&logrus_airbrake.AirbrakeHook{})
|
||||
|
||||
// Output to stderr instead of stdout, could also be a file.
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stderr
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging though logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
```go
|
||||
// Not the real implementation of the Airbrake hook. Just a simple sample.
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.AddHook(new(AirbrakeHook))
|
||||
}
|
||||
|
||||
type AirbrakeHook struct{}
|
||||
|
||||
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
|
||||
// the fields for the entry. See the Fields section of the README.
|
||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||
err := airbrake.Notify(entry.Data["error"].(error))
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
}).Info("Failed to send error to Airbrake")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// `Levels()` returns a slice of `Levels` the hook is fired for.
|
||||
func (hook *AirbrakeHook) Levels() []log.Level {
|
||||
return []log.Level{
|
||||
log.ErrorLevel,
|
||||
log.FatalLevel,
|
||||
log.PanicLevel,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.AddHook(new(logrus_airbrake.AirbrakeHook))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
|
||||
Send errors to an exception tracking service compatible with the Airbrake API.
|
||||
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
|
||||
Send errors to the Papertrail hosted logging service via UDP.
|
||||
|
||||
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
||||
Send errors to remote syslog server.
|
||||
Uses standard library `log/syslog` behind the scenes.
|
||||
|
||||
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
||||
Send errors to a channel in hipchat.
|
||||
|
||||
* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
|
||||
Send logs to Loggly (https://www.loggly.com/)
|
||||
|
||||
* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
|
||||
Hook for Slack chat.
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(logrus.JSONFormatter)
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(logrus.TextFormatter)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotated(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
|
||||
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
|
248
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go
generated
vendored
248
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go
generated
vendored
@ -1,248 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||
// passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||
Level Level
|
||||
|
||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, give a little extra room
|
||||
Data: make(Fields, 5),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a reader for the entry, which is a proxy to the formatter.
|
||||
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
return bytes.NewBuffer(serialized), err
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
reader, err := entry.Reader()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return reader.String(), err
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := Fields{}
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
data[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data}
|
||||
}
|
||||
|
||||
func (entry *Entry) log(level Level, msg string) {
|
||||
entry.Time = time.Now()
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
|
||||
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
reader, err := entry.Reader()
|
||||
if err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
|
||||
_, err = io.Copy(entry.Logger.Out, reader)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||
}
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.Warn(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.Fatal(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.Panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.Debug(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.Info(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.Warn(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.Error(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.Fatal(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.Panic(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
53
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
53
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
@ -1,53 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEntryPanicln(t *testing.T) {
|
||||
errBoom := fmt.Errorf("boom time")
|
||||
|
||||
defer func() {
|
||||
p := recover()
|
||||
assert.NotNil(t, p)
|
||||
|
||||
switch pVal := p.(type) {
|
||||
case *Entry:
|
||||
assert.Equal(t, "kaboom", pVal.Message)
|
||||
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||
default:
|
||||
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||
}
|
||||
}()
|
||||
|
||||
logger := New()
|
||||
logger.Out = &bytes.Buffer{}
|
||||
entry := NewEntry(logger)
|
||||
entry.WithField("err", errBoom).Panicln("kaboom")
|
||||
}
|
||||
|
||||
func TestEntryPanicf(t *testing.T) {
|
||||
errBoom := fmt.Errorf("boom again")
|
||||
|
||||
defer func() {
|
||||
p := recover()
|
||||
assert.NotNil(t, p)
|
||||
|
||||
switch pVal := p.(type) {
|
||||
case *Entry:
|
||||
assert.Equal(t, "kaboom true", pVal.Message)
|
||||
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||
default:
|
||||
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||
}
|
||||
}()
|
||||
|
||||
logger := New()
|
||||
logger.Out = &bytes.Buffer{}
|
||||
entry := NewEntry(logger)
|
||||
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
|
||||
}
|
40
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
40
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.JSONFormatter)
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"err": err,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "orca",
|
||||
"size": 9009,
|
||||
}).Panic("It's over 9000!")
|
||||
}
|
35
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
35
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
"github.com/tobi/airbrake-go"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
|
||||
}
|
||||
|
||||
func main() {
|
||||
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
|
||||
airbrake.ApiKey = "whatever"
|
||||
airbrake.Environment = "production"
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
182
Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go
generated
vendored
182
Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go
generated
vendored
@ -1,182 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Out = out
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Level = level
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
return std.Level
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
44
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go
generated
vendored
44
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go
generated
vendored
@ -1,44 +0,0 @@
|
||||
package logrus
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields) {
|
||||
_, ok := data["time"]
|
||||
if ok {
|
||||
data["fields.time"] = data["time"]
|
||||
}
|
||||
|
||||
_, ok = data["msg"]
|
||||
if ok {
|
||||
data["fields.msg"] = data["msg"]
|
||||
}
|
||||
|
||||
_, ok = data["level"]
|
||||
if ok {
|
||||
data["fields.level"] = data["level"]
|
||||
}
|
||||
}
|
88
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
88
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
@ -1,88 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// smallFields is a small size data set for benchmarking
|
||||
var smallFields = Fields{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
"one": "two",
|
||||
"three": "four",
|
||||
}
|
||||
|
||||
// largeFields is a large size data set for benchmarking
|
||||
var largeFields = Fields{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
"one": "two",
|
||||
"three": "four",
|
||||
"five": "six",
|
||||
"seven": "eight",
|
||||
"nine": "ten",
|
||||
"eleven": "twelve",
|
||||
"thirteen": "fourteen",
|
||||
"fifteen": "sixteen",
|
||||
"seventeen": "eighteen",
|
||||
"nineteen": "twenty",
|
||||
"a": "b",
|
||||
"c": "d",
|
||||
"e": "f",
|
||||
"g": "h",
|
||||
"i": "j",
|
||||
"k": "l",
|
||||
"m": "n",
|
||||
"o": "p",
|
||||
"q": "r",
|
||||
"s": "t",
|
||||
"u": "v",
|
||||
"w": "x",
|
||||
"y": "z",
|
||||
"this": "will",
|
||||
"make": "thirty",
|
||||
"entries": "yeah",
|
||||
}
|
||||
|
||||
func BenchmarkSmallTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkLargeTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
|
||||
}
|
||||
|
||||
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
|
||||
}
|
||||
|
||||
func BenchmarkSmallJSONFormatter(b *testing.B) {
|
||||
doBenchmark(b, &JSONFormatter{}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||
doBenchmark(b, &JSONFormatter{}, largeFields)
|
||||
}
|
||||
|
||||
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||
entry := &Entry{
|
||||
Time: time.Time{},
|
||||
Level: InfoLevel,
|
||||
Message: "message",
|
||||
Data: fields,
|
||||
}
|
||||
var d []byte
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
d, err = formatter.Format(entry)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(d)))
|
||||
}
|
||||
}
|
122
Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
122
Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
@ -1,122 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type TestHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *TestHook) Fire(entry *Entry) error {
|
||||
hook.Fired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *TestHook) Levels() []Level {
|
||||
return []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookFires(t *testing.T) {
|
||||
hook := new(TestHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
assert.Equal(t, hook.Fired, false)
|
||||
|
||||
log.Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
type ModifyHook struct {
|
||||
}
|
||||
|
||||
func (hook *ModifyHook) Fire(entry *Entry) error {
|
||||
entry.Data["wow"] = "whale"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ModifyHook) Levels() []Level {
|
||||
return []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookCanModifyEntry(t *testing.T) {
|
||||
hook := new(ModifyHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.WithField("wow", "elephant").Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["wow"], "whale")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanFireMultipleHooks(t *testing.T) {
|
||||
hook1 := new(ModifyHook)
|
||||
hook2 := new(TestHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook1)
|
||||
log.Hooks.Add(hook2)
|
||||
|
||||
log.WithField("wow", "elephant").Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["wow"], "whale")
|
||||
assert.Equal(t, hook2.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
type ErrorHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *ErrorHook) Fire(entry *Entry) error {
|
||||
hook.Fired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ErrorHook) Levels() []Level {
|
||||
return []Level{
|
||||
ErrorLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
||||
hook := new(ErrorHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||
hook := new(ErrorHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.Error("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
34
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go
generated
vendored
34
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go
generated
vendored
@ -1,34 +0,0 @@
|
||||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type levelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks levelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks levelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
generated
vendored
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
generated
vendored
@ -1,54 +0,0 @@
|
||||
package logrus_airbrake
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/tobi/airbrake-go"
|
||||
)
|
||||
|
||||
// AirbrakeHook to send exceptions to an exception-tracking service compatible
|
||||
// with the Airbrake API. You must set:
|
||||
// * airbrake.Endpoint
|
||||
// * airbrake.ApiKey
|
||||
// * airbrake.Environment (only sends exceptions when set to "production")
|
||||
//
|
||||
// Before using this hook, to send an error. Entries that trigger an Error,
|
||||
// Fatal or Panic should now include an "error" field to send to Airbrake.
|
||||
type AirbrakeHook struct{}
|
||||
|
||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||
if entry.Data["error"] == nil {
|
||||
entry.Logger.WithFields(logrus.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
|
||||
return nil
|
||||
}
|
||||
|
||||
err, ok := entry.Data["error"].(error)
|
||||
if !ok {
|
||||
entry.Logger.WithFields(logrus.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
|
||||
return nil
|
||||
}
|
||||
|
||||
airErr := airbrake.Notify(err)
|
||||
if airErr != nil {
|
||||
entry.Logger.WithFields(logrus.Fields{
|
||||
"source": "airbrake",
|
||||
"endpoint": airbrake.Endpoint,
|
||||
"error": airErr,
|
||||
}).Warn("Failed to send error to Airbrake")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *AirbrakeHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.PanicLevel,
|
||||
}
|
||||
}
|
28
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md
generated
vendored
28
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md
generated
vendored
@ -1,28 +0,0 @@
|
||||
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||
|
||||
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
|
||||
|
||||
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
|
||||
|
||||
## Usage
|
||||
|
||||
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
|
||||
|
||||
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/papertrail"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
55
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
55
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
@ -1,55 +0,0 @@
|
||||
package logrus_papertrail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
format = "Jan 2 15:04:05"
|
||||
)
|
||||
|
||||
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
|
||||
type PapertrailHook struct {
|
||||
Host string
|
||||
Port int
|
||||
AppName string
|
||||
UDPConn net.Conn
|
||||
}
|
||||
|
||||
// NewPapertrailHook creates a hook to be added to an instance of logger.
|
||||
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
|
||||
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
|
||||
return &PapertrailHook{host, port, appName, conn}, err
|
||||
}
|
||||
|
||||
// Fire is called when a log event is fired.
|
||||
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
|
||||
date := time.Now().Format(format)
|
||||
msg, _ := entry.String()
|
||||
payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg)
|
||||
|
||||
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels returns the available logging levels.
|
||||
func (hook *PapertrailHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.PanicLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.ErrorLevel,
|
||||
logrus.WarnLevel,
|
||||
logrus.InfoLevel,
|
||||
logrus.DebugLevel,
|
||||
}
|
||||
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
@ -1,26 +0,0 @@
|
||||
package logrus_papertrail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stvp/go-udp-testing"
|
||||
)
|
||||
|
||||
func TestWritingToUDP(t *testing.T) {
|
||||
port := 16661
|
||||
udp.SetAddr(fmt.Sprintf(":%d", port))
|
||||
|
||||
hook, err := NewPapertrailHook("localhost", port, "test")
|
||||
if err != nil {
|
||||
t.Errorf("Unable to connect to local UDP server.")
|
||||
}
|
||||
|
||||
log := logrus.New()
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
udp.ShouldReceive(t, "foo", func() {
|
||||
log.Info("foo")
|
||||
})
|
||||
}
|
61
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
generated
vendored
61
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
generated
vendored
@ -1,61 +0,0 @@
|
||||
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||
|
||||
[Sentry](https://getsentry.com) provides both self-hosted and hosted
|
||||
solutions for exception tracking.
|
||||
Both client and server are
|
||||
[open source](https://github.com/getsentry/sentry).
|
||||
|
||||
## Usage
|
||||
|
||||
Every sentry application defined on the server gets a different
|
||||
[DSN](https://www.getsentry.com/docs/). In the example below replace
|
||||
`YOUR_DSN` with the one created for your application.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/sentry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
|
||||
logrus.PanicLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.ErrorLevel,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Special fields
|
||||
|
||||
Some logrus fields have a special meaning in this hook,
|
||||
these are server_name and logger.
|
||||
When logs are sent to sentry these fields are treated differently.
|
||||
- server_name (also known as hostname) is the name of the server which
|
||||
is logging the event (hostname.example.com)
|
||||
- logger is the part of the application which is logging the event.
|
||||
In go this usually means setting it to the name of the package.
|
||||
|
||||
## Timeout
|
||||
|
||||
`Timeout` is the time the sentry hook will wait for a response
|
||||
from the sentry server.
|
||||
|
||||
If this time elapses with no response from
|
||||
the server an error will be returned.
|
||||
|
||||
If `Timeout` is set to 0 the SentryHook will not wait for a reply
|
||||
and will assume a correct delivery.
|
||||
|
||||
The SentryHook has a default timeout of `100 milliseconds` when created
|
||||
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
|
||||
|
||||
```go
|
||||
hook, _ := logrus_sentry.NewSentryHook(...)
|
||||
hook.Timeout = 20*time.Seconds
|
||||
```
|
100
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
generated
vendored
100
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
generated
vendored
@ -1,100 +0,0 @@
|
||||
package logrus_sentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/getsentry/raven-go"
|
||||
)
|
||||
|
||||
var (
|
||||
severityMap = map[logrus.Level]raven.Severity{
|
||||
logrus.DebugLevel: raven.DEBUG,
|
||||
logrus.InfoLevel: raven.INFO,
|
||||
logrus.WarnLevel: raven.WARNING,
|
||||
logrus.ErrorLevel: raven.ERROR,
|
||||
logrus.FatalLevel: raven.FATAL,
|
||||
logrus.PanicLevel: raven.FATAL,
|
||||
}
|
||||
)
|
||||
|
||||
func getAndDel(d logrus.Fields, key string) (string, bool) {
|
||||
var (
|
||||
ok bool
|
||||
v interface{}
|
||||
val string
|
||||
)
|
||||
if v, ok = d[key]; !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if val, ok = v.(string); !ok {
|
||||
return "", false
|
||||
}
|
||||
delete(d, key)
|
||||
return val, true
|
||||
}
|
||||
|
||||
// SentryHook delivers logs to a sentry server.
|
||||
type SentryHook struct {
|
||||
// Timeout sets the time to wait for a delivery error from the sentry server.
|
||||
// If this is set to zero the server will not wait for any response and will
|
||||
// consider the message correctly sent
|
||||
Timeout time.Duration
|
||||
|
||||
client *raven.Client
|
||||
levels []logrus.Level
|
||||
}
|
||||
|
||||
// NewSentryHook creates a hook to be added to an instance of logger
|
||||
// and initializes the raven client.
|
||||
// This method sets the timeout to 100 milliseconds.
|
||||
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
|
||||
client, err := raven.NewClient(DSN, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SentryHook{100 * time.Millisecond, client, levels}, nil
|
||||
}
|
||||
|
||||
// Called when an event should be sent to sentry
|
||||
// Special fields that sentry uses to give more information to the server
|
||||
// are extracted from entry.Data (if they are found)
|
||||
// These fields are: logger and server_name
|
||||
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
|
||||
packet := &raven.Packet{
|
||||
Message: entry.Message,
|
||||
Timestamp: raven.Timestamp(entry.Time),
|
||||
Level: severityMap[entry.Level],
|
||||
Platform: "go",
|
||||
}
|
||||
|
||||
d := entry.Data
|
||||
|
||||
if logger, ok := getAndDel(d, "logger"); ok {
|
||||
packet.Logger = logger
|
||||
}
|
||||
if serverName, ok := getAndDel(d, "server_name"); ok {
|
||||
packet.ServerName = serverName
|
||||
}
|
||||
packet.Extra = map[string]interface{}(d)
|
||||
|
||||
_, errCh := hook.client.Capture(packet, nil)
|
||||
timeout := hook.Timeout
|
||||
if timeout != 0 {
|
||||
timeoutCh := time.After(timeout)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-timeoutCh:
|
||||
return fmt.Errorf("no response from sentry server in %s", timeout)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels returns the available logging levels.
|
||||
func (hook *SentryHook) Levels() []logrus.Level {
|
||||
return hook.levels
|
||||
}
|
97
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
generated
vendored
97
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
generated
vendored
@ -1,97 +0,0 @@
|
||||
package logrus_sentry
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/getsentry/raven-go"
|
||||
)
|
||||
|
||||
const (
|
||||
message = "error message"
|
||||
server_name = "testserver.internal"
|
||||
logger_name = "test.logger"
|
||||
)
|
||||
|
||||
func getTestLogger() *logrus.Logger {
|
||||
l := logrus.New()
|
||||
l.Out = ioutil.Discard
|
||||
return l
|
||||
}
|
||||
|
||||
func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
|
||||
pch := make(chan *raven.Packet, 1)
|
||||
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
defer req.Body.Close()
|
||||
d := json.NewDecoder(req.Body)
|
||||
p := &raven.Packet{}
|
||||
err := d.Decode(p)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
pch <- p
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
fragments := strings.SplitN(s.URL, "://", 2)
|
||||
dsn := fmt.Sprintf(
|
||||
"%s://public:secret@%s/sentry/project-id",
|
||||
fragments[0],
|
||||
fragments[1],
|
||||
)
|
||||
tf(dsn, pch)
|
||||
}
|
||||
|
||||
func TestSpecialFields(t *testing.T) {
|
||||
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
||||
logger := getTestLogger()
|
||||
|
||||
hook, err := NewSentryHook(dsn, []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
logger.Hooks.Add(hook)
|
||||
logger.WithFields(logrus.Fields{
|
||||
"server_name": server_name,
|
||||
"logger": logger_name,
|
||||
}).Error(message)
|
||||
|
||||
packet := <-pch
|
||||
if packet.Logger != logger_name {
|
||||
t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
|
||||
}
|
||||
|
||||
if packet.ServerName != server_name {
|
||||
t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSentryHandler(t *testing.T) {
|
||||
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
||||
logger := getTestLogger()
|
||||
hook, err := NewSentryHook(dsn, []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
logger.Hooks.Add(hook)
|
||||
|
||||
logger.Error(message)
|
||||
packet := <-pch
|
||||
if packet.Message != message {
|
||||
t.Errorf("message should have been %s, was %s", message, packet.Message)
|
||||
}
|
||||
})
|
||||
}
|
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
@ -1,20 +0,0 @@
|
||||
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/Sirupsen/logrus"
|
||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
59
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
59
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
@ -1,59 +0,0 @@
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"log/syslog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// SyslogHook to send logs via syslog.
|
||||
type SyslogHook struct {
|
||||
Writer *syslog.Writer
|
||||
SyslogNetwork string
|
||||
SyslogRaddr string
|
||||
}
|
||||
|
||||
// Creates a hook to be added to an instance of logger. This is called with
|
||||
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
||||
// `if err == nil { log.Hooks.Add(hook) }`
|
||||
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
||||
w, err := syslog.Dial(network, raddr, priority, tag)
|
||||
return &SyslogHook{w, network, raddr}, err
|
||||
}
|
||||
|
||||
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
||||
line, err := entry.String()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch entry.Level {
|
||||
case logrus.PanicLevel:
|
||||
return hook.Writer.Crit(line)
|
||||
case logrus.FatalLevel:
|
||||
return hook.Writer.Crit(line)
|
||||
case logrus.ErrorLevel:
|
||||
return hook.Writer.Err(line)
|
||||
case logrus.WarnLevel:
|
||||
return hook.Writer.Warning(line)
|
||||
case logrus.InfoLevel:
|
||||
return hook.Writer.Info(line)
|
||||
case logrus.DebugLevel:
|
||||
return hook.Writer.Debug(line)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (hook *SyslogHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.PanicLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.ErrorLevel,
|
||||
logrus.WarnLevel,
|
||||
logrus.InfoLevel,
|
||||
logrus.DebugLevel,
|
||||
}
|
||||
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
@ -1,26 +0,0 @@
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"log/syslog"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||
log := logrus.New()
|
||||
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unable to connect to local syslog.")
|
||||
}
|
||||
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
for _, level := range hook.Levels() {
|
||||
if len(log.Hooks[level]) != 1 {
|
||||
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Congratulations!")
|
||||
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
@ -1,26 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSONFormatter struct{}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
data["time"] = entry.Time.Format(time.RFC3339)
|
||||
data["msg"] = entry.Message
|
||||
data["level"] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
161
Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go
generated
vendored
161
Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go
generated
vendored
@ -1,161 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stdout`. You can also set this to
|
||||
// something more adventorous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks levelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged. `logrus.Debug` is useful in
|
||||
Level Level
|
||||
// Used to sync writing to the log.
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(JSONFormatter),
|
||||
// Hooks: make(levelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stdout,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(levelHooks),
|
||||
Level: InfoLevel,
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a field to the log entry, note that you it doesn't log until you call
|
||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||
// Ff you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
return NewEntry(logger).WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
return NewEntry(logger).WithFields(fields)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Debugf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
NewEntry(logger).Infof(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Printf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Panicf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
NewEntry(logger).Debug(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
NewEntry(logger).Error(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
NewEntry(logger).Fatal(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
NewEntry(logger).Panic(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
NewEntry(logger).Debugln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
NewEntry(logger).Infoln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
NewEntry(logger).Println(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
NewEntry(logger).Errorln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
NewEntry(logger).Fatalln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
NewEntry(logger).Panicln(args...)
|
||||
}
|
94
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go
generated
vendored
94
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go
generated
vendored
@ -1,94 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint8
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch lvl {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var _ StdLogger = &log.Logger{}
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
283
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
283
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
@ -1,283 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
log(logger)
|
||||
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assertions(fields)
|
||||
}
|
||||
|
||||
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = &TextFormatter{
|
||||
DisableColors: true,
|
||||
}
|
||||
|
||||
log(logger)
|
||||
|
||||
fields := make(map[string]string)
|
||||
for _, kv := range strings.Split(buffer.String(), " ") {
|
||||
if !strings.Contains(kv, "=") {
|
||||
continue
|
||||
}
|
||||
kvArr := strings.Split(kv, "=")
|
||||
key := strings.TrimSpace(kvArr[0])
|
||||
val := kvArr[1]
|
||||
if kvArr[1][0] == '"' {
|
||||
var err error
|
||||
val, err = strconv.Unquote(val)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
fields[key] = val
|
||||
}
|
||||
assertions(fields)
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWarn(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Warn("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "warning")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln("test", "test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test test")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln("test", 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln(10, 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "10 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln(10, 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "10 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test", 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test", "test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "testtest")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
localLog := logger.WithFields(Fields{
|
||||
"key1": "value1",
|
||||
})
|
||||
|
||||
localLog.WithField("key2", "value2").Info("test")
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "value2", fields["key2"])
|
||||
assert.Equal(t, "value1", fields["key1"])
|
||||
|
||||
buffer = bytes.Buffer{}
|
||||
fields = Fields{}
|
||||
localLog.Info("test")
|
||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, ok := fields["key2"]
|
||||
assert.Equal(t, false, ok)
|
||||
assert.Equal(t, "value1", fields["key1"])
|
||||
}
|
||||
|
||||
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("msg", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("msg", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["fields.msg"], "hello")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("time", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["fields.time"], "hello")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("level", 1).Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
assert.Equal(t, fields["fields.level"], 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
|
||||
LogAndAssertText(t, func(log *Logger) {
|
||||
ll := log.WithField("herp", "derp")
|
||||
ll.Info("hello")
|
||||
ll.Info("bye")
|
||||
}, func(fields map[string]string) {
|
||||
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
|
||||
if _, ok := fields[fieldName]; ok {
|
||||
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
llog := logger.WithField("context", "eating raw fish")
|
||||
|
||||
llog.Info("looks delicious")
|
||||
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.NoError(t, err, "should have decoded first message")
|
||||
assert.Len(t, fields, 4, "should only have msg/time/level/context fields")
|
||||
assert.Equal(t, fields["msg"], "looks delicious")
|
||||
assert.Equal(t, fields["context"], "eating raw fish")
|
||||
|
||||
buffer.Reset()
|
||||
|
||||
llog.Warn("omg it is!")
|
||||
|
||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.NoError(t, err, "should have decoded second message")
|
||||
assert.Len(t, fields, 4, "should only have msg/time/level/context fields")
|
||||
assert.Equal(t, fields["msg"], "omg it is!")
|
||||
assert.Equal(t, fields["context"], "eating raw fish")
|
||||
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
||||
|
||||
}
|
||||
|
||||
func TestConvertLevelToString(t *testing.T) {
|
||||
assert.Equal(t, "debug", DebugLevel.String())
|
||||
assert.Equal(t, "info", InfoLevel.String())
|
||||
assert.Equal(t, "warning", WarnLevel.String())
|
||||
assert.Equal(t, "error", ErrorLevel.String())
|
||||
assert.Equal(t, "fatal", FatalLevel.String())
|
||||
assert.Equal(t, "panic", PanicLevel.String())
|
||||
}
|
||||
|
||||
func TestParseLevel(t *testing.T) {
|
||||
l, err := ParseLevel("panic")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, PanicLevel, l)
|
||||
|
||||
l, err = ParseLevel("fatal")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, FatalLevel, l)
|
||||
|
||||
l, err = ParseLevel("error")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ErrorLevel, l)
|
||||
|
||||
l, err = ParseLevel("warn")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, WarnLevel, l)
|
||||
|
||||
l, err = ParseLevel("warning")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, WarnLevel, l)
|
||||
|
||||
l, err = ParseLevel("info")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, InfoLevel, l)
|
||||
|
||||
l, err = ParseLevel("debug")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, DebugLevel, l)
|
||||
|
||||
l, err = ParseLevel("invalid")
|
||||
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
|
||||
}
|
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go
generated
vendored
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go
generated
vendored
@ -1,12 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 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 logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go
generated
vendored
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go
generated
vendored
@ -1,20 +0,0 @@
|
||||
/*
|
||||
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
||||
*/
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios struct {
|
||||
Iflag uint32
|
||||
Oflag uint32
|
||||
Cflag uint32
|
||||
Lflag uint32
|
||||
Cc [20]uint8
|
||||
Ispeed uint32
|
||||
Ospeed uint32
|
||||
}
|
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
@ -1,12 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 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 logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
type Termios syscall.Termios
|
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
@ -1,21 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 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.
|
||||
|
||||
// +build linux,!appengine darwin freebsd
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
fd := syscall.Stdout
|
||||
var termios Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
27
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
27
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
@ -1,27 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
fd := syscall.Stdout
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
124
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
124
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
@ -1,124 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
isTerminal bool
|
||||
noQuoteNeeded *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
isTerminal = IsTerminal()
|
||||
}
|
||||
|
||||
func miniTS() int {
|
||||
return int(time.Since(baseTimestamp) / time.Second)
|
||||
}
|
||||
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
DisableColors bool
|
||||
// Set to true to disable timestamp logging (useful when the output
|
||||
// is redirected to a logging system already adding a timestamp)
|
||||
DisableTimestamp bool
|
||||
}
|
||||
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
|
||||
var keys []string
|
||||
for k := range entry.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
prefixFieldClashes(entry.Data)
|
||||
|
||||
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||
|
||||
if isColored {
|
||||
printColored(b, entry, keys)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
|
||||
}
|
||||
f.appendKeyValue(b, "level", entry.Level.String())
|
||||
f.appendKeyValue(b, "msg", entry.Message)
|
||||
for _, key := range keys {
|
||||
f.appendKeyValue(b, key, entry.Data[key])
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuoting(text string) bool {
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch < '9') ||
|
||||
ch == '-' || ch == '.') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
if needsQuoting(value.(string)) {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||
}
|
||||
case error:
|
||||
if needsQuoting(value.(error).Error()) {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||
}
|
||||
}
|
33
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go
generated
vendored
33
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQuoting(t *testing.T) {
|
||||
tf := &TextFormatter{DisableColors: true}
|
||||
|
||||
checkQuoting := func(q bool, value interface{}) {
|
||||
b, _ := tf.Format(WithField("test", value))
|
||||
idx := bytes.Index(b, ([]byte)("test="))
|
||||
cont := bytes.Contains(b[idx+5:], []byte{'"'})
|
||||
if cont != q {
|
||||
if q {
|
||||
t.Errorf("quoting expected for: %#v", value)
|
||||
} else {
|
||||
t.Errorf("quoting not expected for: %#v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkQuoting(false, "abcd")
|
||||
checkQuoting(false, "v1.0")
|
||||
checkQuoting(true, "/foobar")
|
||||
checkQuoting(true, "x y")
|
||||
checkQuoting(true, "x,y")
|
||||
checkQuoting(false, errors.New("invalid"))
|
||||
checkQuoting(true, errors.New("invalid argument"))
|
||||
}
|
23
Godeps/_workspace/src/github.com/armon/consul-api/.gitignore
generated
vendored
23
Godeps/_workspace/src/github.com/armon/consul-api/.gitignore
generated
vendored
@ -1,23 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
362
Godeps/_workspace/src/github.com/armon/consul-api/LICENSE
generated
vendored
362
Godeps/_workspace/src/github.com/armon/consul-api/LICENSE
generated
vendored
@ -1,362 +0,0 @@
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
39
Godeps/_workspace/src/github.com/armon/consul-api/README.md
generated
vendored
39
Godeps/_workspace/src/github.com/armon/consul-api/README.md
generated
vendored
@ -1,39 +0,0 @@
|
||||
consul-api
|
||||
==========
|
||||
|
||||
This package provides the `consulapi` package which attempts to
|
||||
provide programmatic access to the full Consul API.
|
||||
|
||||
Currently, all of the Consul APIs included in version 0.3 are supported.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
The full documentation is available on [Godoc](http://godoc.org/github.com/armon/consul-api)
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Below is an example of using the Consul client:
|
||||
|
||||
```go
|
||||
// Get a new client, with KV endpoints
|
||||
client, _ := consulapi.NewClient(consulapi.DefaultConfig())
|
||||
kv := client.KV()
|
||||
|
||||
// PUT a new KV pair
|
||||
p := &consulapi.KVPair{Key: "foo", Value: []byte("test")}
|
||||
_, err := kv.Put(p, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Lookup the pair
|
||||
pair, _, err := kv.Get("foo", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("KV: %v", pair)
|
||||
|
||||
```
|
||||
|
140
Godeps/_workspace/src/github.com/armon/consul-api/acl.go
generated
vendored
140
Godeps/_workspace/src/github.com/armon/consul-api/acl.go
generated
vendored
@ -1,140 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
const (
|
||||
// ACLCLientType is the client type token
|
||||
ACLClientType = "client"
|
||||
|
||||
// ACLManagementType is the management type token
|
||||
ACLManagementType = "management"
|
||||
)
|
||||
|
||||
// ACLEntry is used to represent an ACL entry
|
||||
type ACLEntry struct {
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Rules string
|
||||
}
|
||||
|
||||
// ACL can be used to query the ACL endpoints
|
||||
type ACL struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// ACL returns a handle to the ACL endpoints
|
||||
func (c *Client) ACL() *ACL {
|
||||
return &ACL{c}
|
||||
}
|
||||
|
||||
// Create is used to generate a new token with the given parameters
|
||||
func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/create")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = acl
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Update is used to update the rules of an existing token
|
||||
func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/update")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = acl
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Destroy is used to destroy a given ACL token ID
|
||||
func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Clone is used to return a new token cloned from an existing one
|
||||
func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Info is used to query for information about an ACL token
|
||||
func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/info/"+id)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*ACLEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List is used to get all the ACL tokens
|
||||
func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/list")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*ACLEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
140
Godeps/_workspace/src/github.com/armon/consul-api/acl_test.go
generated
vendored
140
Godeps/_workspace/src/github.com/armon/consul-api/acl_test.go
generated
vendored
@ -1,140 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ROOT is a management token for the tests
|
||||
var CONSUL_ROOT string
|
||||
|
||||
func init() {
|
||||
CONSUL_ROOT = os.Getenv("CONSUL_ROOT")
|
||||
}
|
||||
|
||||
func TestACL_CreateDestroy(t *testing.T) {
|
||||
if CONSUL_ROOT == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
c := makeClient(t)
|
||||
c.config.Token = CONSUL_ROOT
|
||||
acl := c.ACL()
|
||||
|
||||
ae := ACLEntry{
|
||||
Name: "API test",
|
||||
Type: ACLClientType,
|
||||
Rules: `key "" { policy = "deny" }`,
|
||||
}
|
||||
|
||||
id, wm, err := acl.Create(&ae, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if wm.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", wm)
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
t.Fatalf("invalid: %v", id)
|
||||
}
|
||||
|
||||
ae2, _, err := acl.Info(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if ae2.Name != ae.Name || ae2.Type != ae.Type || ae2.Rules != ae.Rules {
|
||||
t.Fatalf("Bad: %#v", ae2)
|
||||
}
|
||||
|
||||
wm, err = acl.Destroy(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if wm.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", wm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestACL_CloneDestroy(t *testing.T) {
|
||||
if CONSUL_ROOT == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
c := makeClient(t)
|
||||
c.config.Token = CONSUL_ROOT
|
||||
acl := c.ACL()
|
||||
|
||||
id, wm, err := acl.Clone(CONSUL_ROOT, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if wm.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", wm)
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
t.Fatalf("invalid: %v", id)
|
||||
}
|
||||
|
||||
wm, err = acl.Destroy(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if wm.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", wm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestACL_Info(t *testing.T) {
|
||||
if CONSUL_ROOT == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
c := makeClient(t)
|
||||
c.config.Token = CONSUL_ROOT
|
||||
acl := c.ACL()
|
||||
|
||||
ae, qm, err := acl.Info(CONSUL_ROOT, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
|
||||
if ae == nil || ae.ID != CONSUL_ROOT || ae.Type != ACLManagementType {
|
||||
t.Fatalf("bad: %#v", ae)
|
||||
}
|
||||
}
|
||||
|
||||
func TestACL_List(t *testing.T) {
|
||||
if CONSUL_ROOT == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
c := makeClient(t)
|
||||
c.config.Token = CONSUL_ROOT
|
||||
acl := c.ACL()
|
||||
|
||||
acls, qm, err := acl.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(acls) < 2 {
|
||||
t.Fatalf("bad: %v", acls)
|
||||
}
|
||||
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
}
|
272
Godeps/_workspace/src/github.com/armon/consul-api/agent.go
generated
vendored
272
Godeps/_workspace/src/github.com/armon/consul-api/agent.go
generated
vendored
@ -1,272 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AgentCheck represents a check known to the agent
|
||||
type AgentCheck struct {
|
||||
Node string
|
||||
CheckID string
|
||||
Name string
|
||||
Status string
|
||||
Notes string
|
||||
Output string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// AgentService represents a service known to the agent
|
||||
type AgentService struct {
|
||||
ID string
|
||||
Service string
|
||||
Tags []string
|
||||
Port int
|
||||
}
|
||||
|
||||
// AgentMember represents a cluster member known to the agent
|
||||
type AgentMember struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port uint16
|
||||
Tags map[string]string
|
||||
Status int
|
||||
ProtocolMin uint8
|
||||
ProtocolMax uint8
|
||||
ProtocolCur uint8
|
||||
DelegateMin uint8
|
||||
DelegateMax uint8
|
||||
DelegateCur uint8
|
||||
}
|
||||
|
||||
// AgentServiceRegistration is used to register a new service
|
||||
type AgentServiceRegistration struct {
|
||||
ID string `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Port int `json:",omitempty"`
|
||||
Check *AgentServiceCheck
|
||||
}
|
||||
|
||||
// AgentCheckRegistration is used to register a new check
|
||||
type AgentCheckRegistration struct {
|
||||
ID string `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
Notes string `json:",omitempty"`
|
||||
AgentServiceCheck
|
||||
}
|
||||
|
||||
// AgentServiceCheck is used to create an associated
|
||||
// check for a service
|
||||
type AgentServiceCheck struct {
|
||||
Script string `json:",omitempty"`
|
||||
Interval string `json:",omitempty"`
|
||||
TTL string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Agent can be used to query the Agent endpoints
|
||||
type Agent struct {
|
||||
c *Client
|
||||
|
||||
// cache the node name
|
||||
nodeName string
|
||||
}
|
||||
|
||||
// Agent returns a handle to the agent endpoints
|
||||
func (c *Client) Agent() *Agent {
|
||||
return &Agent{c: c}
|
||||
}
|
||||
|
||||
// Self is used to query the agent we are speaking to for
|
||||
// information about itself
|
||||
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/self")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]map[string]interface{}
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NodeName is used to get the node name of the agent
|
||||
func (a *Agent) NodeName() (string, error) {
|
||||
if a.nodeName != "" {
|
||||
return a.nodeName, nil
|
||||
}
|
||||
info, err := a.Self()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := info["Config"]["NodeName"].(string)
|
||||
a.nodeName = name
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Checks returns the locally registered checks
|
||||
func (a *Agent) Checks() (map[string]*AgentCheck, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/checks")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]*AgentCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Services returns the locally registered services
|
||||
func (a *Agent) Services() (map[string]*AgentService, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/services")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]*AgentService
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Members returns the known gossip members. The WAN
|
||||
// flag can be used to query a server for WAN members.
|
||||
func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/members")
|
||||
if wan {
|
||||
r.params.Set("wan", "1")
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []*AgentMember
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ServiceRegister is used to register a new service with
|
||||
// the local agent
|
||||
func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/register")
|
||||
r.obj = service
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceDeregister is used to deregister a service with
|
||||
// the local agent
|
||||
func (a *Agent) ServiceDeregister(serviceID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PassTTL is used to set a TTL check to the passing state
|
||||
func (a *Agent) PassTTL(checkID, note string) error {
|
||||
return a.UpdateTTL(checkID, note, "pass")
|
||||
}
|
||||
|
||||
// WarnTTL is used to set a TTL check to the warning state
|
||||
func (a *Agent) WarnTTL(checkID, note string) error {
|
||||
return a.UpdateTTL(checkID, note, "warn")
|
||||
}
|
||||
|
||||
// FailTTL is used to set a TTL check to the failing state
|
||||
func (a *Agent) FailTTL(checkID, note string) error {
|
||||
return a.UpdateTTL(checkID, note, "fail")
|
||||
}
|
||||
|
||||
// UpdateTTL is used to update the TTL of a check
|
||||
func (a *Agent) UpdateTTL(checkID, note, status string) error {
|
||||
switch status {
|
||||
case "pass":
|
||||
case "warn":
|
||||
case "fail":
|
||||
default:
|
||||
return fmt.Errorf("Invalid status: %s", status)
|
||||
}
|
||||
endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
|
||||
r := a.c.newRequest("PUT", endpoint)
|
||||
r.params.Set("note", note)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckRegister is used to register a new check with
|
||||
// the local agent
|
||||
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/check/register")
|
||||
r.obj = check
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDeregister is used to deregister a check with
|
||||
// the local agent
|
||||
func (a *Agent) CheckDeregister(checkID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Join is used to instruct the agent to attempt a join to
|
||||
// another cluster member
|
||||
func (a *Agent) Join(addr string, wan bool) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
|
||||
if wan {
|
||||
r.params.Set("wan", "1")
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceLeave is used to have the agent eject a failed node
|
||||
func (a *Agent) ForceLeave(node string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
162
Godeps/_workspace/src/github.com/armon/consul-api/agent_test.go
generated
vendored
162
Godeps/_workspace/src/github.com/armon/consul-api/agent_test.go
generated
vendored
@ -1,162 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAgent_Self(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
info, err := agent.Self()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
name := info["Config"]["NodeName"]
|
||||
if name == "" {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Members(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
members, err := agent.Members(false)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(members) != 1 {
|
||||
t.Fatalf("bad: %v", members)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Services(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
reg := &AgentServiceRegistration{
|
||||
Name: "foo",
|
||||
Tags: []string{"bar", "baz"},
|
||||
Port: 8000,
|
||||
Check: &AgentServiceCheck{
|
||||
TTL: "15s",
|
||||
},
|
||||
}
|
||||
if err := agent.ServiceRegister(reg); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
services, err := agent.Services()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, ok := services["foo"]; !ok {
|
||||
t.Fatalf("missing service: %v", services)
|
||||
}
|
||||
|
||||
checks, err := agent.Checks()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, ok := checks["service:foo"]; !ok {
|
||||
t.Fatalf("missing check: %v", checks)
|
||||
}
|
||||
|
||||
if err := agent.ServiceDeregister("foo"); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_SetTTLStatus(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
reg := &AgentServiceRegistration{
|
||||
Name: "foo",
|
||||
Check: &AgentServiceCheck{
|
||||
TTL: "15s",
|
||||
},
|
||||
}
|
||||
if err := agent.ServiceRegister(reg); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if err := agent.WarnTTL("service:foo", "test"); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checks, err := agent.Checks()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
chk, ok := checks["service:foo"]
|
||||
if !ok {
|
||||
t.Fatalf("missing check: %v", checks)
|
||||
}
|
||||
if chk.Status != "warning" {
|
||||
t.Fatalf("Bad: %#v", chk)
|
||||
}
|
||||
if chk.Output != "test" {
|
||||
t.Fatalf("Bad: %#v", chk)
|
||||
}
|
||||
|
||||
if err := agent.ServiceDeregister("foo"); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Checks(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
reg := &AgentCheckRegistration{
|
||||
Name: "foo",
|
||||
}
|
||||
reg.TTL = "15s"
|
||||
if err := agent.CheckRegister(reg); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checks, err := agent.Checks()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, ok := checks["foo"]; !ok {
|
||||
t.Fatalf("missing check: %v", checks)
|
||||
}
|
||||
|
||||
if err := agent.CheckDeregister("foo"); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Join(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
info, err := agent.Self()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Join ourself
|
||||
addr := info["Config"]["AdvertiseAddr"].(string)
|
||||
err = agent.Join(addr, false)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_ForceLeave(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
|
||||
// Eject somebody
|
||||
err := agent.ForceLeave("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
305
Godeps/_workspace/src/github.com/armon/consul-api/api.go
generated
vendored
305
Godeps/_workspace/src/github.com/armon/consul-api/api.go
generated
vendored
@ -1,305 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// QueryOptions are used to parameterize a query
|
||||
type QueryOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
// by the Config
|
||||
Datacenter string
|
||||
|
||||
// AllowStale allows any Consul server (non-leader) to service
|
||||
// a read. This allows for lower latency and higher throughput
|
||||
AllowStale bool
|
||||
|
||||
// RequireConsistent forces the read to be fully consistent.
|
||||
// This is more expensive but prevents ever performing a stale
|
||||
// read.
|
||||
RequireConsistent bool
|
||||
|
||||
// WaitIndex is used to enable a blocking query. Waits
|
||||
// until the timeout or the next index is reached
|
||||
WaitIndex uint64
|
||||
|
||||
// WaitTime is used to bound the duration of a wait.
|
||||
// Defaults to that of the Config, but can be overriden.
|
||||
WaitTime time.Duration
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// WriteOptions are used to parameterize a write
|
||||
type WriteOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
// by the Config
|
||||
Datacenter string
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// QueryMeta is used to return meta data about a query
|
||||
type QueryMeta struct {
|
||||
// LastIndex. This can be used as a WaitIndex to perform
|
||||
// a blocking query
|
||||
LastIndex uint64
|
||||
|
||||
// Time of last contact from the leader for the
|
||||
// server servicing the request
|
||||
LastContact time.Duration
|
||||
|
||||
// Is there a known leader
|
||||
KnownLeader bool
|
||||
|
||||
// How long did the request take
|
||||
RequestTime time.Duration
|
||||
}
|
||||
|
||||
// WriteMeta is used to return meta data about a write
|
||||
type WriteMeta struct {
|
||||
// How long did the request take
|
||||
RequestTime time.Duration
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of a client
|
||||
type Config struct {
|
||||
// Address is the address of the Consul server
|
||||
Address string
|
||||
|
||||
// Scheme is the URI scheme for the Consul server
|
||||
Scheme string
|
||||
|
||||
// Datacenter to use. If not provided, the default agent datacenter is used.
|
||||
Datacenter string
|
||||
|
||||
// HttpClient is the client to use. Default will be
|
||||
// used if not provided.
|
||||
HttpClient *http.Client
|
||||
|
||||
// WaitTime limits how long a Watch will block. If not provided,
|
||||
// the agent default values will be used.
|
||||
WaitTime time.Duration
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// DefaultConfig returns a default configuration for the client
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Address: "127.0.0.1:8500",
|
||||
Scheme: "http",
|
||||
HttpClient: http.DefaultClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Client provides a client to the Consul API
|
||||
type Client struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// NewClient returns a new client
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
// bootstrap the config
|
||||
defConfig := DefaultConfig()
|
||||
|
||||
if len(config.Address) == 0 {
|
||||
config.Address = defConfig.Address
|
||||
}
|
||||
|
||||
if len(config.Scheme) == 0 {
|
||||
config.Scheme = defConfig.Scheme
|
||||
}
|
||||
|
||||
if config.HttpClient == nil {
|
||||
config.HttpClient = defConfig.HttpClient
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
config: *config,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// request is used to help build up a request
|
||||
type request struct {
|
||||
config *Config
|
||||
method string
|
||||
url *url.URL
|
||||
params url.Values
|
||||
body io.Reader
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
// setQueryOptions is used to annotate the request with
|
||||
// additional query options
|
||||
func (r *request) setQueryOptions(q *QueryOptions) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.Datacenter != "" {
|
||||
r.params.Set("dc", q.Datacenter)
|
||||
}
|
||||
if q.AllowStale {
|
||||
r.params.Set("stale", "")
|
||||
}
|
||||
if q.RequireConsistent {
|
||||
r.params.Set("consistent", "")
|
||||
}
|
||||
if q.WaitIndex != 0 {
|
||||
r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
|
||||
}
|
||||
if q.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(q.WaitTime))
|
||||
}
|
||||
if q.Token != "" {
|
||||
r.params.Set("token", q.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// durToMsec converts a duration to a millisecond specified string
|
||||
func durToMsec(dur time.Duration) string {
|
||||
return fmt.Sprintf("%dms", dur/time.Millisecond)
|
||||
}
|
||||
|
||||
// setWriteOptions is used to annotate the request with
|
||||
// additional write options
|
||||
func (r *request) setWriteOptions(q *WriteOptions) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.Datacenter != "" {
|
||||
r.params.Set("dc", q.Datacenter)
|
||||
}
|
||||
if q.Token != "" {
|
||||
r.params.Set("token", q.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// toHTTP converts the request to an HTTP request
|
||||
func (r *request) toHTTP() (*http.Request, error) {
|
||||
// Encode the query parameters
|
||||
r.url.RawQuery = r.params.Encode()
|
||||
|
||||
// Get the url sring
|
||||
urlRaw := r.url.String()
|
||||
|
||||
// Check if we should encode the body
|
||||
if r.body == nil && r.obj != nil {
|
||||
if b, err := encodeBody(r.obj); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
r.body = b
|
||||
}
|
||||
}
|
||||
|
||||
// Create the HTTP request
|
||||
return http.NewRequest(r.method, urlRaw, r.body)
|
||||
}
|
||||
|
||||
// newRequest is used to create a new request
|
||||
func (c *Client) newRequest(method, path string) *request {
|
||||
r := &request{
|
||||
config: &c.config,
|
||||
method: method,
|
||||
url: &url.URL{
|
||||
Scheme: c.config.Scheme,
|
||||
Host: c.config.Address,
|
||||
Path: path,
|
||||
},
|
||||
params: make(map[string][]string),
|
||||
}
|
||||
if c.config.Datacenter != "" {
|
||||
r.params.Set("dc", c.config.Datacenter)
|
||||
}
|
||||
if c.config.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(r.config.WaitTime))
|
||||
}
|
||||
if c.config.Token != "" {
|
||||
r.params.Set("token", r.config.Token)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// doRequest runs a request with our client
|
||||
func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) {
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := c.config.HttpClient.Do(req)
|
||||
diff := time.Now().Sub(start)
|
||||
return diff, resp, err
|
||||
}
|
||||
|
||||
// parseQueryMeta is used to help parse query meta-data
|
||||
func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
|
||||
header := resp.Header
|
||||
|
||||
// Parse the X-Consul-Index
|
||||
index, err := strconv.ParseUint(header.Get("X-Consul-Index"), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse X-Consul-Index: %v", err)
|
||||
}
|
||||
q.LastIndex = index
|
||||
|
||||
// Parse the X-Consul-LastContact
|
||||
last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err)
|
||||
}
|
||||
q.LastContact = time.Duration(last) * time.Millisecond
|
||||
|
||||
// Parse the X-Consul-KnownLeader
|
||||
switch header.Get("X-Consul-KnownLeader") {
|
||||
case "true":
|
||||
q.KnownLeader = true
|
||||
default:
|
||||
q.KnownLeader = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBody is used to JSON decode a body
|
||||
func decodeBody(resp *http.Response, out interface{}) error {
|
||||
defer resp.Body.Close()
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
return dec.Decode(out)
|
||||
}
|
||||
|
||||
// encodeBody is used to encode a request body
|
||||
func encodeBody(obj interface{}) (io.Reader, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// requireOK is used to wrap doRequest and check for a 200
|
||||
func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) {
|
||||
if e != nil {
|
||||
return d, resp, e
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, resp.Body)
|
||||
return d, resp, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
|
||||
}
|
||||
return d, resp, e
|
||||
}
|
126
Godeps/_workspace/src/github.com/armon/consul-api/api_test.go
generated
vendored
126
Godeps/_workspace/src/github.com/armon/consul-api/api_test.go
generated
vendored
@ -1,126 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func makeClient(t *testing.T) *Client {
|
||||
conf := DefaultConfig()
|
||||
client, err := NewClient(conf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func testKey() string {
|
||||
buf := make([]byte, 16)
|
||||
if _, err := crand.Read(buf); err != nil {
|
||||
panic(fmt.Errorf("Failed to read random bytes: %v", err))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
|
||||
buf[0:4],
|
||||
buf[4:6],
|
||||
buf[6:8],
|
||||
buf[8:10],
|
||||
buf[10:16])
|
||||
}
|
||||
|
||||
func TestSetQueryOptions(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
r := c.newRequest("GET", "/v1/kv/foo")
|
||||
q := &QueryOptions{
|
||||
Datacenter: "foo",
|
||||
AllowStale: true,
|
||||
RequireConsistent: true,
|
||||
WaitIndex: 1000,
|
||||
WaitTime: 100 * time.Second,
|
||||
Token: "12345",
|
||||
}
|
||||
r.setQueryOptions(q)
|
||||
|
||||
if r.params.Get("dc") != "foo" {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
if _, ok := r.params["stale"]; !ok {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
if _, ok := r.params["consistent"]; !ok {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
if r.params.Get("index") != "1000" {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
if r.params.Get("wait") != "100000ms" {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
if r.params.Get("token") != "12345" {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetWriteOptions(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
r := c.newRequest("GET", "/v1/kv/foo")
|
||||
q := &WriteOptions{
|
||||
Datacenter: "foo",
|
||||
Token: "23456",
|
||||
}
|
||||
r.setWriteOptions(q)
|
||||
|
||||
if r.params.Get("dc") != "foo" {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
if r.params.Get("token") != "23456" {
|
||||
t.Fatalf("bad: %v", r.params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestToHTTP(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
r := c.newRequest("DELETE", "/v1/kv/foo")
|
||||
q := &QueryOptions{
|
||||
Datacenter: "foo",
|
||||
}
|
||||
r.setQueryOptions(q)
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if req.Method != "DELETE" {
|
||||
t.Fatalf("bad: %v", req)
|
||||
}
|
||||
if req.URL.String() != "http://127.0.0.1:8500/v1/kv/foo?dc=foo" {
|
||||
t.Fatalf("bad: %v", req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseQueryMeta(t *testing.T) {
|
||||
resp := &http.Response{
|
||||
Header: make(map[string][]string),
|
||||
}
|
||||
resp.Header.Set("X-Consul-Index", "12345")
|
||||
resp.Header.Set("X-Consul-LastContact", "80")
|
||||
resp.Header.Set("X-Consul-KnownLeader", "true")
|
||||
|
||||
qm := &QueryMeta{}
|
||||
if err := parseQueryMeta(resp, qm); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if qm.LastIndex != 12345 {
|
||||
t.Fatalf("Bad: %v", qm)
|
||||
}
|
||||
if qm.LastContact != 80*time.Millisecond {
|
||||
t.Fatalf("Bad: %v", qm)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("Bad: %v", qm)
|
||||
}
|
||||
}
|
181
Godeps/_workspace/src/github.com/armon/consul-api/catalog.go
generated
vendored
181
Godeps/_workspace/src/github.com/armon/consul-api/catalog.go
generated
vendored
@ -1,181 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
type Node struct {
|
||||
Node string
|
||||
Address string
|
||||
}
|
||||
|
||||
type CatalogService struct {
|
||||
Node string
|
||||
Address string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
ServiceTags []string
|
||||
ServicePort int
|
||||
}
|
||||
|
||||
type CatalogNode struct {
|
||||
Node *Node
|
||||
Services map[string]*AgentService
|
||||
}
|
||||
|
||||
type CatalogRegistration struct {
|
||||
Node string
|
||||
Address string
|
||||
Datacenter string
|
||||
Service *AgentService
|
||||
Check *AgentCheck
|
||||
}
|
||||
|
||||
type CatalogDeregistration struct {
|
||||
Node string
|
||||
Address string
|
||||
Datacenter string
|
||||
ServiceID string
|
||||
CheckID string
|
||||
}
|
||||
|
||||
// Catalog can be used to query the Catalog endpoints
|
||||
type Catalog struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Catalog returns a handle to the catalog endpoints
|
||||
func (c *Client) Catalog() *Catalog {
|
||||
return &Catalog{c}
|
||||
}
|
||||
|
||||
func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("PUT", "/v1/catalog/register")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = reg
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("PUT", "/v1/catalog/deregister")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = dereg
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Datacenters is used to query for all the known datacenters
|
||||
func (c *Catalog) Datacenters() ([]string, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/datacenters")
|
||||
_, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []string
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Nodes is used to query all the known nodes
|
||||
func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/nodes")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*Node
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Services is used to query for all known services
|
||||
func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/services")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out map[string][]string
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Service is used to query catalog entries for a given service
|
||||
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/service/"+service)
|
||||
r.setQueryOptions(q)
|
||||
if tag != "" {
|
||||
r.params.Set("tag", tag)
|
||||
}
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*CatalogService
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Node is used to query for service information about a single node
|
||||
func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out *CatalogNode
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
219
Godeps/_workspace/src/github.com/armon/consul-api/catalog_test.go
generated
vendored
219
Godeps/_workspace/src/github.com/armon/consul-api/catalog_test.go
generated
vendored
@ -1,219 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCatalog_Datacenters(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
datacenters, err := catalog.Datacenters()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(datacenters) == 0 {
|
||||
t.Fatalf("Bad: %v", datacenters)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog_Nodes(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
nodes, meta, err := catalog.Nodes(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("Bad: %v", meta)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
t.Fatalf("Bad: %v", nodes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog_Services(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
services, meta, err := catalog.Services(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("Bad: %v", meta)
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
t.Fatalf("Bad: %v", services)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog_Service(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
services, meta, err := catalog.Service("consul", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("Bad: %v", meta)
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
t.Fatalf("Bad: %v", services)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog_Node(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
name, _ := c.Agent().NodeName()
|
||||
info, meta, err := catalog.Node(name, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("Bad: %v", meta)
|
||||
}
|
||||
if len(info.Services) == 0 {
|
||||
t.Fatalf("Bad: %v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog_Registration(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
service := &AgentService{
|
||||
ID: "redis1",
|
||||
Service: "redis",
|
||||
Tags: []string{"master", "v1"},
|
||||
Port: 8000,
|
||||
}
|
||||
|
||||
check := &AgentCheck{
|
||||
Node: "foobar",
|
||||
CheckID: "service:redis1",
|
||||
Name: "Redis health check",
|
||||
Notes: "Script based health check",
|
||||
Status: "passing",
|
||||
ServiceID: "redis1",
|
||||
}
|
||||
|
||||
reg := &CatalogRegistration{
|
||||
Datacenter: "dc1",
|
||||
Node: "foobar",
|
||||
Address: "192.168.10.10",
|
||||
Service: service,
|
||||
Check: check,
|
||||
}
|
||||
|
||||
_, err := catalog.Register(reg, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
node, _, err := catalog.Node("foobar", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := node.Services["redis1"]; !ok {
|
||||
t.Fatalf("missing service: redis1")
|
||||
}
|
||||
|
||||
health, _, err := c.Health().Node("foobar", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if health[0].CheckID != "service:redis1" {
|
||||
t.Fatalf("missing checkid service:redis1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog_Deregistration(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
catalog := c.Catalog()
|
||||
|
||||
dereg := &CatalogDeregistration{
|
||||
Datacenter: "dc1",
|
||||
Node: "foobar",
|
||||
Address: "192.168.10.10",
|
||||
ServiceID: "redis1",
|
||||
}
|
||||
|
||||
_, err := catalog.Deregister(dereg, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
node, _, err := catalog.Node("foobar", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := node.Services["redis1"]; ok {
|
||||
t.Fatalf("ServiceID:redis1 is not deregistered")
|
||||
}
|
||||
|
||||
dereg = &CatalogDeregistration{
|
||||
Datacenter: "dc1",
|
||||
Node: "foobar",
|
||||
Address: "192.168.10.10",
|
||||
CheckID: "service:redis1",
|
||||
}
|
||||
|
||||
_, err = catalog.Deregister(dereg, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
health, _, err := c.Health().Node("foobar", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(health) != 0 {
|
||||
t.Fatalf("CheckID:service:redis1 is not deregistered")
|
||||
}
|
||||
|
||||
dereg = &CatalogDeregistration{
|
||||
Datacenter: "dc1",
|
||||
Node: "foobar",
|
||||
Address: "192.168.10.10",
|
||||
}
|
||||
|
||||
_, err = catalog.Deregister(dereg, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
node, _, err = catalog.Node("foobar", nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if node != nil {
|
||||
t.Fatalf("node is not deregistered: %v", node)
|
||||
}
|
||||
}
|
104
Godeps/_workspace/src/github.com/armon/consul-api/event.go
generated
vendored
104
Godeps/_workspace/src/github.com/armon/consul-api/event.go
generated
vendored
@ -1,104 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Event can be used to query the Event endpoints
|
||||
type Event struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// UserEvent represents an event that was fired by the user
|
||||
type UserEvent struct {
|
||||
ID string
|
||||
Name string
|
||||
Payload []byte
|
||||
NodeFilter string
|
||||
ServiceFilter string
|
||||
TagFilter string
|
||||
Version int
|
||||
LTime uint64
|
||||
}
|
||||
|
||||
// Session returns a handle to the session endpoints
|
||||
func (c *Client) Event() *Event {
|
||||
return &Event{c}
|
||||
}
|
||||
|
||||
// Fire is used to fire a new user event. Only the Name, Payload and Filters
|
||||
// are respected. This returns the ID or an associated error. Cross DC requests
|
||||
// are supported.
|
||||
func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name)
|
||||
r.setWriteOptions(q)
|
||||
if params.NodeFilter != "" {
|
||||
r.params.Set("node", params.NodeFilter)
|
||||
}
|
||||
if params.ServiceFilter != "" {
|
||||
r.params.Set("service", params.ServiceFilter)
|
||||
}
|
||||
if params.TagFilter != "" {
|
||||
r.params.Set("tag", params.TagFilter)
|
||||
}
|
||||
if params.Payload != nil {
|
||||
r.body = bytes.NewReader(params.Payload)
|
||||
}
|
||||
|
||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out UserEvent
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// List is used to get the most recent events an agent has received.
|
||||
// This list can be optionally filtered by the name. This endpoint supports
|
||||
// quasi-blocking queries. The index is not monotonic, nor does it provide provide
|
||||
// LastContact or KnownLeader.
|
||||
func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) {
|
||||
r := e.c.newRequest("GET", "/v1/event/list")
|
||||
r.setQueryOptions(q)
|
||||
if name != "" {
|
||||
r.params.Set("name", name)
|
||||
}
|
||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*UserEvent
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// IDToIndex is a bit of a hack. This simulates the index generation to
|
||||
// convert an event ID into a WaitIndex.
|
||||
func (e *Event) IDToIndex(uuid string) uint64 {
|
||||
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
|
||||
upper := uuid[19:23] + uuid[24:36]
|
||||
lowVal, err := strconv.ParseUint(lower, 16, 64)
|
||||
if err != nil {
|
||||
panic("Failed to convert " + lower)
|
||||
}
|
||||
highVal, err := strconv.ParseUint(upper, 16, 64)
|
||||
if err != nil {
|
||||
panic("Failed to convert " + upper)
|
||||
}
|
||||
return lowVal ^ highVal
|
||||
}
|
37
Godeps/_workspace/src/github.com/armon/consul-api/event_test.go
generated
vendored
37
Godeps/_workspace/src/github.com/armon/consul-api/event_test.go
generated
vendored
@ -1,37 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvent_FireList(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
event := c.Event()
|
||||
|
||||
params := &UserEvent{Name: "foo"}
|
||||
id, meta, err := event.Fire(params, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
t.Fatalf("invalid: %v", id)
|
||||
}
|
||||
|
||||
events, qm, err := event.List("", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if qm.LastIndex != event.IDToIndex(id) {
|
||||
t.Fatalf("Bad: %#v", qm)
|
||||
}
|
||||
|
||||
if events[len(events)-1].ID != id {
|
||||
t.Fatalf("bad: %#v", events)
|
||||
}
|
||||
}
|
136
Godeps/_workspace/src/github.com/armon/consul-api/health.go
generated
vendored
136
Godeps/_workspace/src/github.com/armon/consul-api/health.go
generated
vendored
@ -1,136 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HealthCheck is used to represent a single check
|
||||
type HealthCheck struct {
|
||||
Node string
|
||||
CheckID string
|
||||
Name string
|
||||
Status string
|
||||
Notes string
|
||||
Output string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// ServiceEntry is used for the health service endpoint
|
||||
type ServiceEntry struct {
|
||||
Node *Node
|
||||
Service *AgentService
|
||||
Checks []*HealthCheck
|
||||
}
|
||||
|
||||
// Health can be used to query the Health endpoints
|
||||
type Health struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Health returns a handle to the health endpoints
|
||||
func (c *Client) Health() *Health {
|
||||
return &Health{c}
|
||||
}
|
||||
|
||||
// Node is used to query for checks belonging to a given node
|
||||
func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Checks is used to return the checks associated with a service
|
||||
func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Service is used to query health information along with service info
|
||||
// for a given service. It can optionally do server-side filtering on a tag
|
||||
// or nodes with passing health checks only.
|
||||
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/service/"+service)
|
||||
r.setQueryOptions(q)
|
||||
if tag != "" {
|
||||
r.params.Set("tag", tag)
|
||||
}
|
||||
if passingOnly {
|
||||
r.params.Set("passing", "1")
|
||||
}
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*ServiceEntry
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// State is used to retreive all the checks in a given state.
|
||||
// The wildcard "any" state can also be used for all checks.
|
||||
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
switch state {
|
||||
case "any":
|
||||
case "warning":
|
||||
case "critical":
|
||||
case "passing":
|
||||
case "unknown":
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
||||
}
|
||||
r := h.c.newRequest("GET", "/v1/health/state/"+state)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
98
Godeps/_workspace/src/github.com/armon/consul-api/health_test.go
generated
vendored
98
Godeps/_workspace/src/github.com/armon/consul-api/health_test.go
generated
vendored
@ -1,98 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHealth_Node(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
health := c.Health()
|
||||
|
||||
info, err := agent.Self()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
name := info["Config"]["NodeName"].(string)
|
||||
|
||||
checks, meta, err := health.Node(name, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
if len(checks) == 0 {
|
||||
t.Fatalf("Bad: %v", checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth_Checks(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
agent := c.Agent()
|
||||
health := c.Health()
|
||||
|
||||
// Make a service with a check
|
||||
reg := &AgentServiceRegistration{
|
||||
Name: "foo",
|
||||
Check: &AgentServiceCheck{
|
||||
TTL: "15s",
|
||||
},
|
||||
}
|
||||
if err := agent.ServiceRegister(reg); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer agent.ServiceDeregister("foo")
|
||||
|
||||
// Wait for the register...
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
|
||||
checks, meta, err := health.Checks("foo", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
if len(checks) == 0 {
|
||||
t.Fatalf("Bad: %v", checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth_Service(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
health := c.Health()
|
||||
|
||||
// consul service should always exist...
|
||||
checks, meta, err := health.Service("consul", "", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
if len(checks) == 0 {
|
||||
t.Fatalf("Bad: %v", checks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth_State(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
health := c.Health()
|
||||
|
||||
checks, meta, err := health.State("any", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
if len(checks) == 0 {
|
||||
t.Fatalf("Bad: %v", checks)
|
||||
}
|
||||
}
|
219
Godeps/_workspace/src/github.com/armon/consul-api/kv.go
generated
vendored
219
Godeps/_workspace/src/github.com/armon/consul-api/kv.go
generated
vendored
@ -1,219 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KVPair is used to represent a single K/V entry
|
||||
type KVPair struct {
|
||||
Key string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
LockIndex uint64
|
||||
Flags uint64
|
||||
Value []byte
|
||||
Session string
|
||||
}
|
||||
|
||||
// KVPairs is a list of KVPair objects
|
||||
type KVPairs []*KVPair
|
||||
|
||||
// KV is used to manipulate the K/V API
|
||||
type KV struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// KV is used to return a handle to the K/V apis
|
||||
func (c *Client) KV() *KV {
|
||||
return &KV{c}
|
||||
}
|
||||
|
||||
// Get is used to lookup a single key
|
||||
func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) {
|
||||
resp, qm, err := k.getInternal(key, nil, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []*KVPair
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List is used to lookup all keys under a prefix
|
||||
func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) {
|
||||
resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []*KVPair
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// Keys is used to list all the keys under a prefix. Optionally,
|
||||
// a separator can be used to limit the responses.
|
||||
func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) {
|
||||
params := map[string]string{"keys": ""}
|
||||
if separator != "" {
|
||||
params["separator"] = separator
|
||||
}
|
||||
resp, qm, err := k.getInternal(prefix, params, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []string
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
||||
r := k.c.newRequest("GET", "/v1/kv/"+key)
|
||||
r.setQueryOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
rtt, resp, err := k.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
resp.Body.Close()
|
||||
return nil, qm, nil
|
||||
} else if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Put is used to write a new value. Only the
|
||||
// Key, Flags and Value is respected.
|
||||
func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) {
|
||||
params := make(map[string]string, 1)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
_, wm, err := k.put(p.Key, params, p.Value, q)
|
||||
return wm, err
|
||||
}
|
||||
|
||||
// CAS is used for a Check-And-Set operation. The Key,
|
||||
// ModifyIndex, Flags and Value are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["cas"] = strconv.FormatUint(p.ModifyIndex, 10)
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
// Acquire is used for a lock acquisiiton operation. The Key,
|
||||
// Flags, Value and Session are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["acquire"] = p.Session
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
// Release is used for a lock release operation. The Key,
|
||||
// Flags, Value and Session are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["release"] = p.Session
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
r := k.c.newRequest("PUT", "/v1/kv/"+key)
|
||||
r.setWriteOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
r.body = bytes.NewReader(body)
|
||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &WriteMeta{}
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||
}
|
||||
res := strings.Contains(string(buf.Bytes()), "true")
|
||||
return res, qm, nil
|
||||
}
|
||||
|
||||
// Delete is used to delete a single key
|
||||
func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) {
|
||||
return k.deleteInternal(key, nil, w)
|
||||
}
|
||||
|
||||
// DeleteTree is used to delete all keys under a prefix
|
||||
func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
||||
return k.deleteInternal(prefix, []string{"recurse"}, w)
|
||||
}
|
||||
|
||||
func (k *KV) deleteInternal(key string, params []string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := k.c.newRequest("DELETE", "/v1/kv/"+key)
|
||||
r.setWriteOptions(q)
|
||||
for _, param := range params {
|
||||
r.params.Set(param, "")
|
||||
}
|
||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
qm := &WriteMeta{}
|
||||
qm.RequestTime = rtt
|
||||
return qm, nil
|
||||
}
|
374
Godeps/_workspace/src/github.com/armon/consul-api/kv_test.go
generated
vendored
374
Godeps/_workspace/src/github.com/armon/consul-api/kv_test.go
generated
vendored
@ -1,374 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestClientPutGetDelete(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
// Get a get without a key
|
||||
key := testKey()
|
||||
pair, _, err := kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair != nil {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
|
||||
// Put the key
|
||||
value := []byte("test")
|
||||
p := &KVPair{Key: key, Flags: 42, Value: value}
|
||||
if _, err := kv.Put(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Get should work
|
||||
pair, meta, err := kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair == nil {
|
||||
t.Fatalf("expected value: %#v", pair)
|
||||
}
|
||||
if !bytes.Equal(pair.Value, value) {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
if pair.Flags != 42 {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// Delete
|
||||
if _, err := kv.Delete(key, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Get should fail
|
||||
pair, _, err = kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair != nil {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_List_DeleteRecurse(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
// Generate some test keys
|
||||
prefix := testKey()
|
||||
var keys []string
|
||||
for i := 0; i < 100; i++ {
|
||||
keys = append(keys, path.Join(prefix, testKey()))
|
||||
}
|
||||
|
||||
// Set values
|
||||
value := []byte("test")
|
||||
for _, key := range keys {
|
||||
p := &KVPair{Key: key, Value: value}
|
||||
if _, err := kv.Put(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// List the values
|
||||
pairs, meta, err := kv.List(prefix, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(pairs) != len(keys) {
|
||||
t.Fatalf("got %d keys", len(pairs))
|
||||
}
|
||||
for _, pair := range pairs {
|
||||
if !bytes.Equal(pair.Value, value) {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// Delete all
|
||||
if _, err := kv.DeleteTree(prefix, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// List the values
|
||||
pairs, _, err = kv.List(prefix, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(pairs) != 0 {
|
||||
t.Fatalf("got %d keys", len(pairs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CAS(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
// Put the key
|
||||
key := testKey()
|
||||
value := []byte("test")
|
||||
p := &KVPair{Key: key, Value: value}
|
||||
if work, _, err := kv.CAS(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
} else if !work {
|
||||
t.Fatalf("CAS failure")
|
||||
}
|
||||
|
||||
// Get should work
|
||||
pair, meta, err := kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair == nil {
|
||||
t.Fatalf("expected value: %#v", pair)
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// CAS update with bad index
|
||||
newVal := []byte("foo")
|
||||
p.Value = newVal
|
||||
p.ModifyIndex = 1
|
||||
if work, _, err := kv.CAS(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
} else if work {
|
||||
t.Fatalf("unexpected CAS")
|
||||
}
|
||||
|
||||
// CAS update with valid index
|
||||
p.ModifyIndex = meta.LastIndex
|
||||
if work, _, err := kv.CAS(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
} else if !work {
|
||||
t.Fatalf("unexpected CAS failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_WatchGet(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
// Get a get without a key
|
||||
key := testKey()
|
||||
pair, meta, err := kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair != nil {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// Put the key
|
||||
value := []byte("test")
|
||||
go func() {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
p := &KVPair{Key: key, Flags: 42, Value: value}
|
||||
if _, err := kv.Put(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Get should work
|
||||
options := &QueryOptions{WaitIndex: meta.LastIndex}
|
||||
pair, meta2, err := kv.Get(key, options)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair == nil {
|
||||
t.Fatalf("expected value: %#v", pair)
|
||||
}
|
||||
if !bytes.Equal(pair.Value, value) {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
if pair.Flags != 42 {
|
||||
t.Fatalf("unexpected value: %#v", pair)
|
||||
}
|
||||
if meta2.LastIndex <= meta.LastIndex {
|
||||
t.Fatalf("unexpected value: %#v", meta2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_WatchList(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
// Get a get without a key
|
||||
prefix := testKey()
|
||||
key := path.Join(prefix, testKey())
|
||||
pairs, meta, err := kv.List(prefix, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(pairs) != 0 {
|
||||
t.Fatalf("unexpected value: %#v", pairs)
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// Put the key
|
||||
value := []byte("test")
|
||||
go func() {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
p := &KVPair{Key: key, Flags: 42, Value: value}
|
||||
if _, err := kv.Put(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Get should work
|
||||
options := &QueryOptions{WaitIndex: meta.LastIndex}
|
||||
pairs, meta2, err := kv.List(prefix, options)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(pairs) != 1 {
|
||||
t.Fatalf("expected value: %#v", pairs)
|
||||
}
|
||||
if !bytes.Equal(pairs[0].Value, value) {
|
||||
t.Fatalf("unexpected value: %#v", pairs)
|
||||
}
|
||||
if pairs[0].Flags != 42 {
|
||||
t.Fatalf("unexpected value: %#v", pairs)
|
||||
}
|
||||
if meta2.LastIndex <= meta.LastIndex {
|
||||
t.Fatalf("unexpected value: %#v", meta2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClient_Keys_DeleteRecurse(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
kv := c.KV()
|
||||
|
||||
// Generate some test keys
|
||||
prefix := testKey()
|
||||
var keys []string
|
||||
for i := 0; i < 100; i++ {
|
||||
keys = append(keys, path.Join(prefix, testKey()))
|
||||
}
|
||||
|
||||
// Set values
|
||||
value := []byte("test")
|
||||
for _, key := range keys {
|
||||
p := &KVPair{Key: key, Value: value}
|
||||
if _, err := kv.Put(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// List the values
|
||||
out, meta, err := kv.Keys(prefix, "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(out) != len(keys) {
|
||||
t.Fatalf("got %d keys", len(out))
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// Delete all
|
||||
if _, err := kv.DeleteTree(prefix, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// List the values
|
||||
out, _, err = kv.Keys(prefix, "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(out) != 0 {
|
||||
t.Fatalf("got %d keys", len(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AcquireRelease(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
session := c.Session()
|
||||
kv := c.KV()
|
||||
|
||||
// Make a session
|
||||
id, _, err := session.CreateNoChecks(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer session.Destroy(id, nil)
|
||||
|
||||
// Acquire the key
|
||||
key := testKey()
|
||||
value := []byte("test")
|
||||
p := &KVPair{Key: key, Value: value, Session: id}
|
||||
if work, _, err := kv.Acquire(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
} else if !work {
|
||||
t.Fatalf("Lock failure")
|
||||
}
|
||||
|
||||
// Get should work
|
||||
pair, meta, err := kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair == nil {
|
||||
t.Fatalf("expected value: %#v", pair)
|
||||
}
|
||||
if pair.LockIndex != 1 {
|
||||
t.Fatalf("Expected lock: %v", pair)
|
||||
}
|
||||
if pair.Session != id {
|
||||
t.Fatalf("Expected lock: %v", pair)
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
|
||||
// Release
|
||||
if work, _, err := kv.Release(p, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
} else if !work {
|
||||
t.Fatalf("Release fail")
|
||||
}
|
||||
|
||||
// Get should work
|
||||
pair, meta, err = kv.Get(key, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pair == nil {
|
||||
t.Fatalf("expected value: %#v", pair)
|
||||
}
|
||||
if pair.LockIndex != 1 {
|
||||
t.Fatalf("Expected lock: %v", pair)
|
||||
}
|
||||
if pair.Session != "" {
|
||||
t.Fatalf("Expected unlock: %v", pair)
|
||||
}
|
||||
if meta.LastIndex == 0 {
|
||||
t.Fatalf("unexpected value: %#v", meta)
|
||||
}
|
||||
}
|
167
Godeps/_workspace/src/github.com/armon/consul-api/session.go
generated
vendored
167
Godeps/_workspace/src/github.com/armon/consul-api/session.go
generated
vendored
@ -1,167 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionEntry represents a session in consul
|
||||
type SessionEntry struct {
|
||||
CreateIndex uint64
|
||||
ID string
|
||||
Name string
|
||||
Node string
|
||||
Checks []string
|
||||
LockDelay time.Duration
|
||||
}
|
||||
|
||||
// Session can be used to query the Session endpoints
|
||||
type Session struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Session returns a handle to the session endpoints
|
||||
func (c *Client) Session() *Session {
|
||||
return &Session{c}
|
||||
}
|
||||
|
||||
// CreateNoChecks is like Create but is used specifically to create
|
||||
// a session with no associated health checks.
|
||||
func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
body := make(map[string]interface{})
|
||||
body["Checks"] = []string{}
|
||||
if se != nil {
|
||||
if se.Name != "" {
|
||||
body["Name"] = se.Name
|
||||
}
|
||||
if se.Node != "" {
|
||||
body["Node"] = se.Node
|
||||
}
|
||||
if se.LockDelay != 0 {
|
||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
||||
}
|
||||
}
|
||||
return s.create(body, q)
|
||||
|
||||
}
|
||||
|
||||
// Create makes a new session. Providing a session entry can
|
||||
// customize the session. It can also be nil to use defaults.
|
||||
func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var obj interface{}
|
||||
if se != nil {
|
||||
body := make(map[string]interface{})
|
||||
obj = body
|
||||
if se.Name != "" {
|
||||
body["Name"] = se.Name
|
||||
}
|
||||
if se.Node != "" {
|
||||
body["Node"] = se.Node
|
||||
}
|
||||
if se.LockDelay != 0 {
|
||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
||||
}
|
||||
if len(se.Checks) > 0 {
|
||||
body["Checks"] = se.Checks
|
||||
}
|
||||
}
|
||||
return s.create(obj, q)
|
||||
}
|
||||
|
||||
func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := s.c.newRequest("PUT", "/v1/session/create")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = obj
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Destroy invalides a given session
|
||||
func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := s.c.newRequest("PUT", "/v1/session/destroy/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Info looks up a single session
|
||||
func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
|
||||
r := s.c.newRequest("GET", "/v1/session/info/"+id)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List gets sessions for a node
|
||||
func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
||||
r := s.c.newRequest("GET", "/v1/session/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// List gets all active sessions
|
||||
func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
||||
r := s.c.newRequest("GET", "/v1/session/list")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
136
Godeps/_workspace/src/github.com/armon/consul-api/session_test.go
generated
vendored
136
Godeps/_workspace/src/github.com/armon/consul-api/session_test.go
generated
vendored
@ -1,136 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSession_CreateDestroy(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
session := c.Session()
|
||||
|
||||
id, meta, err := session.Create(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
t.Fatalf("invalid: %v", id)
|
||||
}
|
||||
|
||||
meta, err = session.Destroy(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if meta.RequestTime == 0 {
|
||||
t.Fatalf("bad: %v", meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Info(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
session := c.Session()
|
||||
|
||||
id, _, err := session.Create(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer session.Destroy(id, nil)
|
||||
|
||||
info, qm, err := session.Info(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
t.Fatalf("should get session")
|
||||
}
|
||||
if info.CreateIndex == 0 {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
if info.ID != id {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
if info.Name != "" {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
if info.Node == "" {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
if len(info.Checks) == 0 {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
if info.LockDelay == 0 {
|
||||
t.Fatalf("bad: %v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Node(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
session := c.Session()
|
||||
|
||||
id, _, err := session.Create(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer session.Destroy(id, nil)
|
||||
|
||||
info, qm, err := session.Info(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
sessions, qm, err := session.Node(info.Node, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("bad: %v", sessions)
|
||||
}
|
||||
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_List(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
session := c.Session()
|
||||
|
||||
id, _, err := session.Create(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer session.Destroy(id, nil)
|
||||
|
||||
sessions, qm, err := session.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("bad: %v", sessions)
|
||||
}
|
||||
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("bad: %v", qm)
|
||||
}
|
||||
}
|
43
Godeps/_workspace/src/github.com/armon/consul-api/status.go
generated
vendored
43
Godeps/_workspace/src/github.com/armon/consul-api/status.go
generated
vendored
@ -1,43 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
// Status can be used to query the Status endpoints
|
||||
type Status struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Status returns a handle to the status endpoints
|
||||
func (c *Client) Status() *Status {
|
||||
return &Status{c}
|
||||
}
|
||||
|
||||
// Leader is used to query for a known leader
|
||||
func (s *Status) Leader() (string, error) {
|
||||
r := s.c.newRequest("GET", "/v1/status/leader")
|
||||
_, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var leader string
|
||||
if err := decodeBody(resp, &leader); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return leader, nil
|
||||
}
|
||||
|
||||
// Peers is used to query for a known raft peers
|
||||
func (s *Status) Peers() ([]string, error) {
|
||||
r := s.c.newRequest("GET", "/v1/status/peers")
|
||||
_, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var peers []string
|
||||
if err := decodeBody(resp, &peers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return peers, nil
|
||||
}
|
31
Godeps/_workspace/src/github.com/armon/consul-api/status_test.go
generated
vendored
31
Godeps/_workspace/src/github.com/armon/consul-api/status_test.go
generated
vendored
@ -1,31 +0,0 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStatusLeader(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
status := c.Status()
|
||||
|
||||
leader, err := status.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if leader == "" {
|
||||
t.Fatalf("Expected leader")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusPeers(t *testing.T) {
|
||||
c := makeClient(t)
|
||||
status := c.Status()
|
||||
|
||||
peers, err := status.Peers()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(peers) == 0 {
|
||||
t.Fatalf("Expected peers ")
|
||||
}
|
||||
}
|
22
Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore
generated
vendored
22
Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore
generated
vendored
@ -1,22 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
2
Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml
generated
vendored
2
Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml
generated
vendored
@ -1,2 +0,0 @@
|
||||
language: go
|
||||
go: 1.3.3
|
20
Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE
generated
vendored
20
Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE
generated
vendored
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
66
Godeps/_workspace/src/github.com/cenkalti/backoff/README.md
generated
vendored
66
Godeps/_workspace/src/github.com/cenkalti/backoff/README.md
generated
vendored
@ -1,66 +0,0 @@
|
||||
# backoff
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/cenkalti/backoff?status.png)](https://godoc.org/github.com/cenkalti/backoff)
|
||||
[![Build Status](https://travis-ci.org/cenkalti/backoff.png)](https://travis-ci.org/cenkalti/backoff)
|
||||
|
||||
This is a Go port of the exponential backoff algorithm from
|
||||
[google-http-java-client](https://code.google.com/p/google-http-java-client/wiki/ExponentialBackoff).
|
||||
|
||||
[Exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff)
|
||||
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
|
||||
in order to gradually find an acceptable rate.
|
||||
The retries exponentially increase and stop increasing when a certain threshold is met.
|
||||
|
||||
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/cenkalti/backoff
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Simple retry helper that uses exponential back-off algorithm:
|
||||
|
||||
```go
|
||||
operation := func() error {
|
||||
// An operation that might fail
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backoff.NewExponentialBackOff())
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// operation is successfull
|
||||
```
|
||||
|
||||
Ticker example:
|
||||
|
||||
```go
|
||||
operation := func() error {
|
||||
// An operation that may fail
|
||||
}
|
||||
|
||||
b := backoff.NewExponentialBackOff()
|
||||
ticker := backoff.NewTicker(b)
|
||||
|
||||
var err error
|
||||
for t = range ticker.C {
|
||||
if err = operation(); err != nil {
|
||||
log.Println(err, "will retry...")
|
||||
continue
|
||||
}
|
||||
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Operation has failed.
|
||||
}
|
||||
|
||||
// Operation is successfull.
|
||||
```
|
56
Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go
generated
vendored
56
Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go
generated
vendored
@ -1,56 +0,0 @@
|
||||
// Package backoff implements backoff algorithms for retrying operations.
|
||||
//
|
||||
// Also has a Retry() helper for retrying operations that may fail.
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
// Back-off policy when retrying an operation.
|
||||
type BackOff interface {
|
||||
// Gets the duration to wait before retrying the operation or
|
||||
// backoff.Stop to indicate that no retries should be made.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// duration := backoff.NextBackOff();
|
||||
// if (duration == backoff.Stop) {
|
||||
// // do not retry operation
|
||||
// } else {
|
||||
// // sleep for duration and retry operation
|
||||
// }
|
||||
//
|
||||
NextBackOff() time.Duration
|
||||
|
||||
// Reset to initial state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Indicates that no more retries should be made for use in NextBackOff().
|
||||
const Stop time.Duration = -1
|
||||
|
||||
// ZeroBackOff is a fixed back-off policy whose back-off time is always zero,
|
||||
// meaning that the operation is retried immediately without waiting.
|
||||
type ZeroBackOff struct{}
|
||||
|
||||
func (b *ZeroBackOff) Reset() {}
|
||||
|
||||
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
|
||||
|
||||
// StopBackOff is a fixed back-off policy that always returns backoff.Stop for
|
||||
// NextBackOff(), meaning that the operation should not be retried.
|
||||
type StopBackOff struct{}
|
||||
|
||||
func (b *StopBackOff) Reset() {}
|
||||
|
||||
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
|
||||
|
||||
type ConstantBackOff struct {
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
func (b *ConstantBackOff) Reset() {}
|
||||
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
|
||||
|
||||
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
|
||||
return &ConstantBackOff{Interval: d}
|
||||
}
|
28
Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go
generated
vendored
28
Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go
generated
vendored
@ -1,28 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNextBackOffMillis(t *testing.T) {
|
||||
subtestNextBackOff(t, 0, new(ZeroBackOff))
|
||||
subtestNextBackOff(t, Stop, new(StopBackOff))
|
||||
}
|
||||
|
||||
func subtestNextBackOff(t *testing.T, expectedValue time.Duration, backOffPolicy BackOff) {
|
||||
for i := 0; i < 10; i++ {
|
||||
next := backOffPolicy.NextBackOff()
|
||||
if next != expectedValue {
|
||||
t.Errorf("got: %d expected: %d", next, expectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstantBackOff(t *testing.T) {
|
||||
backoff := NewConstantBackOff(time.Second)
|
||||
if backoff.NextBackOff() != time.Second {
|
||||
t.Error("invalid interval")
|
||||
}
|
||||
}
|
141
Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go
generated
vendored
141
Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go
generated
vendored
@ -1,141 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
ExponentialBackOff is an implementation of BackOff that increases the back off
|
||||
period for each retry attempt using a randomization function that grows exponentially.
|
||||
|
||||
NextBackOff() is calculated using the following formula:
|
||||
|
||||
randomized_interval =
|
||||
retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])
|
||||
|
||||
In other words NextBackOff() will range between the randomization factor
|
||||
percentage below and above the retry interval. For example, using 2 seconds as the base retry
|
||||
interval and 0.5 as the randomization factor, the actual back off period used in the next retry
|
||||
attempt will be between 1 and 3 seconds.
|
||||
|
||||
Note: max_interval caps the retry_interval and not the randomized_interval.
|
||||
|
||||
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||
max_elapsed_time then the method NextBackOff() starts returning backoff.Stop.
|
||||
The elapsed time can be reset by calling Reset().
|
||||
|
||||
Example: The default retry_interval is .5 seconds, default randomization_factor is 0.5, default
|
||||
multiplier is 1.5 and the default max_interval is 1 minute. For 10 tries the sequence will be
|
||||
(values in seconds) and assuming we go over the max_elapsed_time on the 10th try:
|
||||
|
||||
request# retry_interval randomized_interval
|
||||
|
||||
1 0.5 [0.25, 0.75]
|
||||
2 0.75 [0.375, 1.125]
|
||||
3 1.125 [0.562, 1.687]
|
||||
4 1.687 [0.8435, 2.53]
|
||||
5 2.53 [1.265, 3.795]
|
||||
6 3.795 [1.897, 5.692]
|
||||
7 5.692 [2.846, 8.538]
|
||||
8 8.538 [4.269, 12.807]
|
||||
9 12.807 [6.403, 19.210]
|
||||
10 19.210 backoff.Stop
|
||||
|
||||
Implementation is not thread-safe.
|
||||
*/
|
||||
type ExponentialBackOff struct {
|
||||
InitialInterval time.Duration
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
// After MaxElapsedTime the ExponentialBackOff stops.
|
||||
// It never stops if MaxElapsedTime == 0.
|
||||
MaxElapsedTime time.Duration
|
||||
Clock Clock
|
||||
|
||||
currentInterval time.Duration
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// Clock is an interface that returns current time for BackOff.
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// Default values for ExponentialBackOff.
|
||||
const (
|
||||
DefaultInitialInterval = 500 * time.Millisecond
|
||||
DefaultRandomizationFactor = 0.5
|
||||
DefaultMultiplier = 1.5
|
||||
DefaultMaxInterval = 60 * time.Second
|
||||
DefaultMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||
func NewExponentialBackOff() *ExponentialBackOff {
|
||||
return &ExponentialBackOff{
|
||||
InitialInterval: DefaultInitialInterval,
|
||||
RandomizationFactor: DefaultRandomizationFactor,
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
Clock: SystemClock,
|
||||
}
|
||||
}
|
||||
|
||||
type systemClock struct{}
|
||||
|
||||
func (t systemClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// SystemClock implements Clock interface that uses time.Now().
|
||||
var SystemClock = systemClock{}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
b.startTime = b.Clock.Now()
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next back off interval using the formula:
|
||||
// randomized_interval = retry_interval +/- (randomization_factor * retry_interval)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
// Make sure we have not gone over the maximum elapsed time.
|
||||
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
|
||||
return Stop
|
||||
}
|
||||
defer b.incrementCurrentInterval()
|
||||
return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||
// is created and is reset when Reset() is called.
|
||||
//
|
||||
// The elapsed time is computed using time.Now().UnixNano().
|
||||
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||
return b.Clock.Now().Sub(b.startTime)
|
||||
}
|
||||
|
||||
// Increments the current interval by multiplying it with the multiplier.
|
||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||
b.currentInterval = b.MaxInterval
|
||||
} else {
|
||||
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random value from the interval:
|
||||
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
||||
var maxInterval = float64(currentInterval) + delta
|
||||
// Get a random value from the range [minInterval, maxInterval].
|
||||
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||
}
|
111
Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go
generated
vendored
111
Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go
generated
vendored
@ -1,111 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBackOff(t *testing.T) {
|
||||
var (
|
||||
testInitialInterval = 500 * time.Millisecond
|
||||
testRandomizationFactor = 0.1
|
||||
testMultiplier = 2.0
|
||||
testMaxInterval = 5 * time.Second
|
||||
testMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
exp := NewExponentialBackOff()
|
||||
exp.InitialInterval = testInitialInterval
|
||||
exp.RandomizationFactor = testRandomizationFactor
|
||||
exp.Multiplier = testMultiplier
|
||||
exp.MaxInterval = testMaxInterval
|
||||
exp.MaxElapsedTime = testMaxElapsedTime
|
||||
exp.Reset()
|
||||
|
||||
var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000}
|
||||
for i, d := range expectedResults {
|
||||
expectedResults[i] = d * time.Millisecond
|
||||
}
|
||||
|
||||
for _, expected := range expectedResults {
|
||||
assertEquals(t, expected, exp.currentInterval)
|
||||
// Assert that the next back off falls in the expected range.
|
||||
var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected))
|
||||
var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected))
|
||||
var actualInterval = exp.NextBackOff()
|
||||
if !(minInterval <= actualInterval && actualInterval <= maxInterval) {
|
||||
t.Error("error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomizedInterval(t *testing.T) {
|
||||
// 33% chance of being 1.
|
||||
assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2))
|
||||
assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2))
|
||||
// 33% chance of being 2.
|
||||
assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2))
|
||||
assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2))
|
||||
// 33% chance of being 3.
|
||||
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2))
|
||||
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2))
|
||||
}
|
||||
|
||||
type TestClock struct {
|
||||
i time.Duration
|
||||
start time.Time
|
||||
}
|
||||
|
||||
func (c *TestClock) Now() time.Time {
|
||||
t := c.start.Add(c.i)
|
||||
c.i += time.Second
|
||||
return t
|
||||
}
|
||||
|
||||
func TestGetElapsedTime(t *testing.T) {
|
||||
var exp = NewExponentialBackOff()
|
||||
exp.Clock = &TestClock{}
|
||||
exp.Reset()
|
||||
|
||||
var elapsedTime = exp.GetElapsedTime()
|
||||
if elapsedTime != time.Second {
|
||||
t.Errorf("elapsedTime=%d", elapsedTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxElapsedTime(t *testing.T) {
|
||||
var exp = NewExponentialBackOff()
|
||||
exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)}
|
||||
if exp.NextBackOff() != Stop {
|
||||
t.Error("error2")
|
||||
}
|
||||
// Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater
|
||||
// than the max elapsed time.
|
||||
exp.startTime = time.Time{}
|
||||
assertEquals(t, Stop, exp.NextBackOff())
|
||||
}
|
||||
|
||||
func TestBackOffOverflow(t *testing.T) {
|
||||
var (
|
||||
testInitialInterval time.Duration = math.MaxInt64 / 2
|
||||
testMaxInterval time.Duration = math.MaxInt64
|
||||
testMultiplier float64 = 2.1
|
||||
)
|
||||
|
||||
exp := NewExponentialBackOff()
|
||||
exp.InitialInterval = testInitialInterval
|
||||
exp.Multiplier = testMultiplier
|
||||
exp.MaxInterval = testMaxInterval
|
||||
exp.Reset()
|
||||
|
||||
exp.NextBackOff()
|
||||
// Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration .
|
||||
assertEquals(t, testMaxInterval, exp.currentInterval)
|
||||
}
|
||||
|
||||
func assertEquals(t *testing.T, expected, value time.Duration) {
|
||||
if expected != value {
|
||||
t.Errorf("got: %d, expected: %d", value, expected)
|
||||
}
|
||||
}
|
44
Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go
generated
vendored
44
Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go
generated
vendored
@ -1,44 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
// Retry the function f until it does not return error or BackOff stops.
|
||||
// f is guaranteed to be run at least once.
|
||||
// It is the caller's responsibility to reset b after Retry returns.
|
||||
//
|
||||
// Usage:
|
||||
// operation := func() error {
|
||||
// // An operation that may fail
|
||||
// }
|
||||
//
|
||||
// err := backoff.Retry(operation, backoff.NewExponentialBackOff())
|
||||
// if err != nil {
|
||||
// // Operation has failed.
|
||||
// }
|
||||
//
|
||||
// // Operation is successfull.
|
||||
//
|
||||
func Retry(f func() error, b BackOff) error { return RetryNotify(f, b, nil) }
|
||||
|
||||
// RetryNotify calls notify function with the error and wait duration for each failed attempt before sleep.
|
||||
func RetryNotify(f func() error, b BackOff, notify func(err error, wait time.Duration)) error {
|
||||
var err error
|
||||
var next time.Duration
|
||||
|
||||
b.Reset()
|
||||
for {
|
||||
if err = f(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if next = b.NextBackOff(); next == Stop {
|
||||
return err
|
||||
}
|
||||
|
||||
if notify != nil {
|
||||
notify(err, next)
|
||||
}
|
||||
|
||||
time.Sleep(next)
|
||||
}
|
||||
}
|
34
Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go
generated
vendored
34
Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go
generated
vendored
@ -1,34 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRetry(t *testing.T) {
|
||||
const successOn = 3
|
||||
var i = 0
|
||||
|
||||
// This function is successfull on "successOn" calls.
|
||||
f := func() error {
|
||||
i++
|
||||
log.Printf("function is called %d. time\n", i)
|
||||
|
||||
if i == successOn {
|
||||
log.Println("OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("error")
|
||||
return errors.New("error")
|
||||
}
|
||||
|
||||
err := Retry(f, NewExponentialBackOff())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err.Error())
|
||||
}
|
||||
if i != successOn {
|
||||
t.Errorf("invalid number of retries: %d", i)
|
||||
}
|
||||
}
|
102
Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go
generated
vendored
102
Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go
generated
vendored
@ -1,102 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
|
||||
//
|
||||
// Usage:
|
||||
// operation := func() error {
|
||||
// // An operation that may fail
|
||||
// }
|
||||
//
|
||||
// b := backoff.NewExponentialBackOff()
|
||||
// ticker := backoff.NewTicker(b)
|
||||
//
|
||||
// var err error
|
||||
// for _ = range ticker.C {
|
||||
// if err = operation(); err != nil {
|
||||
// log.Println(err, "will retry...")
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// ticker.Stop()
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// // Operation has failed.
|
||||
// }
|
||||
//
|
||||
// // Operation is successfull.
|
||||
//
|
||||
type Ticker struct {
|
||||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
b BackOff
|
||||
stop chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// NewTicker returns a new Ticker containing a channel that will send the time at times
|
||||
// specified by the BackOff argument. Ticker is guaranteed to tick at least once.
|
||||
// The channel is closed when Stop method is called or BackOff stops.
|
||||
func NewTicker(b BackOff) *Ticker {
|
||||
c := make(chan time.Time)
|
||||
t := &Ticker{
|
||||
C: c,
|
||||
c: c,
|
||||
b: b,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
go t.run()
|
||||
runtime.SetFinalizer(t, (*Ticker).Stop)
|
||||
return t
|
||||
}
|
||||
|
||||
// Stop turns off a ticker. After Stop, no more ticks will be sent.
|
||||
func (t *Ticker) Stop() {
|
||||
t.stopOnce.Do(func() { close(t.stop) })
|
||||
}
|
||||
|
||||
func (t *Ticker) run() {
|
||||
c := t.c
|
||||
defer close(c)
|
||||
t.b.Reset()
|
||||
|
||||
// Ticker is guaranteed to tick at least once.
|
||||
afterC := t.send(time.Now())
|
||||
|
||||
for {
|
||||
if afterC == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case tick := <-afterC:
|
||||
afterC = t.send(tick)
|
||||
case <-t.stop:
|
||||
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Ticker) send(tick time.Time) <-chan time.Time {
|
||||
select {
|
||||
case t.c <- tick:
|
||||
case <-t.stop:
|
||||
return nil
|
||||
}
|
||||
|
||||
next := t.b.NextBackOff()
|
||||
if next == Stop {
|
||||
t.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
return time.After(next)
|
||||
}
|
45
Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go
generated
vendored
45
Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go
generated
vendored
@ -1,45 +0,0 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTicker(t *testing.T) {
|
||||
const successOn = 3
|
||||
var i = 0
|
||||
|
||||
// This function is successfull on "successOn" calls.
|
||||
f := func() error {
|
||||
i++
|
||||
log.Printf("function is called %d. time\n", i)
|
||||
|
||||
if i == successOn {
|
||||
log.Println("OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("error")
|
||||
return errors.New("error")
|
||||
}
|
||||
|
||||
b := NewExponentialBackOff()
|
||||
ticker := NewTicker(b)
|
||||
|
||||
var err error
|
||||
for _ = range ticker.C {
|
||||
if err = f(); err != nil {
|
||||
t.Log(err)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err.Error())
|
||||
}
|
||||
if i != successOn {
|
||||
t.Errorf("invalid number of retries: %d", i)
|
||||
}
|
||||
}
|
23
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child.go
generated
vendored
23
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child.go
generated
vendored
@ -1,23 +0,0 @@
|
||||
package etcd
|
||||
|
||||
// Add a new directory with a random etcd-generated key under the given path.
|
||||
func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.post(key, "", ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Add a new file with a random etcd-generated key under the given path.
|
||||
func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.post(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
73
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go
generated
vendored
73
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go
generated
vendored
@ -1,73 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAddChild(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
c.Delete("nonexistentDir", true)
|
||||
}()
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
|
||||
_, err := c.AddChild("fooDir", "v0", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = c.AddChild("fooDir", "v1", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.Get("fooDir", true, false)
|
||||
// The child with v0 should proceed the child with v1 because it's added
|
||||
// earlier, so it should have a lower key.
|
||||
if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) {
|
||||
t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
|
||||
" The response was: %#v", resp)
|
||||
}
|
||||
|
||||
// Creating a child under a nonexistent directory should succeed.
|
||||
// The directory should be created.
|
||||
resp, err = c.AddChild("nonexistentDir", "foo", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddChildDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
c.Delete("nonexistentDir", true)
|
||||
}()
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
|
||||
_, err := c.AddChildDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = c.AddChildDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.Get("fooDir", true, false)
|
||||
// The child with v0 should proceed the child with v1 because it's added
|
||||
// earlier, so it should have a lower key.
|
||||
if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) {
|
||||
t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
|
||||
" The response was: %#v", resp)
|
||||
}
|
||||
|
||||
// Creating a child under a nonexistent directory should succeed.
|
||||
// The directory should be created.
|
||||
resp, err = c.AddChildDir("nonexistentDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
435
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go
generated
vendored
435
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go
generated
vendored
@ -1,435 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// See SetConsistency for how to use these constants.
|
||||
const (
|
||||
// Using strings rather than iota because the consistency level
|
||||
// could be persisted to disk, so it'd be better to use
|
||||
// human-readable values.
|
||||
STRONG_CONSISTENCY = "STRONG"
|
||||
WEAK_CONSISTENCY = "WEAK"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBufferSize = 10
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CertFile string `json:"certFile"`
|
||||
KeyFile string `json:"keyFile"`
|
||||
CaCertFile []string `json:"caCertFiles"`
|
||||
DialTimeout time.Duration `json:"timeout"`
|
||||
Consistency string `json:"consistency"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config Config `json:"config"`
|
||||
cluster *Cluster `json:"cluster"`
|
||||
httpClient *http.Client
|
||||
persistence io.Writer
|
||||
cURLch chan string
|
||||
// CheckRetry can be used to control the policy for failed requests
|
||||
// and modify the cluster if needed.
|
||||
// The client calls it before sending requests again, and
|
||||
// stops retrying if CheckRetry returns some error. The cases that
|
||||
// this function needs to handle include no response and unexpected
|
||||
// http status code of response.
|
||||
// If CheckRetry is nil, client will call the default one
|
||||
// `DefaultCheckRetry`.
|
||||
// Argument cluster is the etcd.Cluster object that these requests have been made on.
|
||||
// Argument numReqs is the number of http.Requests that have been made so far.
|
||||
// Argument lastResp is the http.Responses from the last request.
|
||||
// Argument err is the reason of the failure.
|
||||
CheckRetry func(cluster *Cluster, numReqs int,
|
||||
lastResp http.Response, err error) error
|
||||
}
|
||||
|
||||
// NewClient create a basic client that is configured to be used
|
||||
// with the given machine list.
|
||||
func NewClient(machines []string) *Client {
|
||||
config := Config{
|
||||
// default timeout is one second
|
||||
DialTimeout: time.Second,
|
||||
// default consistency level is STRONG
|
||||
Consistency: STRONG_CONSISTENCY,
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
cluster: NewCluster(machines),
|
||||
config: config,
|
||||
}
|
||||
|
||||
client.initHTTPClient()
|
||||
client.saveConfig()
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// NewTLSClient create a basic client with TLS configuration
|
||||
func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) {
|
||||
// overwrite the default machine to use https
|
||||
if len(machines) == 0 {
|
||||
machines = []string{"https://127.0.0.1:4001"}
|
||||
}
|
||||
|
||||
config := Config{
|
||||
// default timeout is one second
|
||||
DialTimeout: time.Second,
|
||||
// default consistency level is STRONG
|
||||
Consistency: STRONG_CONSISTENCY,
|
||||
CertFile: cert,
|
||||
KeyFile: key,
|
||||
CaCertFile: make([]string, 0),
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
cluster: NewCluster(machines),
|
||||
config: config,
|
||||
}
|
||||
|
||||
err := client.initHTTPSClient(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.AddRootCA(caCert)
|
||||
|
||||
client.saveConfig()
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewClientFromFile creates a client from a given file path.
|
||||
// The given file is expected to use the JSON format.
|
||||
func NewClientFromFile(fpath string) (*Client, error) {
|
||||
fi, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := fi.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return NewClientFromReader(fi)
|
||||
}
|
||||
|
||||
// NewClientFromReader creates a Client configured from a given reader.
|
||||
// The configuration is expected to use the JSON format.
|
||||
func NewClientFromReader(reader io.Reader) (*Client, error) {
|
||||
c := new(Client)
|
||||
|
||||
b, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.config.CertFile == "" {
|
||||
c.initHTTPClient()
|
||||
} else {
|
||||
err = c.initHTTPSClient(c.config.CertFile, c.config.KeyFile)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, caCert := range c.config.CaCertFile {
|
||||
if err := c.AddRootCA(caCert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Override the Client's HTTP Transport object
|
||||
func (c *Client) SetTransport(tr *http.Transport) {
|
||||
c.httpClient.Transport = tr
|
||||
}
|
||||
|
||||
// initHTTPClient initializes a HTTP client for etcd client
|
||||
func (c *Client) initHTTPClient() {
|
||||
tr := &http.Transport{
|
||||
Dial: c.dial,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
c.httpClient = &http.Client{Transport: tr}
|
||||
}
|
||||
|
||||
// initHTTPClient initializes a HTTPS client for etcd client
|
||||
func (c *Client) initHTTPSClient(cert, key string) error {
|
||||
if cert == "" || key == "" {
|
||||
return errors.New("Require both cert and key path")
|
||||
}
|
||||
|
||||
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
Dial: c.dial,
|
||||
}
|
||||
|
||||
c.httpClient = &http.Client{Transport: tr}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPersistence sets a writer to which the config will be
|
||||
// written every time it's changed.
|
||||
func (c *Client) SetPersistence(writer io.Writer) {
|
||||
c.persistence = writer
|
||||
}
|
||||
|
||||
// SetConsistency changes the consistency level of the client.
|
||||
//
|
||||
// When consistency is set to STRONG_CONSISTENCY, all requests,
|
||||
// including GET, are sent to the leader. This means that, assuming
|
||||
// the absence of leader failures, GET requests are guaranteed to see
|
||||
// the changes made by previous requests.
|
||||
//
|
||||
// When consistency is set to WEAK_CONSISTENCY, other requests
|
||||
// are still sent to the leader, but GET requests are sent to a
|
||||
// random server from the server pool. This reduces the read
|
||||
// load on the leader, but it's not guaranteed that the GET requests
|
||||
// will see changes made by previous requests (they might have not
|
||||
// yet been committed on non-leader servers).
|
||||
func (c *Client) SetConsistency(consistency string) error {
|
||||
if !(consistency == STRONG_CONSISTENCY || consistency == WEAK_CONSISTENCY) {
|
||||
return errors.New("The argument must be either STRONG_CONSISTENCY or WEAK_CONSISTENCY.")
|
||||
}
|
||||
c.config.Consistency = consistency
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets the DialTimeout value
|
||||
func (c *Client) SetDialTimeout(d time.Duration) {
|
||||
c.config.DialTimeout = d
|
||||
}
|
||||
|
||||
// AddRootCA adds a root CA cert for the etcd client
|
||||
func (c *Client) AddRootCA(caCert string) error {
|
||||
if c.httpClient == nil {
|
||||
return errors.New("Client has not been initialized yet!")
|
||||
}
|
||||
|
||||
certBytes, err := ioutil.ReadFile(caCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr, ok := c.httpClient.Transport.(*http.Transport)
|
||||
|
||||
if !ok {
|
||||
panic("AddRootCA(): Transport type assert should not fail")
|
||||
}
|
||||
|
||||
if tr.TLSClientConfig.RootCAs == nil {
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok = caCertPool.AppendCertsFromPEM(certBytes)
|
||||
if ok {
|
||||
tr.TLSClientConfig.RootCAs = caCertPool
|
||||
}
|
||||
tr.TLSClientConfig.InsecureSkipVerify = false
|
||||
} else {
|
||||
ok = tr.TLSClientConfig.RootCAs.AppendCertsFromPEM(certBytes)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
err = errors.New("Unable to load caCert")
|
||||
}
|
||||
|
||||
c.config.CaCertFile = append(c.config.CaCertFile, caCert)
|
||||
c.saveConfig()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetCluster updates cluster information using the given machine list.
|
||||
func (c *Client) SetCluster(machines []string) bool {
|
||||
success := c.internalSyncCluster(machines)
|
||||
return success
|
||||
}
|
||||
|
||||
func (c *Client) GetCluster() []string {
|
||||
return c.cluster.Machines
|
||||
}
|
||||
|
||||
// SyncCluster updates the cluster information using the internal machine list.
|
||||
func (c *Client) SyncCluster() bool {
|
||||
return c.internalSyncCluster(c.cluster.Machines)
|
||||
}
|
||||
|
||||
// internalSyncCluster syncs cluster information using the given machine list.
|
||||
func (c *Client) internalSyncCluster(machines []string) bool {
|
||||
for _, machine := range machines {
|
||||
httpPath := c.createHttpPath(machine, path.Join(version, "machines"))
|
||||
resp, err := c.httpClient.Get(httpPath)
|
||||
if err != nil {
|
||||
// try another machine in the cluster
|
||||
continue
|
||||
} else {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
// try another machine in the cluster
|
||||
continue
|
||||
}
|
||||
|
||||
// update Machines List
|
||||
c.cluster.updateFromStr(string(b))
|
||||
|
||||
// update leader
|
||||
// the first one in the machine list is the leader
|
||||
c.cluster.switchLeader(0)
|
||||
|
||||
logger.Debug("sync.machines ", c.cluster.Machines)
|
||||
c.saveConfig()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// createHttpPath creates a complete HTTP URL.
|
||||
// serverName should contain both the host name and a port number, if any.
|
||||
func (c *Client) createHttpPath(serverName string, _path string) string {
|
||||
u, err := url.Parse(serverName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, _path)
|
||||
|
||||
if u.Scheme == "" {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// dial attempts to open a TCP connection to the provided address, explicitly
|
||||
// enabling keep-alives with a one-second interval.
|
||||
func (c *Client) dial(network, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(network, addr, c.config.DialTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpConn, ok := conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("Failed type-assertion of net.Conn as *net.TCPConn")
|
||||
}
|
||||
|
||||
// Keep TCP alive to check whether or not the remote machine is down
|
||||
if err = tcpConn.SetKeepAlive(true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tcpConn.SetKeepAlivePeriod(time.Second); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tcpConn, nil
|
||||
}
|
||||
|
||||
func (c *Client) OpenCURL() {
|
||||
c.cURLch = make(chan string, defaultBufferSize)
|
||||
}
|
||||
|
||||
func (c *Client) CloseCURL() {
|
||||
c.cURLch = nil
|
||||
}
|
||||
|
||||
func (c *Client) sendCURL(command string) {
|
||||
go func() {
|
||||
select {
|
||||
case c.cURLch <- command:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) RecvCURL() string {
|
||||
return <-c.cURLch
|
||||
}
|
||||
|
||||
// saveConfig saves the current config using c.persistence.
|
||||
func (c *Client) saveConfig() error {
|
||||
if c.persistence != nil {
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.persistence.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the Marshaller interface
|
||||
// as defined by the standard JSON package.
|
||||
func (c *Client) MarshalJSON() ([]byte, error) {
|
||||
b, err := json.Marshal(struct {
|
||||
Config Config `json:"config"`
|
||||
Cluster *Cluster `json:"cluster"`
|
||||
}{
|
||||
Config: c.config,
|
||||
Cluster: c.cluster,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the Unmarshaller interface
|
||||
// as defined by the standard JSON package.
|
||||
func (c *Client) UnmarshalJSON(b []byte) error {
|
||||
temp := struct {
|
||||
Config Config `json:"config"`
|
||||
Cluster *Cluster `json:"cluster"`
|
||||
}{}
|
||||
err := json.Unmarshal(b, &temp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.cluster = temp.Cluster
|
||||
c.config = temp.Config
|
||||
return nil
|
||||
}
|
96
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go
generated
vendored
96
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go
generated
vendored
@ -1,96 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// To pass this test, we need to create a cluster of 3 machines
|
||||
// The server should be listening on 127.0.0.1:4001, 4002, 4003
|
||||
func TestSync(t *testing.T) {
|
||||
fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003")
|
||||
|
||||
// Explicit trailing slash to ensure this doesn't reproduce:
|
||||
// https://github.com/coreos/go-etcd/issues/82
|
||||
c := NewClient([]string{"http://127.0.0.1:4001/"})
|
||||
|
||||
success := c.SyncCluster()
|
||||
if !success {
|
||||
t.Fatal("cannot sync machines")
|
||||
}
|
||||
|
||||
for _, m := range c.GetCluster() {
|
||||
u, err := url.Parse(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u.Scheme != "http" {
|
||||
t.Fatal("scheme must be http")
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if host != "127.0.0.1" {
|
||||
t.Fatal("Host must be 127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
badMachines := []string{"abc", "edef"}
|
||||
|
||||
success = c.SetCluster(badMachines)
|
||||
|
||||
if success {
|
||||
t.Fatal("should not sync on bad machines")
|
||||
}
|
||||
|
||||
goodMachines := []string{"127.0.0.1:4002"}
|
||||
|
||||
success = c.SetCluster(goodMachines)
|
||||
|
||||
if !success {
|
||||
t.Fatal("cannot sync machines")
|
||||
} else {
|
||||
fmt.Println(c.cluster.Machines)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPersistence(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
c.SyncCluster()
|
||||
|
||||
fo, err := os.Create("config.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := fo.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.SetPersistence(fo)
|
||||
err = c.saveConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c2, err := NewClientFromFile("config.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify that the two clients have the same config
|
||||
b1, _ := json.Marshal(c)
|
||||
b2, _ := json.Marshal(c2)
|
||||
|
||||
if string(b1) != string(b2) {
|
||||
t.Fatalf("The two configs should be equal!")
|
||||
}
|
||||
}
|
51
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go
generated
vendored
51
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go
generated
vendored
@ -1,51 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Cluster struct {
|
||||
Leader string `json:"leader"`
|
||||
Machines []string `json:"machines"`
|
||||
}
|
||||
|
||||
func NewCluster(machines []string) *Cluster {
|
||||
// if an empty slice was sent in then just assume HTTP 4001 on localhost
|
||||
if len(machines) == 0 {
|
||||
machines = []string{"http://127.0.0.1:4001"}
|
||||
}
|
||||
|
||||
// default leader and machines
|
||||
return &Cluster{
|
||||
Leader: machines[0],
|
||||
Machines: machines,
|
||||
}
|
||||
}
|
||||
|
||||
// switchLeader switch the current leader to machines[num]
|
||||
func (cl *Cluster) switchLeader(num int) {
|
||||
logger.Debugf("switch.leader[from %v to %v]",
|
||||
cl.Leader, cl.Machines[num])
|
||||
|
||||
cl.Leader = cl.Machines[num]
|
||||
}
|
||||
|
||||
func (cl *Cluster) updateFromStr(machines string) {
|
||||
cl.Machines = strings.Split(machines, ", ")
|
||||
}
|
||||
|
||||
func (cl *Cluster) updateLeader(leader string) {
|
||||
logger.Debugf("update.leader[%s,%s]", cl.Leader, leader)
|
||||
cl.Leader = leader
|
||||
}
|
||||
|
||||
func (cl *Cluster) updateLeaderFromURL(u *url.URL) {
|
||||
var leader string
|
||||
if u.Scheme == "" {
|
||||
leader = "http://" + u.Host
|
||||
} else {
|
||||
leader = u.Scheme + "://" + u.Host
|
||||
}
|
||||
cl.updateLeader(leader)
|
||||
}
|
34
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete.go
generated
vendored
34
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete.go
generated
vendored
@ -1,34 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *Client) CompareAndDelete(key string, prevValue string, prevIndex uint64) (*Response, error) {
|
||||
raw, err := c.RawCompareAndDelete(key, prevValue, prevIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawCompareAndDelete(key string, prevValue string, prevIndex uint64) (*RawResponse, error) {
|
||||
if prevValue == "" && prevIndex == 0 {
|
||||
return nil, fmt.Errorf("You must give either prevValue or prevIndex.")
|
||||
}
|
||||
|
||||
options := Options{}
|
||||
if prevValue != "" {
|
||||
options["prevValue"] = prevValue
|
||||
}
|
||||
if prevIndex != 0 {
|
||||
options["prevIndex"] = prevIndex
|
||||
}
|
||||
|
||||
raw, err := c.delete(key, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw, err
|
||||
}
|
46
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go
generated
vendored
46
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go
generated
vendored
@ -1,46 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareAndDelete(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
// This should succeed an correct prevValue
|
||||
resp, err := c.CompareAndDelete("foo", "bar", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndDelete 1 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, _ = c.Set("foo", "bar", 5)
|
||||
// This should fail because it gives an incorrect prevValue
|
||||
_, err = c.CompareAndDelete("foo", "xxx", 0)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndDelete 2 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
|
||||
// This should succeed because it gives an correct prevIndex
|
||||
resp, err = c.CompareAndDelete("foo", "", resp.Node.ModifiedIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
// This should fail because it gives an incorrect prevIndex
|
||||
resp, err = c.CompareAndDelete("foo", "", 29817514)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndDelete 4 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
}
|
36
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap.go
generated
vendored
36
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap.go
generated
vendored
@ -1,36 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *Client) CompareAndSwap(key string, value string, ttl uint64,
|
||||
prevValue string, prevIndex uint64) (*Response, error) {
|
||||
raw, err := c.RawCompareAndSwap(key, value, ttl, prevValue, prevIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawCompareAndSwap(key string, value string, ttl uint64,
|
||||
prevValue string, prevIndex uint64) (*RawResponse, error) {
|
||||
if prevValue == "" && prevIndex == 0 {
|
||||
return nil, fmt.Errorf("You must give either prevValue or prevIndex.")
|
||||
}
|
||||
|
||||
options := Options{}
|
||||
if prevValue != "" {
|
||||
options["prevValue"] = prevValue
|
||||
}
|
||||
if prevIndex != 0 {
|
||||
options["prevIndex"] = prevIndex
|
||||
}
|
||||
|
||||
raw, err := c.put(key, value, ttl, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw, err
|
||||
}
|
57
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go
generated
vendored
57
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go
generated
vendored
@ -1,57 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareAndSwap(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
// This should succeed
|
||||
resp, err := c.CompareAndSwap("foo", "bar2", 5, "bar", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 1 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because it gives an incorrect prevValue
|
||||
resp, err = c.CompareAndSwap("foo", "bar3", 5, "xxx", 0)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndSwap 2 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed
|
||||
resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 3 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because it gives an incorrect prevIndex
|
||||
resp, err = c.CompareAndSwap("foo", "bar3", 5, "", 29817514)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndSwap 4 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
}
|
55
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug.go
generated
vendored
55
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug.go
generated
vendored
@ -1,55 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var logger *etcdLogger
|
||||
|
||||
func SetLogger(l *log.Logger) {
|
||||
logger = &etcdLogger{l}
|
||||
}
|
||||
|
||||
func GetLogger() *log.Logger {
|
||||
return logger.log
|
||||
}
|
||||
|
||||
type etcdLogger struct {
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Debug(args ...interface{}) {
|
||||
msg := "DEBUG: " + fmt.Sprint(args...)
|
||||
p.log.Println(msg)
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Debugf(f string, args ...interface{}) {
|
||||
msg := "DEBUG: " + fmt.Sprintf(f, args...)
|
||||
// Append newline if necessary
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg = msg + "\n"
|
||||
}
|
||||
p.log.Print(msg)
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Warning(args ...interface{}) {
|
||||
msg := "WARNING: " + fmt.Sprint(args...)
|
||||
p.log.Println(msg)
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Warningf(f string, args ...interface{}) {
|
||||
msg := "WARNING: " + fmt.Sprintf(f, args...)
|
||||
// Append newline if necessary
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg = msg + "\n"
|
||||
}
|
||||
p.log.Print(msg)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Default logger uses the go default log.
|
||||
SetLogger(log.New(ioutil.Discard, "go-etcd", log.LstdFlags))
|
||||
}
|
28
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go
generated
vendored
28
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go
generated
vendored
@ -1,28 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Foo struct{}
|
||||
type Bar struct {
|
||||
one string
|
||||
two int
|
||||
}
|
||||
|
||||
// Tests that logs don't panic with arbitrary interfaces
|
||||
func TestDebug(t *testing.T) {
|
||||
f := &Foo{}
|
||||
b := &Bar{"asfd", 3}
|
||||
for _, test := range []interface{}{
|
||||
1234,
|
||||
"asdf",
|
||||
f,
|
||||
b,
|
||||
} {
|
||||
logger.Debug(test)
|
||||
logger.Debugf("something, %s", test)
|
||||
logger.Warning(test)
|
||||
logger.Warningf("something, %s", test)
|
||||
}
|
||||
}
|
40
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete.go
generated
vendored
40
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete.go
generated
vendored
@ -1,40 +0,0 @@
|
||||
package etcd
|
||||
|
||||
// Delete deletes the given key.
|
||||
//
|
||||
// When recursive set to false, if the key points to a
|
||||
// directory the method will fail.
|
||||
//
|
||||
// When recursive set to true, if the key points to a file,
|
||||
// the file will be deleted; if the key points to a directory,
|
||||
// then everything under the directory (including all child directories)
|
||||
// will be deleted.
|
||||
func (c *Client) Delete(key string, recursive bool) (*Response, error) {
|
||||
raw, err := c.RawDelete(key, recursive, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// DeleteDir deletes an empty directory or a key value pair
|
||||
func (c *Client) DeleteDir(key string) (*Response, error) {
|
||||
raw, err := c.RawDelete(key, false, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawDelete(key string, recursive bool, dir bool) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"recursive": recursive,
|
||||
"dir": dir,
|
||||
}
|
||||
|
||||
return c.delete(key, ops)
|
||||
}
|
81
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go
generated
vendored
81
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go
generated
vendored
@ -1,81 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
resp, err := c.Delete("foo", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Node.Value == "") {
|
||||
t.Fatalf("Delete failed with %s", resp.Node.Value)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Value == "bar") {
|
||||
t.Fatalf("Delete PrevNode failed with %s", resp.Node.Value)
|
||||
}
|
||||
|
||||
resp, err = c.Delete("foo", false)
|
||||
if err == nil {
|
||||
t.Fatalf("Delete should have failed because the key foo did not exist. "+
|
||||
"The response was: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
c.SetDir("foo", 5)
|
||||
// test delete an empty dir
|
||||
resp, err := c.DeleteDir("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Node.Value == "") {
|
||||
t.Fatalf("DeleteAll 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") {
|
||||
t.Fatalf("DeleteAll 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
c.Set("fooDir/foo", "bar", 5)
|
||||
_, err = c.DeleteDir("fooDir")
|
||||
if err == nil {
|
||||
t.Fatal("should not able to delete a non-empty dir with deletedir")
|
||||
}
|
||||
|
||||
resp, err = c.Delete("fooDir", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Node.Value == "") {
|
||||
t.Fatalf("DeleteAll 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") {
|
||||
t.Fatalf("DeleteAll 2 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.Delete("foo", true)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+
|
||||
"The response was: %v", resp)
|
||||
}
|
||||
}
|
48
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go
generated
vendored
48
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go
generated
vendored
@ -1,48 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCodeEtcdNotReachable = 501
|
||||
)
|
||||
|
||||
var (
|
||||
errorMap = map[int]string{
|
||||
ErrCodeEtcdNotReachable: "All the given peers are not reachable",
|
||||
}
|
||||
)
|
||||
|
||||
type EtcdError struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause,omitempty"`
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
func (e EtcdError) Error() string {
|
||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.ErrorCode, e.Message, e.Cause, e.Index)
|
||||
}
|
||||
|
||||
func newError(errorCode int, cause string, index uint64) *EtcdError {
|
||||
return &EtcdError{
|
||||
ErrorCode: errorCode,
|
||||
Message: errorMap[errorCode],
|
||||
Cause: cause,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(b []byte) error {
|
||||
etcdErr := new(EtcdError)
|
||||
|
||||
err := json.Unmarshal(b, etcdErr)
|
||||
if err != nil {
|
||||
logger.Warningf("cannot unmarshal etcd error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return etcdErr
|
||||
}
|
27
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go
generated
vendored
27
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go
generated
vendored
@ -1,27 +0,0 @@
|
||||
package etcd
|
||||
|
||||
// Get gets the file or directory associated with the given key.
|
||||
// If the key points to a directory, files and directories under
|
||||
// it will be returned in sorted or unsorted order, depending on
|
||||
// the sort flag.
|
||||
// If recursive is set to false, contents under child directories
|
||||
// will not be returned.
|
||||
// If recursive is set to true, all the contents will be returned.
|
||||
func (c *Client) Get(key string, sort, recursive bool) (*Response, error) {
|
||||
raw, err := c.RawGet(key, sort, recursive)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"recursive": recursive,
|
||||
"sorted": sort,
|
||||
}
|
||||
|
||||
return c.get(key, ops)
|
||||
}
|
131
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go
generated
vendored
131
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go
generated
vendored
@ -1,131 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// cleanNode scrubs Expiration, ModifiedIndex and CreatedIndex of a node.
|
||||
func cleanNode(n *Node) {
|
||||
n.Expiration = nil
|
||||
n.ModifiedIndex = 0
|
||||
n.CreatedIndex = 0
|
||||
}
|
||||
|
||||
// cleanResult scrubs a result object two levels deep of Expiration,
|
||||
// ModifiedIndex and CreatedIndex.
|
||||
func cleanResult(result *Response) {
|
||||
// TODO(philips): make this recursive.
|
||||
cleanNode(result.Node)
|
||||
for i, _ := range result.Node.Nodes {
|
||||
cleanNode(result.Node.Nodes[i])
|
||||
for j, _ := range result.Node.Nodes[i].Nodes {
|
||||
cleanNode(result.Node.Nodes[i].Nodes[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
result, err := c.Get("foo", false, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result.Node.Key != "/foo" || result.Node.Value != "bar" {
|
||||
t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL)
|
||||
}
|
||||
|
||||
result, err = c.Get("goo", false, false)
|
||||
if err == nil {
|
||||
t.Fatalf("should not be able to get non-exist key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
c.Set("fooDir/k0", "v0", 5)
|
||||
c.Set("fooDir/k1", "v1", 5)
|
||||
|
||||
// Return kv-pairs in sorted order
|
||||
result, err := c.Get("fooDir", true, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := Nodes{
|
||||
&Node{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
TTL: 5,
|
||||
},
|
||||
&Node{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
TTL: 5,
|
||||
},
|
||||
}
|
||||
|
||||
cleanResult(result)
|
||||
|
||||
if !reflect.DeepEqual(result.Node.Nodes, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
|
||||
}
|
||||
|
||||
// Test the `recursive` option
|
||||
c.CreateDir("fooDir/childDir", 5)
|
||||
c.Set("fooDir/childDir/k2", "v2", 5)
|
||||
|
||||
// Return kv-pairs in sorted order
|
||||
result, err = c.Get("fooDir", true, true)
|
||||
|
||||
cleanResult(result)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = Nodes{
|
||||
&Node{
|
||||
Key: "/fooDir/childDir",
|
||||
Dir: true,
|
||||
Nodes: Nodes{
|
||||
&Node{
|
||||
Key: "/fooDir/childDir/k2",
|
||||
Value: "v2",
|
||||
TTL: 5,
|
||||
},
|
||||
},
|
||||
TTL: 5,
|
||||
},
|
||||
&Node{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
TTL: 5,
|
||||
},
|
||||
&Node{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
TTL: 5,
|
||||
},
|
||||
}
|
||||
|
||||
cleanResult(result)
|
||||
|
||||
if !reflect.DeepEqual(result.Node.Nodes, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
|
||||
}
|
||||
}
|
72
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go
generated
vendored
72
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go
generated
vendored
@ -1,72 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Options map[string]interface{}
|
||||
|
||||
// An internally-used data structure that represents a mapping
|
||||
// between valid options and their kinds
|
||||
type validOptions map[string]reflect.Kind
|
||||
|
||||
// Valid options for GET, PUT, POST, DELETE
|
||||
// Using CAPITALIZED_UNDERSCORE to emphasize that these
|
||||
// values are meant to be used as constants.
|
||||
var (
|
||||
VALID_GET_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
"consistent": reflect.Bool,
|
||||
"sorted": reflect.Bool,
|
||||
"wait": reflect.Bool,
|
||||
"waitIndex": reflect.Uint64,
|
||||
}
|
||||
|
||||
VALID_PUT_OPTIONS = validOptions{
|
||||
"prevValue": reflect.String,
|
||||
"prevIndex": reflect.Uint64,
|
||||
"prevExist": reflect.Bool,
|
||||
"dir": reflect.Bool,
|
||||
}
|
||||
|
||||
VALID_POST_OPTIONS = validOptions{}
|
||||
|
||||
VALID_DELETE_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
"dir": reflect.Bool,
|
||||
"prevValue": reflect.String,
|
||||
"prevIndex": reflect.Uint64,
|
||||
}
|
||||
)
|
||||
|
||||
// Convert options to a string of HTML parameters
|
||||
func (ops Options) toParameters(validOps validOptions) (string, error) {
|
||||
p := "?"
|
||||
values := url.Values{}
|
||||
|
||||
if ops == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
for k, v := range ops {
|
||||
// Check if the given option is valid (that it exists)
|
||||
kind := validOps[k]
|
||||
if kind == reflect.Invalid {
|
||||
return "", fmt.Errorf("Invalid option: %v", k)
|
||||
}
|
||||
|
||||
// Check if the given option is of the valid type
|
||||
t := reflect.TypeOf(v)
|
||||
if kind != t.Kind() {
|
||||
return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.",
|
||||
k, kind, t.Kind())
|
||||
}
|
||||
|
||||
values.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
p += values.Encode()
|
||||
return p, nil
|
||||
}
|
396
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go
generated
vendored
396
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go
generated
vendored
@ -1,396 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Errors introduced by handling requests
|
||||
var (
|
||||
ErrRequestCancelled = errors.New("sending request is cancelled")
|
||||
)
|
||||
|
||||
type RawRequest struct {
|
||||
Method string
|
||||
RelativePath string
|
||||
Values url.Values
|
||||
Cancel <-chan bool
|
||||
}
|
||||
|
||||
// NewRawRequest returns a new RawRequest
|
||||
func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan bool) *RawRequest {
|
||||
return &RawRequest{
|
||||
Method: method,
|
||||
RelativePath: relativePath,
|
||||
Values: values,
|
||||
Cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
// getCancelable issues a cancelable GET request
|
||||
func (c *Client) getCancelable(key string, options Options,
|
||||
cancel <-chan bool) (*RawResponse, error) {
|
||||
logger.Debugf("get %s [%s]", key, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
// If consistency level is set to STRONG, append
|
||||
// the `consistent` query string.
|
||||
if c.config.Consistency == STRONG_CONSISTENCY {
|
||||
options["consistent"] = true
|
||||
}
|
||||
|
||||
str, err := options.toParameters(VALID_GET_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
req := NewRawRequest("GET", p, nil, cancel)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// get issues a GET request
|
||||
func (c *Client) get(key string, options Options) (*RawResponse, error) {
|
||||
return c.getCancelable(key, options, nil)
|
||||
}
|
||||
|
||||
// put issues a PUT request
|
||||
func (c *Client) put(key string, value string, ttl uint64,
|
||||
options Options) (*RawResponse, error) {
|
||||
|
||||
logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
str, err := options.toParameters(VALID_PUT_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
req := NewRawRequest("PUT", p, buildValues(value, ttl), nil)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// post issues a POST request
|
||||
func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
req := NewRawRequest("POST", p, buildValues(value, ttl), nil)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// delete issues a DELETE request
|
||||
func (c *Client) delete(key string, options Options) (*RawResponse, error) {
|
||||
logger.Debugf("delete %s [%s]", key, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
str, err := options.toParameters(VALID_DELETE_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
req := NewRawRequest("DELETE", p, nil, nil)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SendRequest sends a HTTP request and returns a Response as defined by etcd
|
||||
func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) {
|
||||
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
var httpPath string
|
||||
var err error
|
||||
var respBody []byte
|
||||
|
||||
var numReqs = 1
|
||||
|
||||
checkRetry := c.CheckRetry
|
||||
if checkRetry == nil {
|
||||
checkRetry = DefaultCheckRetry
|
||||
}
|
||||
|
||||
cancelled := make(chan bool, 1)
|
||||
reqLock := new(sync.Mutex)
|
||||
|
||||
if rr.Cancel != nil {
|
||||
cancelRoutine := make(chan bool)
|
||||
defer close(cancelRoutine)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-rr.Cancel:
|
||||
cancelled <- true
|
||||
logger.Debug("send.request is cancelled")
|
||||
case <-cancelRoutine:
|
||||
return
|
||||
}
|
||||
|
||||
// Repeat canceling request until this thread is stopped
|
||||
// because we have no idea about whether it succeeds.
|
||||
for {
|
||||
reqLock.Lock()
|
||||
c.httpClient.Transport.(*http.Transport).CancelRequest(req)
|
||||
reqLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-cancelRoutine:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// If we connect to a follower and consistency is required, retry until
|
||||
// we connect to a leader
|
||||
sleep := 25 * time.Millisecond
|
||||
maxSleep := time.Second
|
||||
|
||||
for attempt := 0; ; attempt++ {
|
||||
if attempt > 0 {
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil, ErrRequestCancelled
|
||||
case <-time.After(sleep):
|
||||
sleep = sleep * 2
|
||||
if sleep > maxSleep {
|
||||
sleep = maxSleep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath)
|
||||
|
||||
if rr.Method == "GET" && c.config.Consistency == WEAK_CONSISTENCY {
|
||||
// If it's a GET and consistency level is set to WEAK,
|
||||
// then use a random machine.
|
||||
httpPath = c.getHttpPath(true, rr.RelativePath)
|
||||
} else {
|
||||
// Else use the leader.
|
||||
httpPath = c.getHttpPath(false, rr.RelativePath)
|
||||
}
|
||||
|
||||
// Return a cURL command if curlChan is set
|
||||
if c.cURLch != nil {
|
||||
command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath)
|
||||
for key, value := range rr.Values {
|
||||
command += fmt.Sprintf(" -d %s=%s", key, value[0])
|
||||
}
|
||||
c.sendCURL(command)
|
||||
}
|
||||
|
||||
logger.Debug("send.request.to ", httpPath, " | method ", rr.Method)
|
||||
|
||||
req, err := func() (*http.Request, error) {
|
||||
reqLock.Lock()
|
||||
defer reqLock.Unlock()
|
||||
|
||||
if rr.Values == nil {
|
||||
if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
body := strings.NewReader(rr.Values.Encode())
|
||||
if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type",
|
||||
"application/x-www-form-urlencoded; param=value")
|
||||
}
|
||||
return req, nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err = c.httpClient.Do(req)
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// If the request was cancelled, return ErrRequestCancelled directly
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil, ErrRequestCancelled
|
||||
default:
|
||||
}
|
||||
|
||||
numReqs++
|
||||
|
||||
// network error, change a machine!
|
||||
if err != nil {
|
||||
logger.Debug("network error: ", err.Error())
|
||||
lastResp := http.Response{}
|
||||
if checkErr := checkRetry(c.cluster, numReqs, lastResp, err); checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
|
||||
c.cluster.switchLeader(attempt % len(c.cluster.Machines))
|
||||
continue
|
||||
}
|
||||
|
||||
// if there is no error, it should receive response
|
||||
logger.Debug("recv.response.from ", httpPath)
|
||||
|
||||
if validHttpStatusCode[resp.StatusCode] {
|
||||
// try to read byte code and break the loop
|
||||
respBody, err = ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
logger.Debug("recv.success ", httpPath)
|
||||
break
|
||||
}
|
||||
// ReadAll error may be caused due to cancel request
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil, ErrRequestCancelled
|
||||
default:
|
||||
}
|
||||
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
// underlying connection was closed prematurely, probably by timeout
|
||||
// TODO: empty body or unexpectedEOF can cause http.Transport to get hosed;
|
||||
// this allows the client to detect that and take evasive action. Need
|
||||
// to revisit once code.google.com/p/go/issues/detail?id=8648 gets fixed.
|
||||
respBody = []byte{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if resp is TemporaryRedirect, set the new leader and retry
|
||||
if resp.StatusCode == http.StatusTemporaryRedirect {
|
||||
u, err := resp.Location()
|
||||
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
} else {
|
||||
// Update cluster leader based on redirect location
|
||||
// because it should point to the leader address
|
||||
c.cluster.updateLeaderFromURL(u)
|
||||
logger.Debug("recv.response.relocate ", u.String())
|
||||
}
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
if checkErr := checkRetry(c.cluster, numReqs, *resp,
|
||||
errors.New("Unexpected HTTP status code")); checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
r := &RawResponse{
|
||||
StatusCode: resp.StatusCode,
|
||||
Body: respBody,
|
||||
Header: resp.Header,
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// DefaultCheckRetry defines the retrying behaviour for bad HTTP requests
|
||||
// If we have retried 2 * machine number, stop retrying.
|
||||
// If status code is InternalServerError, sleep for 200ms.
|
||||
func DefaultCheckRetry(cluster *Cluster, numReqs int, lastResp http.Response,
|
||||
err error) error {
|
||||
|
||||
if numReqs >= 2*len(cluster.Machines) {
|
||||
return newError(ErrCodeEtcdNotReachable,
|
||||
"Tried to connect to each peer twice and failed", 0)
|
||||
}
|
||||
|
||||
code := lastResp.StatusCode
|
||||
if code == http.StatusInternalServerError {
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
}
|
||||
|
||||
logger.Warning("bad response status code", code)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getHttpPath(random bool, s ...string) string {
|
||||
var machine string
|
||||
if random {
|
||||
machine = c.cluster.Machines[rand.Intn(len(c.cluster.Machines))]
|
||||
} else {
|
||||
machine = c.cluster.Leader
|
||||
}
|
||||
|
||||
fullPath := machine + "/" + version
|
||||
for _, seg := range s {
|
||||
fullPath = fullPath + "/" + seg
|
||||
}
|
||||
|
||||
return fullPath
|
||||
}
|
||||
|
||||
// buildValues builds a url.Values map according to the given value and ttl
|
||||
func buildValues(value string, ttl uint64) url.Values {
|
||||
v := url.Values{}
|
||||
|
||||
if value != "" {
|
||||
v.Set("value", value)
|
||||
}
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// convert key string to http path exclude version
|
||||
// for example: key[foo] -> path[keys/foo]
|
||||
// key[/] -> path[keys/]
|
||||
func keyToPath(key string) string {
|
||||
p := path.Join("keys", key)
|
||||
|
||||
// corner case: if key is "/" or "//" ect
|
||||
// path join will clear the tailing "/"
|
||||
// we need to add it back
|
||||
if p == "keys" {
|
||||
p = "keys/"
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
89
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/response.go
generated
vendored
89
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/response.go
generated
vendored
@ -1,89 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
rawResponse = iota
|
||||
normalResponse
|
||||
)
|
||||
|
||||
type responseType int
|
||||
|
||||
type RawResponse struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
var (
|
||||
validHttpStatusCode = map[int]bool{
|
||||
http.StatusCreated: true,
|
||||
http.StatusOK: true,
|
||||
http.StatusBadRequest: true,
|
||||
http.StatusNotFound: true,
|
||||
http.StatusPreconditionFailed: true,
|
||||
http.StatusForbidden: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Unmarshal parses RawResponse and stores the result in Response
|
||||
func (rr *RawResponse) Unmarshal() (*Response, error) {
|
||||
if rr.StatusCode != http.StatusOK && rr.StatusCode != http.StatusCreated {
|
||||
return nil, handleError(rr.Body)
|
||||
}
|
||||
|
||||
resp := new(Response)
|
||||
|
||||
err := json.Unmarshal(rr.Body, resp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// attach index and term to response
|
||||
resp.EtcdIndex, _ = strconv.ParseUint(rr.Header.Get("X-Etcd-Index"), 10, 64)
|
||||
resp.RaftIndex, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Index"), 10, 64)
|
||||
resp.RaftTerm, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Term"), 10, 64)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Action string `json:"action"`
|
||||
Node *Node `json:"node"`
|
||||
PrevNode *Node `json:"prevNode,omitempty"`
|
||||
EtcdIndex uint64 `json:"etcdIndex"`
|
||||
RaftIndex uint64 `json:"raftIndex"`
|
||||
RaftTerm uint64 `json:"raftTerm"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Key string `json:"key, omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
Nodes Nodes `json:"nodes,omitempty"`
|
||||
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
|
||||
CreatedIndex uint64 `json:"createdIndex,omitempty"`
|
||||
}
|
||||
|
||||
type Nodes []*Node
|
||||
|
||||
// interfaces for sorting
|
||||
func (ns Nodes) Len() int {
|
||||
return len(ns)
|
||||
}
|
||||
|
||||
func (ns Nodes) Less(i, j int) bool {
|
||||
return ns[i].Key < ns[j].Key
|
||||
}
|
||||
|
||||
func (ns Nodes) Swap(i, j int) {
|
||||
ns[i], ns[j] = ns[j], ns[i]
|
||||
}
|
42
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go
generated
vendored
42
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go
generated
vendored
@ -1,42 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetCurlChan(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
c.OpenCURL()
|
||||
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
_, err := c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5",
|
||||
c.cluster.Leader)
|
||||
actual := c.RecvCURL()
|
||||
if expected != actual {
|
||||
t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
|
||||
actual, expected)
|
||||
}
|
||||
|
||||
c.SetConsistency(STRONG_CONSISTENCY)
|
||||
_, err = c.Get("foo", false, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false",
|
||||
c.cluster.Leader)
|
||||
actual = c.RecvCURL()
|
||||
if expected != actual {
|
||||
t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
|
||||
actual, expected)
|
||||
}
|
||||
}
|
137
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create.go
generated
vendored
137
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create.go
generated
vendored
@ -1,137 +0,0 @@
|
||||
package etcd
|
||||
|
||||
// Set sets the given key to the given value.
|
||||
// It will create a new key value pair or replace the old one.
|
||||
// It will not replace a existing directory.
|
||||
func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawSet(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// SetDir sets the given key to a directory.
|
||||
// It will create a new directory or replace the old key value pair by a directory.
|
||||
// It will not replace a existing directory.
|
||||
func (c *Client) SetDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawSetDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// CreateDir creates a directory. It succeeds only if
|
||||
// the given key does not yet exist.
|
||||
func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawCreateDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// UpdateDir updates the given directory. It succeeds only if the
|
||||
// given key already exists.
|
||||
func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawUpdateDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Create creates a file with the given value under the given key. It succeeds
|
||||
// only if the given key does not yet exist.
|
||||
func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawCreate(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// CreateInOrder creates a file with a key that's guaranteed to be higher than other
|
||||
// keys in the given directory. It is useful for creating queues.
|
||||
func (c *Client) CreateInOrder(dir string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawCreateInOrder(dir, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Update updates the given key to the given value. It succeeds only if the
|
||||
// given key already exists.
|
||||
func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawUpdate(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": true,
|
||||
"dir": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": false,
|
||||
"dir": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
return c.put(key, value, ttl, nil)
|
||||
}
|
||||
|
||||
func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"dir": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": true,
|
||||
}
|
||||
|
||||
return c.put(key, value, ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": false,
|
||||
}
|
||||
|
||||
return c.put(key, value, ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreateInOrder(dir string, value string, ttl uint64) (*RawResponse, error) {
|
||||
return c.post(dir, value, ttl)
|
||||
}
|
241
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go
generated
vendored
241
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go
generated
vendored
@ -1,241 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
resp, err := c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 {
|
||||
t.Fatalf("Set 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("Set 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.Set("foo", "bar2", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Set 2 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode.Key != "/foo" || resp.PrevNode.Value != "bar" || resp.Node.TTL != 5 {
|
||||
t.Fatalf("Set 2 PrevNode failed: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
c.Delete("nonexistent", true)
|
||||
}()
|
||||
|
||||
resp, err := c.Set("foo", "bar", 5)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed.
|
||||
resp, err = c.Update("foo", "wakawaka", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "update" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Update 1 failed: %#v", resp)
|
||||
}
|
||||
if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Update 1 prevValue failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because the key does not exist.
|
||||
resp, err = c.Update("nonexistent", "whatever", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did not exist, so the update should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("newKey", true)
|
||||
}()
|
||||
|
||||
newKey := "/newKey"
|
||||
newValue := "/newValue"
|
||||
|
||||
// This should succeed
|
||||
resp, err := c.Create(newKey, newValue, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Key == newKey &&
|
||||
resp.Node.Value == newValue && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("Create 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail, because the key is already there
|
||||
resp, err = c.Create(newKey, newValue, 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did exist, so the creation should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateInOrder(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
dir := "/queue"
|
||||
defer func() {
|
||||
c.DeleteDir(dir)
|
||||
}()
|
||||
|
||||
var firstKey, secondKey string
|
||||
|
||||
resp, err := c.CreateInOrder(dir, "1", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Value == "1" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
firstKey = resp.Node.Key
|
||||
|
||||
resp, err = c.CreateInOrder(dir, "2", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Value == "2" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
secondKey = resp.Node.Key
|
||||
|
||||
if firstKey >= secondKey {
|
||||
t.Fatalf("Expected first key to be greater than second key, but %s is not greater than %s",
|
||||
firstKey, secondKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
resp, err := c.CreateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("SetDir 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("SetDir 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because /fooDir already points to a directory
|
||||
resp, err = c.CreateDir("/fooDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("fooDir already points to a directory, so SetDir should have failed."+
|
||||
"The response was: %#v", resp)
|
||||
}
|
||||
|
||||
_, err = c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed
|
||||
// It should replace the key
|
||||
resp, err = c.SetDir("foo", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/foo" && resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("SetDir 2 failed: %#v", resp)
|
||||
}
|
||||
if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("SetDir 2 failed: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
resp, err := c.CreateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed.
|
||||
resp, err = c.UpdateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "update" && resp.Node.Key == "/fooDir" &&
|
||||
resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("UpdateDir 1 failed: %#v", resp)
|
||||
}
|
||||
if !(resp.PrevNode.Key == "/fooDir" && resp.PrevNode.Dir == true && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("UpdateDir 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because the key does not exist.
|
||||
resp, err = c.UpdateDir("nonexistentDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did not exist, so the update should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
// This should succeed
|
||||
resp, err := c.CreateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Key == "/fooDir" &&
|
||||
resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CreateDir 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("CreateDir 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail, because the key is already there
|
||||
resp, err = c.CreateDir("fooDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did exist, so the creation should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
3
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go
generated
vendored
3
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go
generated
vendored
@ -1,3 +0,0 @@
|
||||
package etcd
|
||||
|
||||
const version = "v2"
|
103
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch.go
generated
vendored
103
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch.go
generated
vendored
@ -1,103 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Errors introduced by the Watch command.
|
||||
var (
|
||||
ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel")
|
||||
)
|
||||
|
||||
// If recursive is set to true the watch returns the first change under the given
|
||||
// prefix since the given index.
|
||||
//
|
||||
// If recursive is set to false the watch returns the first change to the given key
|
||||
// since the given index.
|
||||
//
|
||||
// To watch for the latest change, set waitIndex = 0.
|
||||
//
|
||||
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
|
||||
//channel. After someone receives the channel, it will go on to watch that
|
||||
// prefix. If a stop channel is given, the client can close long-term watch using
|
||||
// the stop channel.
|
||||
func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool,
|
||||
receiver chan *Response, stop chan bool) (*Response, error) {
|
||||
logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
defer close(receiver)
|
||||
|
||||
for {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := raw.Unmarshal()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waitIndex = resp.Node.ModifiedIndex + 1
|
||||
receiver <- resp
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool,
|
||||
receiver chan *RawResponse, stop chan bool) (*RawResponse, error) {
|
||||
|
||||
logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
return c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
}
|
||||
|
||||
for {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := raw.Unmarshal()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waitIndex = resp.Node.ModifiedIndex + 1
|
||||
receiver <- raw
|
||||
}
|
||||
}
|
||||
|
||||
// helper func
|
||||
// return when there is change under the given prefix
|
||||
func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) {
|
||||
|
||||
options := Options{
|
||||
"wait": true,
|
||||
}
|
||||
if waitIndex > 0 {
|
||||
options["waitIndex"] = waitIndex
|
||||
}
|
||||
if recursive {
|
||||
options["recursive"] = true
|
||||
}
|
||||
|
||||
resp, err := c.getCancelable(key, options, stop)
|
||||
|
||||
if err == ErrRequestCancelled {
|
||||
return nil, ErrWatchStoppedByUser
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
119
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go
generated
vendored
119
Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go
generated
vendored
@ -1,119 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("watch_foo", true)
|
||||
}()
|
||||
|
||||
go setHelper("watch_foo", "bar", c)
|
||||
|
||||
resp, err := c.Watch("watch_foo", 0, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("Watch 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
go setHelper("watch_foo", "bar", c)
|
||||
|
||||
resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("Watch 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
routineNum := runtime.NumGoroutine()
|
||||
|
||||
ch := make(chan *Response, 10)
|
||||
stop := make(chan bool, 1)
|
||||
|
||||
go setLoop("watch_foo", "bar", c)
|
||||
|
||||
go receiver(ch, stop)
|
||||
|
||||
_, err = c.Watch("watch_foo", 0, false, ch, stop)
|
||||
if err != ErrWatchStoppedByUser {
|
||||
t.Fatalf("Watch returned a non-user stop error")
|
||||
}
|
||||
|
||||
if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum {
|
||||
t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("watch_foo", true)
|
||||
}()
|
||||
|
||||
go setHelper("watch_foo/foo", "bar", c)
|
||||
|
||||
resp, err := c.Watch("watch_foo", 0, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("WatchAll 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
go setHelper("watch_foo/foo", "bar", c)
|
||||
|
||||
resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("WatchAll 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
ch := make(chan *Response, 10)
|
||||
stop := make(chan bool, 1)
|
||||
|
||||
routineNum := runtime.NumGoroutine()
|
||||
|
||||
go setLoop("watch_foo/foo", "bar", c)
|
||||
|
||||
go receiver(ch, stop)
|
||||
|
||||
_, err = c.Watch("watch_foo", 0, true, ch, stop)
|
||||
if err != ErrWatchStoppedByUser {
|
||||
t.Fatalf("Watch returned a non-user stop error")
|
||||
}
|
||||
|
||||
if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum {
|
||||
t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum)
|
||||
}
|
||||
}
|
||||
|
||||
func setHelper(key, value string, c *Client) {
|
||||
time.Sleep(time.Second)
|
||||
c.Set(key, value, 100)
|
||||
}
|
||||
|
||||
func setLoop(key, value string, c *Client) {
|
||||
time.Sleep(time.Second)
|
||||
for i := 0; i < 10; i++ {
|
||||
newValue := fmt.Sprintf("%s_%v", value, i)
|
||||
c.Set(key, newValue, 100)
|
||||
time.Sleep(time.Second / 10)
|
||||
}
|
||||
}
|
||||
|
||||
func receiver(c chan *Response, stop chan bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
<-c
|
||||
}
|
||||
stop <- true
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user