From 1bfdf55a52ba5f7c37cb082b8bc4a11ef19432a7 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 6 Jan 2016 18:58:12 -0800 Subject: [PATCH 1/4] Updating READMEs Signed-off-by: Mary Anthony --- api/README.md | 45 +--- discovery/README.md | 294 +------------------------ discovery/token/README.md | 32 +-- scheduler/filter/README.md | 416 +---------------------------------- scheduler/strategy/README.md | 124 +---------- 5 files changed, 36 insertions(+), 875 deletions(-) diff --git a/api/README.md b/api/README.md index 80afbfadcc..a965e679ee 100644 --- a/api/README.md +++ b/api/README.md @@ -1,38 +1,9 @@ ---- -page_title: Docker Swarm API -page_description: Swarm API -page_keywords: docker, swarm, clustering, api ---- +# Docker Swarm API README -# Docker Swarm API - -The Docker Swarm API is mostly compatible with the [Docker Remote API](https://docs.docker.com/reference/api/docker_remote_api/). This document is an overview of the differences between the Swarm API and the Docker Remote API. - -## Endpoints which behave differently - -* `GET "/containers/{name:.*}/json"`: New field `Node` added: - -```json -"Node": { - "Id": "ODAI:IC6Q:MSBL:TPB5:HIEE:6IKC:VCAM:QRNH:PRGX:ERZT:OK46:PMFX", - "Ip": "0.0.0.0", - "Addr": "http://0.0.0.0:4243", - "Name": "vagrant-ubuntu-saucy-64", - }, -``` -* `GET "/containers/{name:.*}/json"`: `HostIP` replaced by the the actual Node's IP if `HostIP` is `0.0.0.0` - -* `GET "/containers/json"`: Node's name prepended to the container name. - -* `GET "/containers/json"`: `HostIP` replaced by the the actual Node's IP if `HostIP` is `0.0.0.0` - -* `GET "/containers/json"` : Containers started from the `swarm` official image are hidden by default, use `all=1` to display them. - -* `GET "/images/json"` : Use '--filter node=\' to show images of the specific node. - -## Docker Swarm documentation index - -- [User guide](https://docs.docker.com/swarm/) -- [Discovery options](https://docs.docker.com/swarm/discovery/) -- [Scheduler strategies](https://docs.docker.com/swarm/scheduler/strategy/) -- [Scheduler filters](https://docs.docker.com/swarm/scheduler/filter/) +The Docker Swarm API is mostly compatible with the [Docker Remote +API](https://docs.docker.com/reference/api/docker_remote_api/). To read the +end-user API documentation, visit [the Swarm API documentation on +docs.docker.com](https://docs.docker.com/swarm/api/swarm-api/). If you want to +modify the discovery API documentation, start with [the `docs/api/swarm-api.md` +file](https://github.com/docker/swarm/blob/master/docs/api/swarm-api.md) in this +project. diff --git a/discovery/README.md b/discovery/README.md index 189c42ed53..f2deabfa88 100644 --- a/discovery/README.md +++ b/discovery/README.md @@ -1,286 +1,8 @@ ---- -page_title: Docker Swarm discovery -page_description: Swarm discovery -page_keywords: docker, swarm, clustering, discovery ---- - -# Discovery - -Docker Swarm comes with multiple Discovery backends. - -## Backends - -You use a hosted discovery service with Docker Swarm. The service -maintains a list of IPs in your swarm. There are several available -services, such as `etcd`, `consul` and `zookeeper` depending on what -is best suited for your environment. You can even use a static -file. Docker Hub also provides a hosted discovery service which you -can use. - -### Hosted Discovery with Docker Hub - -This example uses the hosted discovery service on Docker Hub. It is not -meant to be used in production scenarios but for development/testing only. -Using Docker Hub's hosted discovery service requires that each node in the -swarm is connected to the internet. To create your swarm: - -First we create a cluster. - -```bash -# create a cluster -$ swarm create -6856663cdefdec325839a4b7e1de38e8 # <- this is your unique -``` - -Then we create each node and join them to the cluster. - -```bash -# on each of your nodes, start the swarm agent -# doesn't have to be public (eg. 192.168.0.X), -# as long as the swarm manager can access it. -$ swarm join --advertise= token:// -``` - -Finally, we start the Swarm manager. This can be on any machine or even -your laptop. - -```bash -$ swarm manage -H tcp:// token:// -``` - -You can then use regular Docker commands to interact with your swarm. - -```bash -docker -H tcp:// info -docker -H tcp:// run ... -docker -H tcp:// ps -docker -H tcp:// logs ... -... -``` - -You can also list the nodes in your cluster. - -```bash -swarm list token:// - -``` - -### Using a static file describing the cluster - -For each of your nodes, add a line to a file. The node IP address -doesn't need to be public as long the Swarm manager can access it. - -```bash -echo >> /tmp/my_cluster -echo >> /tmp/my_cluster -echo >> /tmp/my_cluster -``` - -Then start the Swarm manager on any machine. - -```bash -swarm manage -H tcp:// file:///tmp/my_cluster -``` - -And then use the regular Docker commands. - -```bash -docker -H tcp:// info -docker -H tcp:// run ... -docker -H tcp:// ps -docker -H tcp:// logs ... -... -``` - -You can list the nodes in your cluster. - -```bash -$ swarm list file:///tmp/my_cluster - - - -``` - -### Using etcd - -On each of your nodes, start the Swarm agent. The node IP address -doesn't have to be public as long as the swarm manager can access it. - -```bash -swarm join --advertise= etcd:/// -``` - -Start the manager on any machine or your laptop. - -```bash -swarm manage -H tcp:// etcd:/// -``` - -And then use the regular Docker commands. - -```bash -docker -H tcp:// info -docker -H tcp:// run ... -docker -H tcp:// ps -docker -H tcp:// logs ... -... -``` - -You can list the nodes in your cluster. - -```bash -swarm list etcd:/// - -``` - -### Using consul - -On each of your nodes, start the Swarm agent. The node IP address -doesn't need to be public as long as the Swarm manager can access it. - -```bash -swarm join --advertise= consul:/// -``` - -Start the manager on any machine or your laptop. - -```bash -swarm manage -H tcp:// consul:/// -``` - -And then use the regular Docker commands. - -```bash -docker -H tcp:// info -docker -H tcp:// run ... -docker -H tcp:// ps -docker -H tcp:// logs ... -... -``` - -You can list the nodes in your cluster. - -```bash -swarm list consul:/// - -``` - -### Using zookeeper - -On each of your nodes, start the Swarm agent. The node IP doesn't have -to be public as long as the swarm manager can access it. - -```bash -swarm join --advertise= zk://,/ -``` - -Start the manager on any machine or your laptop. - -```bash -swarm manage -H tcp:// zk://,/ -``` - -You can then use the regular Docker commands. - -```bash -docker -H tcp:// info -docker -H tcp:// run ... -docker -H tcp:// ps -docker -H tcp:// logs ... -... -``` - -You can list the nodes in the cluster. - -```bash -swarm list zk://,/ - -``` - -### Using a static list of IP addresses - -Start the manager on any machine or your laptop - -```bash -swarm manage -H nodes://, -``` - -Or - -```bash -swarm manage -H , -``` - -Then use the regular Docker commands. - -```bash -docker -H info -docker -H run ... -docker -H ps -docker -H logs ... -... -``` - -### Range pattern for IP addresses - -The `file` and `nodes` discoveries support a range pattern to specify IP -addresses, i.e., `10.0.0.[10:200]` will be a list of nodes starting from -`10.0.0.10` to `10.0.0.200`. - -For example for the `file` discovery method. - -```bash -$ echo "10.0.0.[11:100]:2375" >> /tmp/my_cluster -$ echo "10.0.1.[15:20]:2375" >> /tmp/my_cluster -$ echo "192.168.1.2:[2:20]375" >> /tmp/my_cluster -``` - -Then start the manager. - -```bash -swarm manage -H tcp:// file:///tmp/my_cluster -``` - -And for the `nodes` discovery method. - -```bash -swarm manage -H "nodes://10.0.0.[10:200]:2375,10.0.1.[2:250]:2375" -``` - -## Contributing a new discovery backend - -Contributing a new discovery backend is easy, simply implement this -interface: - -```go -type Discovery interface { - Initialize(string, int) error - Fetch() ([]string, error) - Watch(WatchCallback) - Register(string) error -} -``` - -### Initialize - -The parameters are `discovery` location without the scheme and a heartbeat (in seconds). - -### Fetch - -Returns the list of all the nodes from the discovery. - -### Watch - -Triggers an update (`Fetch`). This can happen either via a timer (like -`token`) or use backend specific features (like `etcd`). - -### Register - -Add a new node to the discovery service. - -## Docker Swarm documentation index - -- [User guide](../docs/index.md) -- [Scheduler strategies](../docs/scheduler/strategy.md) -- [Scheduler filters](../docs/scheduler/filter.md) -- [Swarm API](../docs/api/swarm-api.md) +# Contribute a new discovery backend + +Docker Swarm comes with multiple Discovery backends. To read the end-user +documentation, visit the [Swarm discovery documentation on +docs.docker.com](https://docs.docker.com/swarm/discovery/). If you want to +modify the discovery end-user documentation, start with [the `docs/discovery.md` +file](https://github.com/docker/swarm/blob/master/docs/discovery.md) in this +project. diff --git a/discovery/token/README.md b/discovery/token/README.md index 9b1a758572..4d1a9e81d8 100644 --- a/discovery/token/README.md +++ b/discovery/token/README.md @@ -1,29 +1,7 @@ #discovery.hub.docker.com -Docker Swarm comes with a simple discovery service built into the [Docker Hub](http://hub.docker.com) - -#####Create a new cluster -`-> POST https://discovery.hub.docker.com/v1/clusters` - -`<- ` - -#####Add new nodes to a cluster -`-> POST https://discovery.hub.docker.com/v1/clusters/?ttl= Request body: ":"` - -`<- OK` - -`-> POST https://discovery.hub.docker.com/v1/clusters/?ttl= Request body: ":")` - -`<- OK` - - -#####List nodes in a cluster -`-> GET https://discovery.hub.docker.com/v1/clusters/` - -`<- [":", ":"]` - - -#####Delete a cluster (all the nodes in a cluster) -`-> DELETE https://discovery.hub.docker.com/v1/clusters/` - -`<- OK` +Docker Swarm comes with a simple discovery service built into the [Docker +Hub](http://hub.docker.com). To read the end-user API documentation, visit [the +Swarm discovery documentation on +docs.docker.com](https://docs.docker.com/swarm/discovery/). If you want to +modify the discovery documentation, start with [the `docs/discovery.md`] diff --git a/scheduler/filter/README.md b/scheduler/filter/README.md index 22a7cf6598..007d445f19 100644 --- a/scheduler/filter/README.md +++ b/scheduler/filter/README.md @@ -1,408 +1,8 @@ ---- -page_title: Docker Swarm filters -page_description: Swarm filters -page_keywords: docker, swarm, clustering, filters ---- - -# Filters - -The `Docker Swarm` scheduler comes with multiple filters. - -The following filters are currently used to schedule containers on a subset of nodes: - -* [Constraint](#constraint-filter) -* [Affinity](#affinity-filter) -* [Port](#port-filter) -* [Dependency](#dependency-filter) -* [Health](#health-filter) - -You can choose the filter(s) you want to use with the `--filter` flag of `swarm manage` - -## Constraint Filter - -Constraints are key/value pairs associated to particular nodes. You can see them -as *node tags*. - -When creating a container, the user can select a subset of nodes that should be -considered for scheduling by specifying one or more sets of matching key/value pairs. - -This approach has several practical use cases such as: -* Selecting specific host properties (such as `storage=ssd`, in order to schedule - containers on specific hardware). -* Tagging nodes based on their physical location (`region=us-east`, to force - containers to run on a given location). -* Logical cluster partitioning (`environment=production`, to split a cluster into - sub-clusters with different properties). - -To tag a node with a specific set of key/value pairs, one must pass a list of -`--label` options at docker startup time. - -For instance, let's start `node-1` with the `storage=ssd` label: - -```bash -$ docker -d --label storage=ssd -$ swarm join --advertise=192.168.0.42:2375 token://XXXXXXXXXXXXXXXXXX -``` - -Again, but this time `node-2` with `storage=disk`: - -```bash -$ docker -d --label storage=disk -$ swarm join --advertise=192.168.0.43:2375 token://XXXXXXXXXXXXXXXXXX -``` - -Once the nodes are registered with the cluster, the master pulls their respective -tags and will take them into account when scheduling new containers. - -Let's start a MySQL server and make sure it gets good I/O performance by selecting -nodes with flash drives: - -```bash -$ docker run -d -P -e constraint:storage==ssd --name db mysql -f8b693db9cd6 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -f8b693db9cd6 mysql:latest "mysqld" Less than a second ago running 192.168.0.42:49178->3306/tcp node-1 db -``` - -In this case, the master selected all nodes that met the `storage=ssd` constraint -and applied resource management on top of them, as discussed earlier. -`node-1` was selected in this example since it's the only host running flash. - -Now we want to run an Nginx frontend in our cluster. However, we don't want -*flash* drives since we'll mostly write logs to disk. - -```bash -$ docker run -d -P -e constraint:storage==disk --name frontend nginx -963841b138d8 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -963841b138d8 nginx:latest "nginx" Less than a second ago running 192.168.0.43:49177->80/tcp node-2 frontend -f8b693db9cd6 mysql:latest "mysqld" Up About a minute running 192.168.0.42:49178->3306/tcp node-1 db -``` - -The scheduler selected `node-2` since it was started with the `storage=disk` label. - -## Standard Constraints - -Additionally, a standard set of constraints can be used when scheduling containers -without specifying them when starting the node. Those tags are sourced from -`docker info` and currently include: - -* node ID or node Name (using key "node") -* storagedriver -* executiondriver -* kernelversion -* operatingsystem - -## Affinity filter - -You use an `--affinity:` to create "attractions" between containers. For -example, you can run a container and instruct it to locate and run next to -another container based on an identifier, an image, or a label. These -attractions ensure that containers run on the same network node — without -you having to know what each node is running. - -#### Container affinity - -You can schedule a new container to run next to another based on a container -name or ID. For example, you can start a container called `frontend` running -`nginx`: - -```bash -$ docker run -d -p 80:80 --name frontend nginx - 87c4376856a8 - - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -87c4376856a8 nginx:latest "nginx" Less than a second ago running 192.168.0.42:80->80/tcp node-1 frontend -``` - -Then, using `-e affinity:container==frontend` flag schedule a second container to -locate and run next to `frontend`. - -```bash -$ docker run -d --name logger -e affinity:container==frontend logger - 87c4376856a8 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -87c4376856a8 nginx:latest "nginx" Less than a second ago running 192.168.0.42:80->80/tcp node-1 frontend -963841b138d8 logger:latest "logger" Less than a second ago running node-1 logger -``` - -Because of name affinity, the `logger` container ends up on `node-1` along with -the `frontend` container. Instead of the `frontend` name you could have supplied its -ID as follows: - -```bash -docker run -d --name logger -e affinity:container==87c4376856a8` -``` - - -#### Image affinity - -You can schedule a container to run only on nodes where a specific image is already pulled. - -```bash -$ docker -H node-1:2375 pull redis -$ docker -H node-2:2375 pull mysql -$ docker -H node-3:2375 pull redis -``` - -Only `node-1` and `node-3` have the `redis` image. Specify a `-e -affinity:image==redis` filter to schedule several additional containers to run on -these nodes. - -```bash -$ docker run -d --name redis1 -e affinity:image==redis redis -$ docker run -d --name redis2 -e affinity:image==redis redis -$ docker run -d --name redis3 -e affinity:image==redis redis -$ docker run -d --name redis4 -e affinity:image==redis redis -$ docker run -d --name redis5 -e affinity:image==redis redis -$ docker run -d --name redis6 -e affinity:image==redis redis -$ docker run -d --name redis7 -e affinity:image==redis redis -$ docker run -d --name redis8 -e affinity:image==redis redis - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -87c4376856a8 redis:latest "redis" Less than a second ago running node-1 redis1 -1212386856a8 redis:latest "redis" Less than a second ago running node-1 redis2 -87c4376639a8 redis:latest "redis" Less than a second ago running node-3 redis3 -1234376856a8 redis:latest "redis" Less than a second ago running node-1 redis4 -86c2136253a8 redis:latest "redis" Less than a second ago running node-3 redis5 -87c3236856a8 redis:latest "redis" Less than a second ago running node-3 redis6 -87c4376856a8 redis:latest "redis" Less than a second ago running node-3 redis7 -963841b138d8 redis:latest "redis" Less than a second ago running node-1 redis8 -``` - -As you can see here, the containers were only scheduled on nodes that had the -`redis` image. Instead of the image name, you could have specified the image ID. - -```bash -$ docker images -REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE -redis latest 06a1f75304ba 2 days ago 111.1 MB - -$ docker run -d --name redis1 -e affinity:image==06a1f75304ba redis -``` - - -#### Label affinity - -Label affinity allows you to set up an attraction based on a container's label. -For example, you can run a `nginx` container with the `com.example.type=frontend` label. - -```bash -$ docker run -d -p 80:80 --label com.example.type=frontend nginx - 87c4376856a8 - -$ docker ps --filter "label=com.example.type=front" -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -87c4376856a8 nginx:latest "nginx" Less than a second ago running 192.168.0.42:80->80/tcp node-1 trusting_yonath -``` - -Then, use `-e affinity:com.example.type==frontend` to schedule a container next to -the container with the `com.example.type==frontend` label. - -```bash -$ docker run -d -e affinity:com.example.type==frontend logger - 87c4376856a8 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -87c4376856a8 nginx:latest "nginx" Less than a second ago running 192.168.0.42:80->80/tcp node-1 trusting_yonath -963841b138d8 logger:latest "logger" Less than a second ago running node-1 happy_hawking -``` - -The `logger` container ends up on `node-1` because its affinity with the `com.example.type==frontend` label. - -#### Expression Syntax - -An affinity or a constraint expression consists of a `key` and a `value`. -A `key` must conform the alpha-numeric pattern, with the leading alphabet or underscore. - -A `value` must be one of the following: -* An alpha-numeric string, dots, hyphens, and underscores. -* A globbing pattern, i.e., `abc*`. -* A regular expression in the form of `/regexp/`. We support the Go's regular expression syntax. - -Currently Swarm supports the following affinity/constraint operators: `==` and `!=`. - -For example, -* `constraint:node==node1` will match node `node1`. -* `constraint:node!=node1` will match all nodes, except `node1`. -* `constraint:region!=us*` will match all nodes outside the regions prefixed with `us`. -* `constraint:node==/node[12]/` will match nodes `node1` and `node2`. -* `constraint:node==/node\d/` will match all nodes with `node` + 1 digit. -* `constraint:node!=/node-[01]/` will match all nodes, except `node-0` and `node-1`. -* `constraint:node!=/foo\[bar\]/` will match all nodes, except `foo[bar]`. You can see the use of escape characters here. -* `constraint:node==/(?i)node1/` will match node `node1` case-insensitive. So `NoDe1` or `NODE1` will also match. - -#### Soft Affinities/Constraints - -By default, affinities and constraints are hard enforced. If an affinity or -constraint is not met, the container won't be scheduled. With soft -affinities/constraints the scheduler will try to meet the rule. If it is not -met, the scheduler will discard the filter and schedule the container according -to the scheduler's strategy. - -Soft affinities/constraints are expressed with a **~** in the -expression, for example: - -```bash -$ docker run -d --name redis1 -e affinity:image==~redis redis -``` - -If none of the nodes in the cluster has the image `redis`, the scheduler will -discard the affinity and schedule according to the strategy. - -```bash -$ docker run -d --name redis2 -e constraint:region==~us* redis -``` - -If none of the nodes in the cluster belongs to the `us` region, the scheduler will -discard the constraint and schedule according to the strategy. - -```bash -$ docker run -d --name redis5 -e affinity:container!=~redis* redis -``` - -The affinity filter will be used to schedule a new `redis5` container to a -different node that doesn't have a container with the name that satisfies -`redis*`. If each node in the cluster has a `redis*` container, the scheduler -will discard the affinity rule and schedule according to the strategy. - -## Port Filter - -With this filter, `ports` are considered unique resources. - -```bash -$ docker run -d -p 80:80 nginx -87c4376856a8 - -$ docker ps -CONTAINER ID IMAGE COMMAND PORTS NODE NAMES -87c4376856a8 nginx:latest "nginx" 192.168.0.42:80->80/tcp node-1 prickly_engelbart -``` - -Docker cluster selects a node where the public `80` port is available and schedules -a container on it, in this case `node-1`. - -Attempting to run another container with the public `80` port will result in -the cluster selecting a different node, since that port is already occupied on `node-1`: - -```bash -$ docker run -d -p 80:80 nginx -963841b138d8 - -$ docker ps -CONTAINER ID IMAGE COMMAND PORTS NODE NAMES -963841b138d8 nginx:latest "nginx" 192.168.0.43:80->80/tcp node-2 dreamy_turing -87c4376856a8 nginx:latest "nginx" 192.168.0.42:80->80/tcp node-1 prickly_engelbart -``` - -Again, repeating the same command will result in the selection of `node-3`, since -port `80` is neither available on `node-1` nor `node-2`: - -```bash -$ docker run -d -p 80:80 nginx -963841b138d8 - -$ docker ps -CONTAINER ID IMAGE COMMAND PORTS NODE NAMES -f8b693db9cd6 nginx:latest "nginx" 192.168.0.44:80->80/tcp node-3 stoic_albattani -963841b138d8 nginx:latest "nginx" 192.168.0.43:80->80/tcp node-2 dreamy_turing -87c4376856a8 nginx:latest "nginx" 192.168.0.42:80->80/tcp node-1 prickly_engelbart -``` - -Finally, Docker Swarm will refuse to run another container that requires port -`80` since not a single node in the cluster has it available: - -```bash -$ docker run -d -p 80:80 nginx -2014/10/29 00:33:20 Error response from daemon: no resources available to schedule container -``` - -### Port filter in Host Mode - -Docker in the host mode, running with `--net=host`, differs from the -default `bridge` mode as the `host` mode does not perform any port -binding. So, it require that you explicitly expose one or more port numbers -(using `EXPOSE` in the `Dockerfile` or `--expose` on the command line). -Swarm makes use of this information in conjunction with the `host` -mode to choose an available node for a new container. - -For example, the following commands start `nginx` on 3-node cluster. - -```bash -$ docker run -d --expose=80 --net=host nginx -640297cb29a7 -$ docker run -d --expose=80 --net=host nginx -7ecf562b1b3f -$ docker run -d --expose=80 --net=host nginx -09a92f582bc2 -``` - -Port binding information will not be available through the `docker ps` command because all the nodes are started in the `host` mode. - -```bash -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -640297cb29a7 nginx:1 "nginx -g 'daemon of Less than a second ago Up 30 seconds box3/furious_heisenberg -7ecf562b1b3f nginx:1 "nginx -g 'daemon of Less than a second ago Up 28 seconds box2/ecstatic_meitner -09a92f582bc2 nginx:1 "nginx -g 'daemon of 46 seconds ago Up 27 seconds box1/mad_goldstine -``` - -The swarm will refuse the operation when trying to instantiate the 4th container. - -```bash -$ docker run -d --expose=80 --net=host nginx -FATA[0000] Error response from daemon: unable to find a node with port 80/tcp available in the Host mode -``` - -However port binding to the different value, e.g. `81`, is still allowed. - -```bash -$ docker run -d -p 81:80 nginx:latest -832f42819adc -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -832f42819adc nginx:1 "nginx -g 'daemon of Less than a second ago Up Less than a second 443/tcp, 192.168.136.136:81->80/tcp box3/thirsty_hawking -640297cb29a7 nginx:1 "nginx -g 'daemon of 8 seconds ago Up About a minute box3/furious_heisenberg -7ecf562b1b3f nginx:1 "nginx -g 'daemon of 13 seconds ago Up About a minute box2/ecstatic_meitner -09a92f582bc2 nginx:1 "nginx -g 'daemon of About a minute ago Up About a minute box1/mad_goldstine -``` - -## Dependency Filter - -This filter co-schedules dependent containers on the same node. - -Currently, dependencies are declared as follows: - -- Shared volumes: `--volumes-from=dependency` -- Links: `--link=dependency:alias` -- Shared network stack: `--net=container:dependency` - -Swarm will attempt to co-locate the dependent container on the same node. If it -cannot be done (because the dependent container doesn't exist, or because the -node doesn't have enough resources), it will prevent the container creation. - -The combination of multiple dependencies will be honored if possible. For -instance, `--volumes-from=A --net=container:B` will attempt to co-locate the -container on the same node as `A` and `B`. If those containers are running on -different nodes, Swarm will prevent you from scheduling the container. - -## Health Filter - -This filter will prevent scheduling containers on unhealthy nodes. - -## Docker Swarm documentation index - -- [User guide](https://docs.docker.com/swarm/) -- [Discovery options](https://docs.docker.com/swarm/discovery/) -- [Scheduler strategies](https://docs.docker.com/swarm/scheduler/strategy/) -- [Swarm API](https://docs.docker.com/swarm/API/) +# Filters README + +The `Docker Swarm` scheduler comes with multiple filters. To read the end-user +filters documentation, visit [the Swarm API documentation on +docs.docker.com](https://docs.docker.com/swarm/scheduler/filter/). If you want +to modify the filter documentation, start with [the `docs/scheduler` +directory](https://github.com/docker/swarm/blob/master/docs/scheduler/index.md) +in this project. diff --git a/scheduler/strategy/README.md b/scheduler/strategy/README.md index 6c59e93408..e08ef750b2 100644 --- a/scheduler/strategy/README.md +++ b/scheduler/strategy/README.md @@ -1,118 +1,8 @@ ---- -page_title: Docker Swarm strategies -page_description: Swarm strategies -page_keywords: docker, swarm, clustering, strategies ---- +# Strategies README -# Strategies - -The Docker Swarm scheduler features multiple strategies for ranking nodes. The -strategy you choose determines how Swarm computes ranking. When you run a new -container, Swarm chooses to place it on the node with the highest computed ranking -for your chosen strategy. - -To choose a ranking strategy, pass the `--strategy` flag and a strategy value to -the `swarm manage` command. Swarm currently supports these values: - -* `spread` -* `binpack` -* `random` - -The `spread` and `binpack` strategies compute rank according to a node's -available CPU, its RAM, and the number of containers it is running. The `random` -strategy uses no computation. It selects a node at random and is primarily -intended for debugging. - -Your goal in choosing a strategy is to best optimize your swarm according to -your company's needs. - -Under the `spread` strategy, Swarm optimizes for the node with the fewest running containers. -The `binpack` strategy causes Swarm to optimize for the node which is most packed. -The `random` strategy, like it sounds, chooses nodes at random regardless of their available CPU or RAM. - -Using the `spread` strategy results in containers spread thinly over many -machines. The advantage of this strategy is that if a node goes down you only -lose a few containers. - -The `binpack` strategy avoids fragmentation because it leaves room for bigger -containers on unused machines. The strategic advantage of `binpack` is that you -use fewer machines as Swarm tries to pack as many containers as it can on a -node. - -If you do not specify a `--strategy` Swarm uses `spread` by default. - -## Spread strategy example - -In this example, your swarm is using the `spread` strategy which optimizes for -nodes that have the fewest containers. In this swarm, both `node-1` and `node-2` -have 2G of RAM, 2 CPUs, and neither node is running a container. Under this strategy -`node-1` and `node-2` have the same ranking. - -When you run a new container, the system chooses `node-1` at random from the swarm -of two equally ranked nodes: - -```bash -$ docker run -d -P -m 1G --name db mysql -f8b693db9cd6 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -f8b693db9cd6 mysql:latest "mysqld" Less than a second ago running 192.168.0.42:49178->3306/tcp node-1 db -``` - -Now, we start another container and ask for 1G of RAM again. - -```bash -$ docker run -d -P -m 1G --name frontend nginx -963841b138d8 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -963841b138d8 nginx:latest "nginx" Less than a second ago running 192.168.0.42:49177->80/tcp node-2 frontend -f8b693db9cd6 mysql:latest "mysqld" Up About a minute running 192.168.0.42:49178->3306/tcp node-1 db -``` - -The container `frontend` was started on `node-2` because it was the node with the -fewest running containers. If two nodes have the same amount of available RAM and -CPUs, the `spread` strategy prefers the node with the fewest containers running. - -## BinPack strategy example - -In this example, let's says that both `node-1` and `node-2` have 2G of RAM and -neither is running a container. Again, the nodes are equal. When you run a new -container, the system chooses `node-1` at random from the swarm: - -```bash -$ docker run -d -P -m 1G --name db mysql -f8b693db9cd6 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -f8b693db9cd6 mysql:latest "mysqld" Less than a second ago running 192.168.0.42:49178->3306/tcp node-1 db -``` - -Now, you start another container, asking for 1G of RAM again. - -```bash -$ docker run -d -P -m 1G --name frontend nginx -963841b138d8 - -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NODE NAMES -963841b138d8 nginx:latest "nginx" Less than a second ago running 192.168.0.42:49177->80/tcp node-1 frontend -f8b693db9cd6 mysql:latest "mysqld" Up About a minute running 192.168.0.42:49178->3306/tcp node-1 db -``` - -The system starts the new `frontend` container on `node-1` because it was the -node with the most running containers. This allows us to start a container requiring 2G -of RAM on `node-2`. - -If two nodes have the same amount of available RAM and CPUs, the `binpack` -strategy prefers the node with the most running containers. - -## Docker Swarm documentation index - -- [User guide](https://docs.docker.com/swarm/) -- [Discovery options](https://docs.docker.com/swarm/discovery/) -- [Scheduler filters](https://docs.docker.com/swarm/scheduler/filter/) -- [Swarm API](https://docs.docker.com/swarm/api/swarm-api/) +The `Docker Swarm` scheduler comes with multiple strategies. To read the +end-user strategy documentation, visit [the Swarm strategy documentation on +docs.docker.com](https://docs.docker.com/swarm/scheduler/strategy/). If you want +to modify the filter documentation, start with [the `docs/scheduler` +directory](https://github.com/docker/swarm/blob/master/docs/scheduler/index.md) +in this project. From fc1e7bbca25a33a2cd1d416b13a2c8230edff868 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 11 Jan 2016 12:05:16 -0800 Subject: [PATCH 2/4] use docker/docker/pkg/discovery Signed-off-by: Victor Vieux --- cli/join.go | 2 +- cli/list.go | 2 +- cli/manage.go | 8 +- cluster/swarm/cluster.go | 6 +- discovery/discovery.go | 166 ---------------------------- discovery/discovery_test.go | 120 -------------------- discovery/file/file.go | 109 ------------------- discovery/file/file_test.go | 106 ------------------ discovery/generator.go | 35 ------ discovery/generator_test.go | 55 ---------- discovery/kv/kv.go | 193 --------------------------------- discovery/kv/kv_test.go | 199 ---------------------------------- discovery/nodes/nodes.go | 54 --------- discovery/nodes/nodes_test.go | 43 -------- discovery/token/token.go | 2 +- discovery/token/token_test.go | 2 +- docs/discovery.md | 5 +- main.go | 6 +- 18 files changed, 17 insertions(+), 1096 deletions(-) delete mode 100644 discovery/discovery.go delete mode 100644 discovery/discovery_test.go delete mode 100644 discovery/file/file.go delete mode 100644 discovery/file/file_test.go delete mode 100644 discovery/generator.go delete mode 100644 discovery/generator_test.go delete mode 100644 discovery/kv/kv.go delete mode 100644 discovery/kv/kv_test.go delete mode 100644 discovery/nodes/nodes.go delete mode 100644 discovery/nodes/nodes_test.go diff --git a/cli/join.go b/cli/join.go index e302e15f73..df8c64d245 100644 --- a/cli/join.go +++ b/cli/join.go @@ -7,7 +7,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" - "github.com/docker/swarm/discovery" + "github.com/docker/docker/pkg/discovery" ) func checkAddrFormat(addr string) bool { diff --git a/cli/list.go b/cli/list.go index 24e9db5467..8e9bc23a12 100644 --- a/cli/list.go +++ b/cli/list.go @@ -6,7 +6,7 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/docker/swarm/discovery" + "github.com/docker/docker/pkg/discovery" ) func list(c *cli.Context) { diff --git a/cli/manage.go b/cli/manage.go index 550baa07db..37a2566f56 100644 --- a/cli/manage.go +++ b/cli/manage.go @@ -11,12 +11,12 @@ import ( log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" + "github.com/docker/docker/pkg/discovery" + kvdiscovery "github.com/docker/docker/pkg/discovery/kv" "github.com/docker/swarm/api" "github.com/docker/swarm/cluster" "github.com/docker/swarm/cluster/mesos" "github.com/docker/swarm/cluster/swarm" - "github.com/docker/swarm/discovery" - kvdiscovery "github.com/docker/swarm/discovery/kv" "github.com/docker/swarm/leadership" "github.com/docker/swarm/scheduler" "github.com/docker/swarm/scheduler/filter" @@ -98,7 +98,7 @@ func loadTLSConfig(ca, cert, key string, verify bool) (*tls.Config, error) { } // Initialize the discovery service. -func createDiscovery(uri string, c *cli.Context, discoveryOpt []string) discovery.Discovery { +func createDiscovery(uri string, c *cli.Context, discoveryOpt []string) discovery.Backend { hb, err := time.ParseDuration(c.String("heartbeat")) if err != nil { log.Fatalf("invalid --heartbeat: %v", err) @@ -129,7 +129,7 @@ func getDiscoveryOpt(c *cli.Context) map[string]string { return options } -func setupReplication(c *cli.Context, cluster cluster.Cluster, server *api.Server, discovery discovery.Discovery, addr string, leaderTTL time.Duration, tlsConfig *tls.Config) { +func setupReplication(c *cli.Context, cluster cluster.Cluster, server *api.Server, discovery discovery.Backend, addr string, leaderTTL time.Duration, tlsConfig *tls.Config) { kvDiscovery, ok := discovery.(*kvdiscovery.Discovery) if !ok { log.Fatal("Leader election is only supported with consul, etcd and zookeeper discovery.") diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 969d28f497..96985b7807 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -12,10 +12,10 @@ import ( "time" log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/discovery" "github.com/docker/docker/pkg/stringid" "github.com/docker/go-units" "github.com/docker/swarm/cluster" - "github.com/docker/swarm/discovery" "github.com/docker/swarm/scheduler" "github.com/docker/swarm/scheduler/node" "github.com/samalba/dockerclient" @@ -54,7 +54,7 @@ type Cluster struct { engines map[string]*cluster.Engine pendingEngines map[string]*cluster.Engine scheduler *scheduler.Scheduler - discovery discovery.Discovery + discovery discovery.Backend pendingContainers map[string]*pendingContainer overcommitRatio float64 @@ -63,7 +63,7 @@ type Cluster struct { } // NewCluster is exported -func NewCluster(scheduler *scheduler.Scheduler, TLSConfig *tls.Config, discovery discovery.Discovery, options cluster.DriverOpts, engineOptions *cluster.EngineOpts) (cluster.Cluster, error) { +func NewCluster(scheduler *scheduler.Scheduler, TLSConfig *tls.Config, discovery discovery.Backend, options cluster.DriverOpts, engineOptions *cluster.EngineOpts) (cluster.Cluster, error) { log.WithFields(log.Fields{"name": "swarm"}).Debug("Initializing cluster") cluster := &Cluster{ diff --git a/discovery/discovery.go b/discovery/discovery.go deleted file mode 100644 index 1888d1a0f4..0000000000 --- a/discovery/discovery.go +++ /dev/null @@ -1,166 +0,0 @@ -package discovery - -import ( - "errors" - "fmt" - "net" - "strings" - "time" - - log "github.com/Sirupsen/logrus" -) - -// An Entry represents a swarm host. -type Entry struct { - Host string - Port string -} - -// NewEntry creates a new entry. -func NewEntry(url string) (*Entry, error) { - host, port, err := net.SplitHostPort(url) - if err != nil { - return nil, err - } - return &Entry{host, port}, nil -} - -// String returns the string form of an entry. -func (e *Entry) String() string { - return fmt.Sprintf("%s:%s", e.Host, e.Port) -} - -// Equals returns true if cmp contains the same data. -func (e *Entry) Equals(cmp *Entry) bool { - return e.Host == cmp.Host && e.Port == cmp.Port -} - -// Entries is a list of *Entry with some helpers. -type Entries []*Entry - -// Equals returns true if cmp contains the same data. -func (e Entries) Equals(cmp Entries) bool { - // Check if the file has really changed. - if len(e) != len(cmp) { - return false - } - for i := range e { - if !e[i].Equals(cmp[i]) { - return false - } - } - return true -} - -// Contains returns true if the Entries contain a given Entry. -func (e Entries) Contains(entry *Entry) bool { - for _, curr := range e { - if curr.Equals(entry) { - return true - } - } - return false -} - -// Diff compares two entries and returns the added and removed entries. -func (e Entries) Diff(cmp Entries) (Entries, Entries) { - added := Entries{} - for _, entry := range cmp { - if !e.Contains(entry) { - added = append(added, entry) - } - } - - removed := Entries{} - for _, entry := range e { - if !cmp.Contains(entry) { - removed = append(removed, entry) - } - } - - return added, removed -} - -// The Discovery interface is implemented by Discovery backends which -// manage swarm host entries. -type Discovery interface { - // Initialize the discovery with URIs, a heartbeat, a ttl and optional settings. - Initialize(string, time.Duration, time.Duration, map[string]string) error - - // Watch the discovery for entry changes. - // Returns a channel that will receive changes or an error. - // Providing a non-nil stopCh can be used to stop watching. - Watch(stopCh <-chan struct{}) (<-chan Entries, <-chan error) - - // Register to the discovery - Register(string) error -} - -var ( - discoveries map[string]Discovery - // ErrNotSupported is returned when a discovery service is not supported. - ErrNotSupported = errors.New("discovery service not supported") - // ErrNotImplemented is returned when discovery feature is not implemented - // by discovery backend. - ErrNotImplemented = errors.New("not implemented in this discovery service") -) - -func init() { - discoveries = make(map[string]Discovery) -} - -// Register makes a discovery backend available by the provided scheme. -// If Register is called twice with the same scheme an error is returned. -func Register(scheme string, d Discovery) error { - if _, exists := discoveries[scheme]; exists { - return fmt.Errorf("scheme already registered %s", scheme) - } - log.WithField("name", scheme).Debug("Registering discovery service") - discoveries[scheme] = d - - return nil -} - -func parse(rawurl string) (string, string) { - parts := strings.SplitN(rawurl, "://", 2) - - // nodes:port,node2:port => nodes://node1:port,node2:port - if len(parts) == 1 { - return "nodes", parts[0] - } - return parts[0], parts[1] -} - -// New returns a new Discovery given a URL, heartbeat and ttl settings. -// Returns an error if the URL scheme is not supported. -func New(rawurl string, heartbeat time.Duration, ttl time.Duration, discoveryOpt map[string]string) (Discovery, error) { - scheme, uri := parse(rawurl) - - if discovery, exists := discoveries[scheme]; exists { - log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service") - err := discovery.Initialize(uri, heartbeat, ttl, discoveryOpt) - return discovery, err - } - - return nil, ErrNotSupported -} - -// CreateEntries returns an array of entries based on the given addresses. -func CreateEntries(addrs []string) (Entries, error) { - entries := Entries{} - if addrs == nil { - return entries, nil - } - - for _, addr := range addrs { - if len(addr) == 0 { - continue - } - entry, err := NewEntry(addr) - if err != nil { - return nil, err - } - entries = append(entries, entry) - } - return entries, nil -} diff --git a/discovery/discovery_test.go b/discovery/discovery_test.go deleted file mode 100644 index b7128ff258..0000000000 --- a/discovery/discovery_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package discovery - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewEntry(t *testing.T) { - entry, err := NewEntry("127.0.0.1:2375") - assert.NoError(t, err) - assert.True(t, entry.Equals(&Entry{Host: "127.0.0.1", Port: "2375"})) - assert.Equal(t, entry.String(), "127.0.0.1:2375") - - _, err = NewEntry("127.0.0.1") - assert.Error(t, err) -} - -func TestParse(t *testing.T) { - scheme, uri := parse("127.0.0.1:2375") - assert.Equal(t, scheme, "nodes") - assert.Equal(t, uri, "127.0.0.1:2375") - - scheme, uri = parse("localhost:2375") - assert.Equal(t, scheme, "nodes") - assert.Equal(t, uri, "localhost:2375") - - scheme, uri = parse("scheme://127.0.0.1:2375") - assert.Equal(t, scheme, "scheme") - assert.Equal(t, uri, "127.0.0.1:2375") - - scheme, uri = parse("scheme://localhost:2375") - assert.Equal(t, scheme, "scheme") - assert.Equal(t, uri, "localhost:2375") - - scheme, uri = parse("") - assert.Equal(t, scheme, "nodes") - assert.Equal(t, uri, "") -} - -func TestCreateEntries(t *testing.T) { - entries, err := CreateEntries(nil) - assert.Equal(t, entries, Entries{}) - assert.NoError(t, err) - - entries, err = CreateEntries([]string{"127.0.0.1:2375", "127.0.0.2:2375", ""}) - assert.NoError(t, err) - expected := Entries{ - &Entry{Host: "127.0.0.1", Port: "2375"}, - &Entry{Host: "127.0.0.2", Port: "2375"}, - } - assert.True(t, entries.Equals(expected)) - - _, err = CreateEntries([]string{"127.0.0.1", "127.0.0.2"}) - assert.Error(t, err) -} - -func TestContainsEntry(t *testing.T) { - entries, err := CreateEntries([]string{"127.0.0.1:2375", "127.0.0.2:2375", ""}) - assert.NoError(t, err) - assert.True(t, entries.Contains(&Entry{Host: "127.0.0.1", Port: "2375"})) - assert.False(t, entries.Contains(&Entry{Host: "127.0.0.3", Port: "2375"})) -} - -func TestEntriesEquality(t *testing.T) { - entries := Entries{ - &Entry{Host: "127.0.0.1", Port: "2375"}, - &Entry{Host: "127.0.0.2", Port: "2375"}, - } - - // Same - assert.True(t, entries.Equals(Entries{ - &Entry{Host: "127.0.0.1", Port: "2375"}, - &Entry{Host: "127.0.0.2", Port: "2375"}, - })) - - // Different size - assert.False(t, entries.Equals(Entries{ - &Entry{Host: "127.0.0.1", Port: "2375"}, - &Entry{Host: "127.0.0.2", Port: "2375"}, - &Entry{Host: "127.0.0.3", Port: "2375"}, - })) - - // Different content - assert.False(t, entries.Equals(Entries{ - &Entry{Host: "127.0.0.1", Port: "2375"}, - &Entry{Host: "127.0.0.42", Port: "2375"}, - })) -} - -func TestEntriesDiff(t *testing.T) { - entry1 := &Entry{Host: "1.1.1.1", Port: "1111"} - entry2 := &Entry{Host: "2.2.2.2", Port: "2222"} - entry3 := &Entry{Host: "3.3.3.3", Port: "3333"} - entries := Entries{entry1, entry2} - - // No diff - added, removed := entries.Diff(Entries{entry2, entry1}) - assert.Empty(t, added) - assert.Empty(t, removed) - - // Add - added, removed = entries.Diff(Entries{entry2, entry3, entry1}) - assert.Len(t, added, 1) - assert.True(t, added.Contains(entry3)) - assert.Empty(t, removed) - - // Remove - added, removed = entries.Diff(Entries{entry2}) - assert.Empty(t, added) - assert.Len(t, removed, 1) - assert.True(t, removed.Contains(entry1)) - - // Add and remove - added, removed = entries.Diff(Entries{entry1, entry3}) - assert.Len(t, added, 1) - assert.True(t, added.Contains(entry3)) - assert.Len(t, removed, 1) - assert.True(t, removed.Contains(entry2)) -} diff --git a/discovery/file/file.go b/discovery/file/file.go deleted file mode 100644 index 1ec72ad398..0000000000 --- a/discovery/file/file.go +++ /dev/null @@ -1,109 +0,0 @@ -package file - -import ( - "fmt" - "io/ioutil" - "strings" - "time" - - "github.com/docker/swarm/discovery" -) - -// Discovery is exported -type Discovery struct { - heartbeat time.Duration - path string -} - -func init() { - Init() -} - -// Init is exported -func Init() { - discovery.Register("file", &Discovery{}) -} - -// Initialize is exported -func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration, _ map[string]string) error { - s.path = path - s.heartbeat = heartbeat - return nil -} - -func parseFileContent(content []byte) []string { - var result []string - for _, line := range strings.Split(strings.TrimSpace(string(content)), "\n") { - line = strings.TrimSpace(line) - // Ignoring line starts with # - if strings.HasPrefix(line, "#") { - continue - } - // Inlined # comment also ignored. - if strings.Contains(line, "#") { - line = line[0:strings.Index(line, "#")] - // Trim additional spaces caused by above stripping. - line = strings.TrimSpace(line) - } - for _, ip := range discovery.Generate(line) { - result = append(result, ip) - } - } - return result -} - -func (s *Discovery) fetch() (discovery.Entries, error) { - fileContent, err := ioutil.ReadFile(s.path) - if err != nil { - return nil, fmt.Errorf("failed to read '%s': %v", s.path, err) - } - return discovery.CreateEntries(parseFileContent(fileContent)) -} - -// Watch is exported -func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { - ch := make(chan discovery.Entries) - errCh := make(chan error) - ticker := time.NewTicker(s.heartbeat) - - go func() { - defer close(errCh) - defer close(ch) - - // Send the initial entries if available. - currentEntries, err := s.fetch() - if err != nil { - errCh <- err - } else { - ch <- currentEntries - } - - // Periodically send updates. - for { - select { - case <-ticker.C: - newEntries, err := s.fetch() - if err != nil { - errCh <- err - continue - } - - // Check if the file has really changed. - if !newEntries.Equals(currentEntries) { - ch <- newEntries - } - currentEntries = newEntries - case <-stopCh: - ticker.Stop() - return - } - } - }() - - return ch, errCh -} - -// Register is exported -func (s *Discovery) Register(addr string) error { - return discovery.ErrNotImplemented -} diff --git a/discovery/file/file_test.go b/discovery/file/file_test.go deleted file mode 100644 index 05f248fb87..0000000000 --- a/discovery/file/file_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package file - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/docker/swarm/discovery" - "github.com/stretchr/testify/assert" -) - -func TestInitialize(t *testing.T) { - d := &Discovery{} - d.Initialize("/path/to/file", 1000, 0, nil) - assert.Equal(t, d.path, "/path/to/file") -} - -func TestNew(t *testing.T) { - d, err := discovery.New("file:///path/to/file", 0, 0, nil) - assert.NoError(t, err) - assert.Equal(t, d.(*Discovery).path, "/path/to/file") -} - -func TestContent(t *testing.T) { - data := ` -1.1.1.[1:2]:1111 -2.2.2.[2:4]:2222 -` - ips := parseFileContent([]byte(data)) - assert.Len(t, ips, 5) - assert.Equal(t, ips[0], "1.1.1.1:1111") - assert.Equal(t, ips[1], "1.1.1.2:1111") - assert.Equal(t, ips[2], "2.2.2.2:2222") - assert.Equal(t, ips[3], "2.2.2.3:2222") - assert.Equal(t, ips[4], "2.2.2.4:2222") -} - -func TestRegister(t *testing.T) { - discovery := &Discovery{path: "/path/to/file"} - assert.Error(t, discovery.Register("0.0.0.0")) -} - -func TestParsingContentsWithComments(t *testing.T) { - data := ` -### test ### -1.1.1.1:1111 # inline comment -# 2.2.2.2:2222 - ### empty line with comment - 3.3.3.3:3333 -### test ### -` - ips := parseFileContent([]byte(data)) - assert.Len(t, ips, 2) - assert.Equal(t, "1.1.1.1:1111", ips[0]) - assert.Equal(t, "3.3.3.3:3333", ips[1]) -} - -func TestWatch(t *testing.T) { - data := ` -1.1.1.1:1111 -2.2.2.2:2222 -` - expected := discovery.Entries{ - &discovery.Entry{Host: "1.1.1.1", Port: "1111"}, - &discovery.Entry{Host: "2.2.2.2", Port: "2222"}, - } - - // Create a temporary file and remove it. - tmp, err := ioutil.TempFile(os.TempDir(), "discovery-file-test") - assert.NoError(t, err) - assert.NoError(t, tmp.Close()) - assert.NoError(t, os.Remove(tmp.Name())) - - // Set up file discovery. - d := &Discovery{} - d.Initialize(tmp.Name(), 1000, 0, nil) - stopCh := make(chan struct{}) - ch, errCh := d.Watch(stopCh) - - // Make sure it fires errors since the file doesn't exist. - assert.Error(t, <-errCh) - // We have to drain the error channel otherwise Watch will get stuck. - go func() { - for range errCh { - } - }() - - // Write the file and make sure we get the expected value back. - assert.NoError(t, ioutil.WriteFile(tmp.Name(), []byte(data), 0600)) - assert.Equal(t, expected, <-ch) - - // Add a new entry and look it up. - expected = append(expected, &discovery.Entry{Host: "3.3.3.3", Port: "3333"}) - f, err := os.OpenFile(tmp.Name(), os.O_APPEND|os.O_WRONLY, 0600) - assert.NoError(t, err) - assert.NotNil(t, f) - _, err = f.WriteString("\n3.3.3.3:3333\n") - assert.NoError(t, err) - f.Close() - assert.Equal(t, expected, <-ch) - - // Stop and make sure it closes all channels. - close(stopCh) - assert.Nil(t, <-ch) - assert.Nil(t, <-errCh) -} diff --git a/discovery/generator.go b/discovery/generator.go deleted file mode 100644 index d22298298f..0000000000 --- a/discovery/generator.go +++ /dev/null @@ -1,35 +0,0 @@ -package discovery - -import ( - "fmt" - "regexp" - "strconv" -) - -// Generate takes care of IP generation -func Generate(pattern string) []string { - re, _ := regexp.Compile(`\[(.+):(.+)\]`) - submatch := re.FindStringSubmatch(pattern) - if submatch == nil { - return []string{pattern} - } - - from, err := strconv.Atoi(submatch[1]) - if err != nil { - return []string{pattern} - } - to, err := strconv.Atoi(submatch[2]) - if err != nil { - return []string{pattern} - } - - template := re.ReplaceAllString(pattern, "%d") - - var result []string - for val := from; val <= to; val++ { - entry := fmt.Sprintf(template, val) - result = append(result, entry) - } - - return result -} diff --git a/discovery/generator_test.go b/discovery/generator_test.go deleted file mode 100644 index 747334452f..0000000000 --- a/discovery/generator_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package discovery - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGeneratorNotGenerate(t *testing.T) { - ips := Generate("127.0.0.1") - assert.Equal(t, len(ips), 1) - assert.Equal(t, ips[0], "127.0.0.1") -} - -func TestGeneratorWithPortNotGenerate(t *testing.T) { - ips := Generate("127.0.0.1:8080") - assert.Equal(t, len(ips), 1) - assert.Equal(t, ips[0], "127.0.0.1:8080") -} - -func TestGeneratorMatchFailedNotGenerate(t *testing.T) { - ips := Generate("127.0.0.[1]") - assert.Equal(t, len(ips), 1) - assert.Equal(t, ips[0], "127.0.0.[1]") -} - -func TestGeneratorWithPort(t *testing.T) { - ips := Generate("127.0.0.[1:11]:2375") - assert.Equal(t, len(ips), 11) - assert.Equal(t, ips[0], "127.0.0.1:2375") - assert.Equal(t, ips[1], "127.0.0.2:2375") - assert.Equal(t, ips[2], "127.0.0.3:2375") - assert.Equal(t, ips[3], "127.0.0.4:2375") - assert.Equal(t, ips[4], "127.0.0.5:2375") - assert.Equal(t, ips[5], "127.0.0.6:2375") - assert.Equal(t, ips[6], "127.0.0.7:2375") - assert.Equal(t, ips[7], "127.0.0.8:2375") - assert.Equal(t, ips[8], "127.0.0.9:2375") - assert.Equal(t, ips[9], "127.0.0.10:2375") - assert.Equal(t, ips[10], "127.0.0.11:2375") -} - -func TestGenerateWithMalformedInputAtRangeStart(t *testing.T) { - malformedInput := "127.0.0.[x:11]:2375" - ips := Generate(malformedInput) - assert.Equal(t, len(ips), 1) - assert.Equal(t, ips[0], malformedInput) -} - -func TestGenerateWithMalformedInputAtRangeEnd(t *testing.T) { - malformedInput := "127.0.0.[1:x]:2375" - ips := Generate(malformedInput) - assert.Equal(t, len(ips), 1) - assert.Equal(t, ips[0], malformedInput) -} diff --git a/discovery/kv/kv.go b/discovery/kv/kv.go deleted file mode 100644 index fb578e9957..0000000000 --- a/discovery/kv/kv.go +++ /dev/null @@ -1,193 +0,0 @@ -package kv - -import ( - "fmt" - "path" - "strings" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/tlsconfig" - "github.com/docker/libkv" - "github.com/docker/libkv/store" - "github.com/docker/libkv/store/consul" - "github.com/docker/libkv/store/etcd" - "github.com/docker/libkv/store/zookeeper" - "github.com/docker/swarm/discovery" -) - -const ( - defaultDiscoveryPath = "docker/swarm/nodes" -) - -// Discovery is exported -type Discovery struct { - backend store.Backend - store store.Store - heartbeat time.Duration - ttl time.Duration - prefix string - path string -} - -func init() { - Init() -} - -// Init is exported -func Init() { - // Register to libkv - zookeeper.Register() - consul.Register() - etcd.Register() - - // Register to internal Swarm discovery service - discovery.Register("zk", &Discovery{backend: store.ZK}) - discovery.Register("consul", &Discovery{backend: store.CONSUL}) - discovery.Register("etcd", &Discovery{backend: store.ETCD}) -} - -// Initialize is exported -func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration, discoveryOpt map[string]string) error { - var ( - parts = strings.SplitN(uris, "/", 2) - addrs = strings.Split(parts[0], ",") - err error - ) - - // A custom prefix to the path can be optionally used. - if len(parts) == 2 { - s.prefix = parts[1] - } - - s.heartbeat = heartbeat - s.ttl = ttl - - // Use a custom path if specified in discovery options - dpath := defaultDiscoveryPath - if discoveryOpt["kv.path"] != "" { - dpath = discoveryOpt["kv.path"] - } - - s.path = path.Join(s.prefix, dpath) - - var config *store.Config - if discoveryOpt["kv.cacertfile"] != "" && discoveryOpt["kv.certfile"] != "" && discoveryOpt["kv.keyfile"] != "" { - log.Debug("Initializing discovery with TLS") - tlsConfig, err := tlsconfig.Client(tlsconfig.Options{ - CAFile: discoveryOpt["kv.cacertfile"], - CertFile: discoveryOpt["kv.certfile"], - KeyFile: discoveryOpt["kv.keyfile"], - }) - if err != nil { - return err - } - config = &store.Config{ - // Set ClientTLS to trigger https (bug in libkv/etcd) - ClientTLS: &store.ClientTLSConfig{ - CACertFile: discoveryOpt["kv.cacertfile"], - CertFile: discoveryOpt["kv.certfile"], - KeyFile: discoveryOpt["kv.keyfile"], - }, - // The actual TLS config that will be used - TLS: tlsConfig, - } - } else { - log.Debug("Initializing discovery without TLS") - } - - // Creates a new store, will ignore options given - // if not supported by the chosen store - s.store, err = libkv.NewStore(s.backend, addrs, config) - return err -} - -// Watch the store until either there's a store error or we receive a stop request. -// Returns false if we shouldn't attempt watching the store anymore (stop request received). -func (s *Discovery) watchOnce(stopCh <-chan struct{}, watchCh <-chan []*store.KVPair, discoveryCh chan discovery.Entries, errCh chan error) bool { - for { - select { - case pairs := <-watchCh: - if pairs == nil { - return true - } - - log.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs)) - - // Convert `KVPair` into `discovery.Entry`. - addrs := make([]string, len(pairs)) - for _, pair := range pairs { - addrs = append(addrs, string(pair.Value)) - } - - entries, err := discovery.CreateEntries(addrs) - if err != nil { - errCh <- err - } else { - discoveryCh <- entries - } - case <-stopCh: - // We were requested to stop watching. - return false - } - } -} - -// Watch is exported -func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { - ch := make(chan discovery.Entries) - errCh := make(chan error) - - go func() { - defer close(ch) - defer close(errCh) - - // Forever: Create a store watch, watch until we get an error and then try again. - // Will only stop if we receive a stopCh request. - for { - // Create the path to watch if it does not exist yet - exists, err := s.store.Exists(s.path) - if err != nil { - errCh <- err - } - if !exists { - if err := s.store.Put(s.path, []byte(""), &store.WriteOptions{IsDir: true}); err != nil { - errCh <- err - } - } - - // Set up a watch. - watchCh, err := s.store.WatchTree(s.path, stopCh) - if err != nil { - errCh <- err - } else { - if !s.watchOnce(stopCh, watchCh, ch, errCh) { - return - } - } - - // If we get here it means the store watch channel was closed. This - // is unexpected so let's retry later. - errCh <- fmt.Errorf("Unexpected watch error") - time.Sleep(s.heartbeat) - } - }() - - return ch, errCh -} - -// Register is exported -func (s *Discovery) Register(addr string) error { - opts := &store.WriteOptions{TTL: s.ttl} - return s.store.Put(path.Join(s.path, addr), []byte(addr), opts) -} - -// Store returns the underlying store used by KV discovery. -func (s *Discovery) Store() store.Store { - return s.store -} - -// Prefix returns the store prefix -func (s *Discovery) Prefix() string { - return s.prefix -} diff --git a/discovery/kv/kv_test.go b/discovery/kv/kv_test.go deleted file mode 100644 index 045559c276..0000000000 --- a/discovery/kv/kv_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package kv - -import ( - "errors" - "io/ioutil" - "os" - "path" - "testing" - "time" - - "github.com/docker/libkv" - "github.com/docker/libkv/store" - libkvmock "github.com/docker/libkv/store/mock" - "github.com/docker/swarm/discovery" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestInitialize(t *testing.T) { - storeMock, err := libkvmock.New([]string{"127.0.0.1"}, nil) - assert.NotNil(t, storeMock) - assert.NoError(t, err) - - d := &Discovery{backend: store.CONSUL} - d.Initialize("127.0.0.1", 0, 0, nil) - d.store = storeMock - - s := d.store.(*libkvmock.Mock) - assert.Len(t, s.Endpoints, 1) - assert.Equal(t, s.Endpoints[0], "127.0.0.1") - assert.Equal(t, d.path, defaultDiscoveryPath) - - storeMock, err = libkvmock.New([]string{"127.0.0.1:1234"}, nil) - assert.NotNil(t, storeMock) - assert.NoError(t, err) - - d = &Discovery{backend: store.CONSUL} - d.Initialize("127.0.0.1:1234/path", 0, 0, nil) - d.store = storeMock - - s = d.store.(*libkvmock.Mock) - assert.Len(t, s.Endpoints, 1) - assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234") - assert.Equal(t, d.path, "path/"+defaultDiscoveryPath) - - storeMock, err = libkvmock.New([]string{"127.0.0.1:1234", "127.0.0.2:1234", "127.0.0.3:1234"}, nil) - assert.NotNil(t, storeMock) - assert.NoError(t, err) - - d = &Discovery{backend: store.CONSUL} - d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0, nil) - d.store = storeMock - - s = d.store.(*libkvmock.Mock) - if assert.Len(t, s.Endpoints, 3) { - assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234") - assert.Equal(t, s.Endpoints[1], "127.0.0.2:1234") - assert.Equal(t, s.Endpoints[2], "127.0.0.3:1234") - } - assert.Equal(t, d.path, "path/"+defaultDiscoveryPath) -} - -func TestInitializeWithCerts(t *testing.T) { - cert := `-----BEGIN CERTIFICATE----- -MIIDCDCCAfKgAwIBAgIICifG7YeiQOEwCwYJKoZIhvcNAQELMBIxEDAOBgNVBAMT -B1Rlc3QgQ0EwHhcNMTUxMDAxMjMwMDAwWhcNMjAwOTI5MjMwMDAwWjASMRAwDgYD -VQQDEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wRC -O+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4+zE9h80aC4hz+6caRpds -+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhRSoSi3nY+B7F2E8cuz14q -V2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZrpXUyXxAvzXfpFXo1RhSb -UywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUerVYrCPq8vqfn//01qz55 -Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHojxOpXTBepUCIJLbtNnWFT -V44t9gh5IqIWtoBReQIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/ -BAgwBgEB/wIBAjAdBgNVHQ4EFgQUZKUI8IIjIww7X/6hvwggQK4bD24wHwYDVR0j -BBgwFoAUZKUI8IIjIww7X/6hvwggQK4bD24wCwYJKoZIhvcNAQELA4IBAQDES2cz -7sCQfDCxCIWH7X8kpi/JWExzUyQEJ0rBzN1m3/x8ySRxtXyGekimBqQwQdFqlwMI -xzAQKkh3ue8tNSzRbwqMSyH14N1KrSxYS9e9szJHfUasoTpQGPmDmGIoRJuq1h6M -ej5x1SCJ7GWCR6xEXKUIE9OftXm9TdFzWa7Ja3OHz/mXteii8VXDuZ5ACq6EE5bY -8sP4gcICfJ5fTrpTlk9FIqEWWQrCGa5wk95PGEj+GJpNogjXQ97wVoo/Y3p1brEn -t5zjN9PAq4H1fuCMdNNA+p1DHNwd+ELTxcMAnb2ajwHvV6lKPXutrTFc4umJToBX -FpTxDmJHEV4bzUzh ------END CERTIFICATE----- -` - key := `-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA1wRCO+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4 -+zE9h80aC4hz+6caRpds+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhR -SoSi3nY+B7F2E8cuz14qV2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZr -pXUyXxAvzXfpFXo1RhSbUywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUe -rVYrCPq8vqfn//01qz55Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHoj -xOpXTBepUCIJLbtNnWFTV44t9gh5IqIWtoBReQIDAQABAoIBAHSWipORGp/uKFXj -i/mut776x8ofsAxhnLBARQr93ID+i49W8H7EJGkOfaDjTICYC1dbpGrri61qk8sx -qX7p3v/5NzKwOIfEpirgwVIqSNYe/ncbxnhxkx6tXtUtFKmEx40JskvSpSYAhmmO -1XSx0E/PWaEN/nLgX/f1eWJIlxlQkk3QeqL+FGbCXI48DEtlJ9+MzMu4pAwZTpj5 -5qtXo5JJ0jRGfJVPAOznRsYqv864AhMdMIWguzk6EGnbaCWwPcfcn+h9a5LMdony -MDHfBS7bb5tkF3+AfnVY3IBMVx7YlsD9eAyajlgiKu4zLbwTRHjXgShy+4Oussz0 -ugNGnkECgYEA/hi+McrZC8C4gg6XqK8+9joD8tnyDZDz88BQB7CZqABUSwvjDqlP -L8hcwo/lzvjBNYGkqaFPUICGWKjeCtd8pPS2DCVXxDQX4aHF1vUur0uYNncJiV3N -XQz4Iemsa6wnKf6M67b5vMXICw7dw0HZCdIHD1hnhdtDz0uVpeevLZ8CgYEA2KCT -Y43lorjrbCgMqtlefkr3GJA9dey+hTzCiWEOOqn9RqGoEGUday0sKhiLofOgmN2B -LEukpKIey8s+Q/cb6lReajDVPDsMweX8i7hz3Wa4Ugp4Xa5BpHqu8qIAE2JUZ7bU -t88aQAYE58pUF+/Lq1QzAQdrjjzQBx6SrBxieecCgYEAvukoPZEC8mmiN1VvbTX+ -QFHmlZha3QaDxChB+QUe7bMRojEUL/fVnzkTOLuVFqSfxevaI/km9n0ac5KtAchV -xjp2bTnBb5EUQFqjopYktWA+xO07JRJtMfSEmjZPbbay1kKC7rdTfBm961EIHaRj -xZUf6M+rOE8964oGrdgdLlECgYEA046GQmx6fh7/82FtdZDRQp9tj3SWQUtSiQZc -qhO59Lq8mjUXz+MgBuJXxkiwXRpzlbaFB0Bca1fUoYw8o915SrDYf/Zu2OKGQ/qa -V81sgiVmDuEgycR7YOlbX6OsVUHrUlpwhY3hgfMe6UtkMvhBvHF/WhroBEIJm1pV -PXZ/CbMCgYEApNWVktFBjOaYfY6SNn4iSts1jgsQbbpglg3kT7PLKjCAhI6lNsbk -dyT7ut01PL6RaW4SeQWtrJIVQaM6vF3pprMKqlc5XihOGAmVqH7rQx9rtQB5TicL -BFrwkQE4HQtQBV60hYQUzzlSk44VFDz+jxIEtacRHaomDRh2FtOTz+I= ------END RSA PRIVATE KEY----- -` - certFile, err := ioutil.TempFile("", "cert") - assert.Nil(t, err) - defer os.Remove(certFile.Name()) - certFile.Write([]byte(cert)) - certFile.Close() - keyFile, err := ioutil.TempFile("", "key") - assert.Nil(t, err) - defer os.Remove(keyFile.Name()) - keyFile.Write([]byte(key)) - keyFile.Close() - - libkv.AddStore("mock", libkvmock.New) - d := &Discovery{backend: "mock"} - err = d.Initialize("127.0.0.3:1234", 0, 0, map[string]string{ - "kv.cacertfile": certFile.Name(), - "kv.certfile": certFile.Name(), - "kv.keyfile": keyFile.Name(), - }) - assert.Nil(t, err) - s := d.store.(*libkvmock.Mock) - assert.Equal(t, s.Options.ClientTLS.CACertFile, certFile.Name()) - assert.Equal(t, s.Options.ClientTLS.CertFile, certFile.Name()) - assert.Equal(t, s.Options.ClientTLS.KeyFile, keyFile.Name()) -} - -func TestWatch(t *testing.T) { - storeMock, err := libkvmock.New([]string{"127.0.0.1:1234"}, nil) - assert.NotNil(t, storeMock) - assert.NoError(t, err) - - d := &Discovery{backend: store.CONSUL} - d.Initialize("127.0.0.1:1234/path", 0, 0, nil) - d.store = storeMock - - s := d.store.(*libkvmock.Mock) - mockCh := make(chan []*store.KVPair) - - // The first watch will fail on those three calls - s.On("Exists", "path/"+defaultDiscoveryPath).Return(false, errors.New("test error")) - s.On("Put", "path/"+defaultDiscoveryPath, mock.Anything, mock.Anything).Return(errors.New("test error")) - s.On("WatchTree", "path/"+defaultDiscoveryPath, mock.Anything).Return(mockCh, errors.New("test error")).Once() - - // The second one will succeed. - s.On("WatchTree", "path/"+defaultDiscoveryPath, mock.Anything).Return(mockCh, nil).Once() - expected := discovery.Entries{ - &discovery.Entry{Host: "1.1.1.1", Port: "1111"}, - &discovery.Entry{Host: "2.2.2.2", Port: "2222"}, - } - kvs := []*store.KVPair{ - {Key: path.Join("path", defaultDiscoveryPath, "1.1.1.1"), Value: []byte("1.1.1.1:1111")}, - {Key: path.Join("path", defaultDiscoveryPath, "2.2.2.2"), Value: []byte("2.2.2.2:2222")}, - } - - stopCh := make(chan struct{}) - ch, errCh := d.Watch(stopCh) - - // It should fire an error since the first WatchTree call failed. - assert.EqualError(t, <-errCh, "test error") - // We have to drain the error channel otherwise Watch will get stuck. - go func() { - for range errCh { - } - }() - - // Push the entries into the store channel and make sure discovery emits. - mockCh <- kvs - assert.Equal(t, <-ch, expected) - - // Add a new entry. - expected = append(expected, &discovery.Entry{Host: "3.3.3.3", Port: "3333"}) - kvs = append(kvs, &store.KVPair{Key: path.Join("path", defaultDiscoveryPath, "3.3.3.3"), Value: []byte("3.3.3.3:3333")}) - mockCh <- kvs - assert.Equal(t, <-ch, expected) - - // Make sure that if an error occurs it retries. - // This third call to WatchTree will be checked later by AssertExpectations. - s.On("WatchTree", "path/"+defaultDiscoveryPath, mock.Anything).Return(mockCh, nil) - close(mockCh) - // Give it enough time to call WatchTree. - time.Sleep(3) - - // Stop and make sure it closes all channels. - close(stopCh) - assert.Nil(t, <-ch) - assert.Nil(t, <-errCh) - - s.AssertExpectations(t) -} diff --git a/discovery/nodes/nodes.go b/discovery/nodes/nodes.go deleted file mode 100644 index 181a15879f..0000000000 --- a/discovery/nodes/nodes.go +++ /dev/null @@ -1,54 +0,0 @@ -package nodes - -import ( - "fmt" - "strings" - "time" - - "github.com/docker/swarm/discovery" -) - -// Discovery is exported -type Discovery struct { - entries discovery.Entries -} - -func init() { - Init() -} - -// Init is exported -func Init() { - discovery.Register("nodes", &Discovery{}) -} - -// Initialize is exported -func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration, _ map[string]string) error { - for _, input := range strings.Split(uris, ",") { - for _, ip := range discovery.Generate(input) { - entry, err := discovery.NewEntry(ip) - if err != nil { - return fmt.Errorf("%s, please check you are using the correct discovery (missing token:// ?)", err.Error()) - } - s.entries = append(s.entries, entry) - } - } - - return nil -} - -// Watch is exported -func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { - ch := make(chan discovery.Entries) - go func() { - defer close(ch) - ch <- s.entries - <-stopCh - }() - return ch, nil -} - -// Register is exported -func (s *Discovery) Register(addr string) error { - return discovery.ErrNotImplemented -} diff --git a/discovery/nodes/nodes_test.go b/discovery/nodes/nodes_test.go deleted file mode 100644 index dd1ed4fe26..0000000000 --- a/discovery/nodes/nodes_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package nodes - -import ( - "testing" - - "github.com/docker/swarm/discovery" - "github.com/stretchr/testify/assert" -) - -func TestInitialize(t *testing.T) { - d := &Discovery{} - d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0, nil) - assert.Equal(t, len(d.entries), 2) - assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111") - assert.Equal(t, d.entries[1].String(), "2.2.2.2:2222") -} - -func TestInitializeWithPattern(t *testing.T) { - d := &Discovery{} - d.Initialize("1.1.1.[1:2]:1111,2.2.2.[2:4]:2222", 0, 0, nil) - assert.Equal(t, len(d.entries), 5) - assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111") - assert.Equal(t, d.entries[1].String(), "1.1.1.2:1111") - assert.Equal(t, d.entries[2].String(), "2.2.2.2:2222") - assert.Equal(t, d.entries[3].String(), "2.2.2.3:2222") - assert.Equal(t, d.entries[4].String(), "2.2.2.4:2222") -} - -func TestWatch(t *testing.T) { - d := &Discovery{} - d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0, nil) - expected := discovery.Entries{ - &discovery.Entry{Host: "1.1.1.1", Port: "1111"}, - &discovery.Entry{Host: "2.2.2.2", Port: "2222"}, - } - ch, _ := d.Watch(nil) - assert.True(t, expected.Equals(<-ch)) -} - -func TestRegister(t *testing.T) { - d := &Discovery{} - assert.Error(t, d.Register("0.0.0.0")) -} diff --git a/discovery/token/token.go b/discovery/token/token.go index 1f08e516a3..a416fe2035 100644 --- a/discovery/token/token.go +++ b/discovery/token/token.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/docker/swarm/discovery" + "github.com/docker/docker/pkg/discovery" ) const discoveryURL = "https://discovery.hub.docker.com/v1" diff --git a/discovery/token/token_test.go b/discovery/token/token_test.go index d3c08d8f88..d3c29e32aa 100644 --- a/discovery/token/token_test.go +++ b/discovery/token/token_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/docker/swarm/discovery" + "github.com/docker/docker/pkg/discovery" "github.com/stretchr/testify/assert" ) diff --git a/docs/discovery.md b/docs/discovery.md index f111281c75..7670fe8d30 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -209,8 +209,9 @@ swarm is connected to the public internet. To create your swarm: You can contribute a new discovery backend to Swarm. For information on how to do this, see our -discovery README in the Docker Swarm repository. +href="https://github.com/docker/docker/tree/master/pkg/discovery"> +github.com/docker/docker/pkg/discovery. + ## Docker Swarm documentation index diff --git a/main.go b/main.go index f90922793d..e59a0637cc 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,9 @@ package main import ( - _ "github.com/docker/swarm/discovery/file" - _ "github.com/docker/swarm/discovery/kv" - _ "github.com/docker/swarm/discovery/nodes" + _ "github.com/docker/docker/pkg/discovery/file" + _ "github.com/docker/docker/pkg/discovery/kv" + _ "github.com/docker/docker/pkg/discovery/nodes" _ "github.com/docker/swarm/discovery/token" "github.com/docker/swarm/cli" From 31ad0e047f4a7b236c559ccf3793979ae0af086a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 11 Jan 2016 12:19:42 -0800 Subject: [PATCH 3/4] update godeps Signed-off-by: Victor Vieux --- Godeps/Godeps.json | 69 ++-- .../docker/docker/pkg/discovery/README.md | 41 ++ .../docker/docker/pkg/discovery/backends.go | 111 +++++ .../docker/docker/pkg/discovery/discovery.go | 35 ++ .../docker/docker/pkg/discovery/entry.go | 97 +++++ .../docker/docker/pkg/discovery/file/file.go | 109 +++++ .../docker/docker/pkg/discovery/generator.go | 35 ++ .../docker/docker/pkg/discovery/kv/kv.go | 192 +++++++++ .../docker/pkg/discovery/nodes/nodes.go | 54 +++ .../src/github.com/docker/engine-api/LICENSE | 191 +++++++++ .../vendor/src/github.com/miekg/dns/COPYRIGHT | 9 + .../vendor/src/github.com/miekg/dns/LICENSE | 32 ++ .../src/github.com/docker/engine-api/LICENSE | 191 +++++++++ .../api => engine-api}/types/filters/parse.go | 0 .../github.com/docker/go-connections/LICENSE | 191 +++++++++ .../tlsconfig/config.go | 0 .../pmezard/go-difflib/difflib/difflib.go | 22 +- .../testify/assert/assertion_forward.go | 387 ++++++++++++++++++ .../testify/assert/assertion_forward.go.tmpl | 4 + .../stretchr/testify/assert/assertions.go | 73 ++-- .../testify/assert/forward_assertions.go | 270 +----------- .../testify/assert/http_assertions.go | 51 --- .../github.com/stretchr/testify/mock/doc.go | 3 +- .../github.com/stretchr/testify/mock/mock.go | 105 ++--- .../vendor/github.com/davecgh/go-spew/LICENSE | 13 + .../github.com/pmezard/go-difflib/LICENSE | 27 ++ .../github.com/stretchr/objx/LICENSE.md | 23 ++ api/handlers.go | 2 +- cluster/image.go | 2 +- cluster/image_test.go | 2 +- 30 files changed, 1901 insertions(+), 440 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/docker/engine-api/LICENSE create mode 100644 Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/COPYRIGHT create mode 100644 Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/LICENSE create mode 100644 Godeps/_workspace/src/github.com/docker/engine-api/LICENSE rename Godeps/_workspace/src/github.com/docker/{docker/api => engine-api}/types/filters/parse.go (100%) create mode 100644 Godeps/_workspace/src/github.com/docker/go-connections/LICENSE rename Godeps/_workspace/src/github.com/docker/{docker/pkg => go-connections}/tlsconfig/config.go (100%) create mode 100644 Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go create mode 100644 Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go.tmpl create mode 100644 Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/LICENSE create mode 100644 Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/LICENSE create mode 100644 Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/stretchr/objx/LICENSE.md diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a073dd5670..cf7d0e1f10 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -14,57 +14,62 @@ }, { "ImportPath": "github.com/coreos/etcd/client", - "Comment": "v2.3.0-alpha.0-390-gc70d533", - "Rev": "c70d53377134659390f79c00b47e4f2bdd1d01d6" + "Comment": "v2.3.0-alpha.0-430-g374b14e", + "Rev": "374b14e47189c249c069c9b3376cf5c36f286fa6" }, { "ImportPath": "github.com/coreos/etcd/pkg/pathutil", - "Comment": "v2.3.0-alpha.0-390-gc70d533", - "Rev": "c70d53377134659390f79c00b47e4f2bdd1d01d6" + "Comment": "v2.3.0-alpha.0-430-g374b14e", + "Rev": "374b14e47189c249c069c9b3376cf5c36f286fa6" }, { "ImportPath": "github.com/coreos/etcd/pkg/types", - "Comment": "v2.3.0-alpha.0-390-gc70d533", - "Rev": "c70d53377134659390f79c00b47e4f2bdd1d01d6" + "Comment": "v2.3.0-alpha.0-430-g374b14e", + "Rev": "374b14e47189c249c069c9b3376cf5c36f286fa6" }, { "ImportPath": "github.com/davecgh/go-spew/spew", "Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" }, { - "ImportPath": "github.com/docker/docker/api/types/filters", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" + "ImportPath": "github.com/docker/docker/pkg/discovery", + "Comment": "v1.4.1-9107-gf11b6a2", + "Rev": "f11b6a2ab313a03d051dd6f69d264d0482df72d6" }, { "ImportPath": "github.com/docker/docker/pkg/ioutils", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" + "Comment": "v1.4.1-9107-gf11b6a2", + "Rev": "f11b6a2ab313a03d051dd6f69d264d0482df72d6" }, { "ImportPath": "github.com/docker/docker/pkg/longpath", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" + "Comment": "v1.4.1-9107-gf11b6a2", + "Rev": "f11b6a2ab313a03d051dd6f69d264d0482df72d6" }, { "ImportPath": "github.com/docker/docker/pkg/random", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" + "Comment": "v1.4.1-9107-gf11b6a2", + "Rev": "f11b6a2ab313a03d051dd6f69d264d0482df72d6" }, { "ImportPath": "github.com/docker/docker/pkg/stringid", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" - }, - { - "ImportPath": "github.com/docker/docker/pkg/tlsconfig", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" + "Comment": "v1.4.1-9107-gf11b6a2", + "Rev": "f11b6a2ab313a03d051dd6f69d264d0482df72d6" }, { "ImportPath": "github.com/docker/docker/pkg/version", - "Comment": "v1.4.1-8996-g7fab931", - "Rev": "7fab93175d605f98cddf811819d9ab081bb4f90e" + "Comment": "v1.4.1-9107-gf11b6a2", + "Rev": "f11b6a2ab313a03d051dd6f69d264d0482df72d6" + }, + { + "ImportPath": "github.com/docker/engine-api/types/filters", + "Comment": "v0.2.0", + "Rev": "259fce04b5a17bc7a202090d52ca873762fa8b25" + }, + { + "ImportPath": "github.com/docker/go-connections/tlsconfig", + "Comment": "v0.1.2", + "Rev": "4e42727957c146776e5de9cec8c39e4059ed9f20" }, { "ImportPath": "github.com/docker/go-units", @@ -95,8 +100,8 @@ }, { "ImportPath": "github.com/hashicorp/consul/api", - "Comment": "v0.6.0-121-g68969ce", - "Rev": "68969ce5f4499cbe3a4f946917be2e580f1b1936" + "Comment": "v0.6.1-11-g562bf11", + "Rev": "562bf11e9ff784824f9c5fec0ad3609805e13a3d" }, { "ImportPath": "github.com/hashicorp/go-cleanhttp", @@ -148,7 +153,7 @@ }, { "ImportPath": "github.com/pmezard/go-difflib/difflib", - "Rev": "e8554b8641db39598be7f6342874b958f12ae1d4" + "Rev": "792786c7400a136282c1664665ae0a8db921c6c2" }, { "ImportPath": "github.com/samalba/dockerclient", @@ -168,13 +173,13 @@ }, { "ImportPath": "github.com/stretchr/testify/assert", - "Comment": "v1.0-91-g5b9da39", - "Rev": "5b9da39b66e8e994455c2525c4421c8cc00a7f93" + "Comment": "v1.1.3", + "Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18" }, { "ImportPath": "github.com/stretchr/testify/mock", - "Comment": "v1.0-91-g5b9da39", - "Rev": "5b9da39b66e8e994455c2525c4421c8cc00a7f93" + "Comment": "v1.1.3", + "Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18" }, { "ImportPath": "github.com/ugorji/go/codec", @@ -182,7 +187,7 @@ }, { "ImportPath": "golang.org/x/net/context", - "Rev": "1ade16a5450925b7496e1031938175d1f5d30d31" + "Rev": "4fd4a9fed55e5bdee4a89d6406c2eabe38b60300" }, { "ImportPath": "golang.org/x/sys/unix", diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md new file mode 100644 index 0000000000..39777c2171 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md @@ -0,0 +1,41 @@ +--- +page_title: Docker discovery +page_description: discovery +page_keywords: docker, clustering, discovery +--- + +# Discovery + +Docker comes with multiple Discovery backends. + +## Backends + +### Using etcd + +Point your Docker Engine instances to a common etcd instance. You can specify +the address Docker uses to advertise the node using the `--cluster-advertise` +flag. + +```bash +$ docker daemon -H= --cluster-advertise= --cluster-store etcd://,/ +``` + +### Using consul + +Point your Docker Engine instances to a common Consul instance. You can specify +the address Docker uses to advertise the node using the `--cluster-advertise` +flag. + +```bash +$ docker daemon -H= --cluster-advertise= --cluster-store consul:/// +``` + +### Using zookeeper + +Point your Docker Engine instances to a common Zookeeper instance. You can specify +the address Docker uses to advertise the node using the `--cluster-advertise` +flag. + +```bash +$ docker daemon -H= --cluster-advertise= --cluster-store zk://,/ +``` diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go new file mode 100644 index 0000000000..875a26c442 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go @@ -0,0 +1,111 @@ +package discovery + +import ( + "fmt" + "net" + "strings" + "time" + + log "github.com/Sirupsen/logrus" +) + +var ( + // Backends is a global map of discovery backends indexed by their + // associated scheme. + backends map[string]Backend +) + +func init() { + backends = make(map[string]Backend) +} + +// Register makes a discovery backend available by the provided scheme. +// If Register is called twice with the same scheme an error is returned. +func Register(scheme string, d Backend) error { + if _, exists := backends[scheme]; exists { + return fmt.Errorf("scheme already registered %s", scheme) + } + log.WithField("name", scheme).Debug("Registering discovery service") + backends[scheme] = d + return nil +} + +func parse(rawurl string) (string, string) { + parts := strings.SplitN(rawurl, "://", 2) + + // nodes:port,node2:port => nodes://node1:port,node2:port + if len(parts) == 1 { + return "nodes", parts[0] + } + return parts[0], parts[1] +} + +// ParseAdvertise parses the --cluster-advertise daemon config which accepts +// : or : +func ParseAdvertise(store, advertise string) (string, error) { + var ( + iface *net.Interface + addrs []net.Addr + err error + ) + + addr, port, err := net.SplitHostPort(advertise) + + if err != nil { + return "", fmt.Errorf("invalid --cluster-advertise configuration: %s: %v", advertise, err) + } + + ip := net.ParseIP(addr) + // If it is a valid ip-address, use it as is + if ip != nil { + return advertise, nil + } + + // If advertise is a valid interface name, get the valid ipv4 address and use it to advertise + ifaceName := addr + iface, err = net.InterfaceByName(ifaceName) + if err != nil { + return "", fmt.Errorf("invalid cluster advertise IP address or interface name (%s) : %v", advertise, err) + } + + addrs, err = iface.Addrs() + if err != nil { + return "", fmt.Errorf("unable to get advertise IP address from interface (%s) : %v", advertise, err) + } + + if addrs == nil || len(addrs) == 0 { + return "", fmt.Errorf("no available advertise IP address in interface (%s)", advertise) + } + + addr = "" + for _, a := range addrs { + ip, _, err := net.ParseCIDR(a.String()) + if err != nil { + return "", fmt.Errorf("error deriving advertise ip-address in interface (%s) : %v", advertise, err) + } + if ip.To4() == nil || ip.IsLoopback() { + continue + } + addr = ip.String() + break + } + if addr == "" { + return "", fmt.Errorf("couldnt find a valid ip-address in interface %s", advertise) + } + + addr = fmt.Sprintf("%s:%s", addr, port) + return addr, nil +} + +// New returns a new Discovery given a URL, heartbeat and ttl settings. +// Returns an error if the URL scheme is not supported. +func New(rawurl string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) (Backend, error) { + scheme, uri := parse(rawurl) + if backend, exists := backends[scheme]; exists { + log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service") + err := backend.Initialize(uri, heartbeat, ttl, clusterOpts) + return backend, err + } + + return nil, ErrNotSupported +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go new file mode 100644 index 0000000000..ca7f587458 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go @@ -0,0 +1,35 @@ +package discovery + +import ( + "errors" + "time" +) + +var ( + // ErrNotSupported is returned when a discovery service is not supported. + ErrNotSupported = errors.New("discovery service not supported") + + // ErrNotImplemented is returned when discovery feature is not implemented + // by discovery backend. + ErrNotImplemented = errors.New("not implemented in this discovery service") +) + +// Watcher provides watching over a cluster for nodes joining and leaving. +type Watcher interface { + // Watch the discovery for entry changes. + // Returns a channel that will receive changes or an error. + // Providing a non-nil stopCh can be used to stop watching. + Watch(stopCh <-chan struct{}) (<-chan Entries, <-chan error) +} + +// Backend is implemented by discovery backends which manage cluster entries. +type Backend interface { + // Watcher must be provided by every backend. + Watcher + + // Initialize the discovery with URIs, a heartbeat, a ttl and optional settings. + Initialize(string, time.Duration, time.Duration, map[string]string) error + + // Register to the discovery. + Register(string) error +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go new file mode 100644 index 0000000000..e9cee26ee1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go @@ -0,0 +1,97 @@ +package discovery + +import ( + "fmt" + "net" +) + +// NewEntry creates a new entry. +func NewEntry(url string) (*Entry, error) { + host, port, err := net.SplitHostPort(url) + if err != nil { + return nil, err + } + return &Entry{host, port}, nil +} + +// An Entry represents a host. +type Entry struct { + Host string + Port string +} + +// Equals returns true if cmp contains the same data. +func (e *Entry) Equals(cmp *Entry) bool { + return e.Host == cmp.Host && e.Port == cmp.Port +} + +// String returns the string form of an entry. +func (e *Entry) String() string { + return fmt.Sprintf("%s:%s", e.Host, e.Port) +} + +// Entries is a list of *Entry with some helpers. +type Entries []*Entry + +// Equals returns true if cmp contains the same data. +func (e Entries) Equals(cmp Entries) bool { + // Check if the file has really changed. + if len(e) != len(cmp) { + return false + } + for i := range e { + if !e[i].Equals(cmp[i]) { + return false + } + } + return true +} + +// Contains returns true if the Entries contain a given Entry. +func (e Entries) Contains(entry *Entry) bool { + for _, curr := range e { + if curr.Equals(entry) { + return true + } + } + return false +} + +// Diff compares two entries and returns the added and removed entries. +func (e Entries) Diff(cmp Entries) (Entries, Entries) { + added := Entries{} + for _, entry := range cmp { + if !e.Contains(entry) { + added = append(added, entry) + } + } + + removed := Entries{} + for _, entry := range e { + if !cmp.Contains(entry) { + removed = append(removed, entry) + } + } + + return added, removed +} + +// CreateEntries returns an array of entries based on the given addresses. +func CreateEntries(addrs []string) (Entries, error) { + entries := Entries{} + if addrs == nil { + return entries, nil + } + + for _, addr := range addrs { + if len(addr) == 0 { + continue + } + entry, err := NewEntry(addr) + if err != nil { + return nil, err + } + entries = append(entries, entry) + } + return entries, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go new file mode 100644 index 0000000000..b4f870b864 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go @@ -0,0 +1,109 @@ +package file + +import ( + "fmt" + "io/ioutil" + "strings" + "time" + + "github.com/docker/docker/pkg/discovery" +) + +// Discovery is exported +type Discovery struct { + heartbeat time.Duration + path string +} + +func init() { + Init() +} + +// Init is exported +func Init() { + discovery.Register("file", &Discovery{}) +} + +// Initialize is exported +func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration, _ map[string]string) error { + s.path = path + s.heartbeat = heartbeat + return nil +} + +func parseFileContent(content []byte) []string { + var result []string + for _, line := range strings.Split(strings.TrimSpace(string(content)), "\n") { + line = strings.TrimSpace(line) + // Ignoring line starts with # + if strings.HasPrefix(line, "#") { + continue + } + // Inlined # comment also ignored. + if strings.Contains(line, "#") { + line = line[0:strings.Index(line, "#")] + // Trim additional spaces caused by above stripping. + line = strings.TrimSpace(line) + } + for _, ip := range discovery.Generate(line) { + result = append(result, ip) + } + } + return result +} + +func (s *Discovery) fetch() (discovery.Entries, error) { + fileContent, err := ioutil.ReadFile(s.path) + if err != nil { + return nil, fmt.Errorf("failed to read '%s': %v", s.path, err) + } + return discovery.CreateEntries(parseFileContent(fileContent)) +} + +// Watch is exported +func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { + ch := make(chan discovery.Entries) + errCh := make(chan error) + ticker := time.NewTicker(s.heartbeat) + + go func() { + defer close(errCh) + defer close(ch) + + // Send the initial entries if available. + currentEntries, err := s.fetch() + if err != nil { + errCh <- err + } else { + ch <- currentEntries + } + + // Periodically send updates. + for { + select { + case <-ticker.C: + newEntries, err := s.fetch() + if err != nil { + errCh <- err + continue + } + + // Check if the file has really changed. + if !newEntries.Equals(currentEntries) { + ch <- newEntries + } + currentEntries = newEntries + case <-stopCh: + ticker.Stop() + return + } + } + }() + + return ch, errCh +} + +// Register is exported +func (s *Discovery) Register(addr string) error { + return discovery.ErrNotImplemented +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go new file mode 100644 index 0000000000..d22298298f --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go @@ -0,0 +1,35 @@ +package discovery + +import ( + "fmt" + "regexp" + "strconv" +) + +// Generate takes care of IP generation +func Generate(pattern string) []string { + re, _ := regexp.Compile(`\[(.+):(.+)\]`) + submatch := re.FindStringSubmatch(pattern) + if submatch == nil { + return []string{pattern} + } + + from, err := strconv.Atoi(submatch[1]) + if err != nil { + return []string{pattern} + } + to, err := strconv.Atoi(submatch[2]) + if err != nil { + return []string{pattern} + } + + template := re.ReplaceAllString(pattern, "%d") + + var result []string + for val := from; val <= to; val++ { + entry := fmt.Sprintf(template, val) + result = append(result, entry) + } + + return result +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go new file mode 100644 index 0000000000..f371c0cba0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go @@ -0,0 +1,192 @@ +package kv + +import ( + "fmt" + "path" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/discovery" + "github.com/docker/go-connections/tlsconfig" + "github.com/docker/libkv" + "github.com/docker/libkv/store" + "github.com/docker/libkv/store/consul" + "github.com/docker/libkv/store/etcd" + "github.com/docker/libkv/store/zookeeper" +) + +const ( + defaultDiscoveryPath = "docker/nodes" +) + +// Discovery is exported +type Discovery struct { + backend store.Backend + store store.Store + heartbeat time.Duration + ttl time.Duration + prefix string + path string +} + +func init() { + Init() +} + +// Init is exported +func Init() { + // Register to libkv + zookeeper.Register() + consul.Register() + etcd.Register() + + // Register to internal discovery service + discovery.Register("zk", &Discovery{backend: store.ZK}) + discovery.Register("consul", &Discovery{backend: store.CONSUL}) + discovery.Register("etcd", &Discovery{backend: store.ETCD}) +} + +// Initialize is exported +func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) error { + var ( + parts = strings.SplitN(uris, "/", 2) + addrs = strings.Split(parts[0], ",") + err error + ) + + // A custom prefix to the path can be optionally used. + if len(parts) == 2 { + s.prefix = parts[1] + } + + s.heartbeat = heartbeat + s.ttl = ttl + + // Use a custom path if specified in discovery options + dpath := defaultDiscoveryPath + if clusterOpts["kv.path"] != "" { + dpath = clusterOpts["kv.path"] + } + + s.path = path.Join(s.prefix, dpath) + + var config *store.Config + if clusterOpts["kv.cacertfile"] != "" && clusterOpts["kv.certfile"] != "" && clusterOpts["kv.keyfile"] != "" { + log.Info("Initializing discovery with TLS") + tlsConfig, err := tlsconfig.Client(tlsconfig.Options{ + CAFile: clusterOpts["kv.cacertfile"], + CertFile: clusterOpts["kv.certfile"], + KeyFile: clusterOpts["kv.keyfile"], + }) + if err != nil { + return err + } + config = &store.Config{ + // Set ClientTLS to trigger https (bug in libkv/etcd) + ClientTLS: &store.ClientTLSConfig{ + CACertFile: clusterOpts["kv.cacertfile"], + CertFile: clusterOpts["kv.certfile"], + KeyFile: clusterOpts["kv.keyfile"], + }, + // The actual TLS config that will be used + TLS: tlsConfig, + } + } else { + log.Info("Initializing discovery without TLS") + } + + // Creates a new store, will ignore options given + // if not supported by the chosen store + s.store, err = libkv.NewStore(s.backend, addrs, config) + return err +} + +// Watch the store until either there's a store error or we receive a stop request. +// Returns false if we shouldn't attempt watching the store anymore (stop request received). +func (s *Discovery) watchOnce(stopCh <-chan struct{}, watchCh <-chan []*store.KVPair, discoveryCh chan discovery.Entries, errCh chan error) bool { + for { + select { + case pairs := <-watchCh: + if pairs == nil { + return true + } + + log.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs)) + + // Convert `KVPair` into `discovery.Entry`. + addrs := make([]string, len(pairs)) + for _, pair := range pairs { + addrs = append(addrs, string(pair.Value)) + } + + entries, err := discovery.CreateEntries(addrs) + if err != nil { + errCh <- err + } else { + discoveryCh <- entries + } + case <-stopCh: + // We were requested to stop watching. + return false + } + } +} + +// Watch is exported +func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { + ch := make(chan discovery.Entries) + errCh := make(chan error) + + go func() { + defer close(ch) + defer close(errCh) + + // Forever: Create a store watch, watch until we get an error and then try again. + // Will only stop if we receive a stopCh request. + for { + // Create the path to watch if it does not exist yet + exists, err := s.store.Exists(s.path) + if err != nil { + errCh <- err + } + if !exists { + if err := s.store.Put(s.path, []byte(""), &store.WriteOptions{IsDir: true}); err != nil { + errCh <- err + } + } + + // Set up a watch. + watchCh, err := s.store.WatchTree(s.path, stopCh) + if err != nil { + errCh <- err + } else { + if !s.watchOnce(stopCh, watchCh, ch, errCh) { + return + } + } + + // If we get here it means the store watch channel was closed. This + // is unexpected so let's retry later. + errCh <- fmt.Errorf("Unexpected watch error") + time.Sleep(s.heartbeat) + } + }() + return ch, errCh +} + +// Register is exported +func (s *Discovery) Register(addr string) error { + opts := &store.WriteOptions{TTL: s.ttl} + return s.store.Put(path.Join(s.path, addr), []byte(addr), opts) +} + +// Store returns the underlying store used by KV discovery. +func (s *Discovery) Store() store.Store { + return s.store +} + +// Prefix returns the store prefix +func (s *Discovery) Prefix() string { + return s.prefix +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go new file mode 100644 index 0000000000..c0e3c07b22 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go @@ -0,0 +1,54 @@ +package nodes + +import ( + "fmt" + "strings" + "time" + + "github.com/docker/docker/pkg/discovery" +) + +// Discovery is exported +type Discovery struct { + entries discovery.Entries +} + +func init() { + Init() +} + +// Init is exported +func Init() { + discovery.Register("nodes", &Discovery{}) +} + +// Initialize is exported +func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration, _ map[string]string) error { + for _, input := range strings.Split(uris, ",") { + for _, ip := range discovery.Generate(input) { + entry, err := discovery.NewEntry(ip) + if err != nil { + return fmt.Errorf("%s, please check you are using the correct discovery (missing token:// ?)", err.Error()) + } + s.entries = append(s.entries, entry) + } + } + + return nil +} + +// Watch is exported +func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { + ch := make(chan discovery.Entries) + go func() { + defer close(ch) + ch <- s.entries + <-stopCh + }() + return ch, nil +} + +// Register is exported +func (s *Discovery) Register(addr string) error { + return discovery.ErrNotImplemented +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/docker/engine-api/LICENSE b/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/docker/engine-api/LICENSE new file mode 100644 index 0000000000..c157bff96a --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/docker/engine-api/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2015-2016 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/COPYRIGHT b/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/COPYRIGHT new file mode 100644 index 0000000000..35702b10e8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/COPYRIGHT @@ -0,0 +1,9 @@ +Copyright 2009 The Go Authors. All rights reserved. Use of this source code +is governed by a BSD-style license that can be found in the LICENSE file. +Extensions of the original work are copyright (c) 2011 Miek Gieben + +Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is +governed by a BSD-style license that can be found in the LICENSE file. + +Copyright 2014 CloudFlare. All rights reserved. Use of this source code is +governed by a BSD-style license that can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/LICENSE b/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/LICENSE new file mode 100644 index 0000000000..5763fa7fe5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/vendor/src/github.com/miekg/dns/LICENSE @@ -0,0 +1,32 @@ +Extensions of the original work are copyright (c) 2011 Miek Gieben + +As this is fork of the official Go code the same license applies: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Godeps/_workspace/src/github.com/docker/engine-api/LICENSE b/Godeps/_workspace/src/github.com/docker/engine-api/LICENSE new file mode 100644 index 0000000000..c157bff96a --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/engine-api/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2015-2016 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/docker/docker/api/types/filters/parse.go b/Godeps/_workspace/src/github.com/docker/engine-api/types/filters/parse.go similarity index 100% rename from Godeps/_workspace/src/github.com/docker/docker/api/types/filters/parse.go rename to Godeps/_workspace/src/github.com/docker/engine-api/types/filters/parse.go diff --git a/Godeps/_workspace/src/github.com/docker/go-connections/LICENSE b/Godeps/_workspace/src/github.com/docker/go-connections/LICENSE new file mode 100644 index 0000000000..b55b37bc31 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-connections/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2015 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go b/Godeps/_workspace/src/github.com/docker/go-connections/tlsconfig/config.go similarity index 100% rename from Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go rename to Godeps/_workspace/src/github.com/docker/go-connections/tlsconfig/config.go diff --git a/Godeps/_workspace/src/github.com/pmezard/go-difflib/difflib/difflib.go b/Godeps/_workspace/src/github.com/pmezard/go-difflib/difflib/difflib.go index e5005daaa3..003e99fadb 100644 --- a/Godeps/_workspace/src/github.com/pmezard/go-difflib/difflib/difflib.go +++ b/Godeps/_workspace/src/github.com/pmezard/go-difflib/difflib/difflib.go @@ -585,13 +585,15 @@ func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error { if len(diff.ToDate) > 0 { toDate = "\t" + diff.ToDate } - err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol) - if err != nil { - return err - } - err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol) - if err != nil { - return err + if diff.FromFile != "" || diff.ToFile != "" { + err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol) + if err != nil { + return err + } + err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol) + if err != nil { + return err + } } } first, last := g[0], g[len(g)-1] @@ -710,8 +712,10 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error { if len(diff.ToDate) > 0 { toDate = "\t" + diff.ToDate } - wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol) - wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol) + if diff.FromFile != "" || diff.ToFile != "" { + wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol) + wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol) + } } first, last := g[0], g[len(g)-1] diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go b/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go new file mode 100644 index 0000000000..e6a796046c --- /dev/null +++ b/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go @@ -0,0 +1,387 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND +*/ + +package assert + +import ( + + http "net/http" + url "net/url" + time "time" +) + + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { + return Condition(a.t, comp, msgAndArgs...) +} + + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") +// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + return Contains(a.t, s, contains, msgAndArgs...) +} + + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { + return Empty(a.t, object, msgAndArgs...) +} + + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return Equal(a.t, expected, actual, msgAndArgs...) +} + + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { + return EqualError(a.t, theError, errString, msgAndArgs...) +} + + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return EqualValues(a.t, expected, actual, msgAndArgs...) +} + + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { + return Error(a.t, err, msgAndArgs...) +} + + +// Exactly asserts that two objects are equal is value and type. +// +// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return Exactly(a.t, expected, actual, msgAndArgs...) +} + + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { + return Fail(a.t, failureMessage, msgAndArgs...) +} + + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { + return FailNow(a.t, failureMessage, msgAndArgs...) +} + + +// False asserts that the specified value is false. +// +// a.False(myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { + return False(a.t, value, msgAndArgs...) +} + + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { + return HTTPBodyContains(a.t, handler, method, url, values, str) +} + + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { + return HTTPBodyNotContains(a.t, handler, method, url, values, str) +} + + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { + return HTTPError(a.t, handler, method, url, values) +} + + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { + return HTTPRedirect(a.t, handler, method, url, values) +} + + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { + return HTTPSuccess(a.t, handler, method, url, values) +} + + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + return Implements(a.t, interfaceObject, object, msgAndArgs...) +} + + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + + +// InEpsilonSlice is the same as InEpsilon, except it compares two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) +} + + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + return IsType(a.t, expectedType, object, msgAndArgs...) +} + + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { + return JSONEq(a.t, expected, actual, msgAndArgs...) +} + + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { + return Len(a.t, object, length, msgAndArgs...) +} + + +// Nil asserts that the specified object is nil. +// +// a.Nil(err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { + return Nil(a.t, object, msgAndArgs...) +} + + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { + return NoError(a.t, err, msgAndArgs...) +} + + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + return NotContains(a.t, s, contains, msgAndArgs...) +} + + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { + return NotEmpty(a.t, object, msgAndArgs...) +} + + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + return NotEqual(a.t, expected, actual, msgAndArgs...) +} + + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { + return NotNil(a.t, object, msgAndArgs...) +} + + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + return NotPanics(a.t, f, msgAndArgs...) +} + + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + return NotRegexp(a.t, rx, str, msgAndArgs...) +} + + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { + return NotZero(a.t, i, msgAndArgs...) +} + + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + return Panics(a.t, f, msgAndArgs...) +} + + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + return Regexp(a.t, rx, str, msgAndArgs...) +} + + +// True asserts that the specified value is true. +// +// a.True(myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { + return True(a.t, value, msgAndArgs...) +} + + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + + +// Zero asserts that i is the zero value for its type and returns the truth. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { + return Zero(a.t, i, msgAndArgs...) +} diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go.tmpl b/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go.tmpl new file mode 100644 index 0000000000..99f9acfbba --- /dev/null +++ b/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertion_forward.go.tmpl @@ -0,0 +1,4 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { + return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertions.go b/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertions.go index e95357d29c..d7c16c5903 100644 --- a/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertions.go +++ b/Godeps/_workspace/src/github.com/stretchr/testify/assert/assertions.go @@ -181,6 +181,28 @@ func indentMessageLines(message string, tabs int) string { return outBuf.String() } +type failNower interface { + FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + Fail(t, failureMessage, msgAndArgs...) + + // We cannot extend TestingT with FailNow() and + // maintain backwards compatibility, so we fallback + // to panicking when FailNow is not available in + // TestingT. + // See issue #263 + + if t, ok := t.(failNower); ok { + t.FailNow() + } else { + panic("test failed and t is missing `FailNow()`") + } + return false +} + // Fail reports a failure through func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { @@ -363,6 +385,11 @@ func isEmpty(object interface{}) bool { { return (objValue.Len() == 0) } + case reflect.Struct: + switch object.(type) { + case time.Time: + return object.(time.Time).IsZero() + } case reflect.Ptr: { if objValue.IsNil() { @@ -739,42 +766,40 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn return true } -// min(|expected|, |actual|) * epsilon -func calcEpsilonDelta(expected, actual interface{}, epsilon float64) float64 { +func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) + if !aok { + return 0, fmt.Errorf("expected value %q cannot be converted to float", expected) + } + if af == 0 { + return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error") + } bf, bok := toFloat(actual) - - if !aok || !bok { - // invalid input - return 0 + if !bok { + return 0, fmt.Errorf("expected value %q cannot be converted to float", actual) } - if af < 0 { - af = -af - } - if bf < 0 { - bf = -bf - } - var delta float64 - if af < bf { - delta = af * epsilon - } else { - delta = bf * epsilon - } - return delta + return math.Abs(af-bf) / math.Abs(af), nil } // InEpsilon asserts that expected and actual have a relative error less than epsilon // // Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { - delta := calcEpsilonDelta(expected, actual, epsilon) + actualEpsilon, err := calcRelativeError(expected, actual) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if actualEpsilon > epsilon { + return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ + " < %#v (actual)", actualEpsilon, epsilon), msgAndArgs...) + } - return InDelta(t, expected, actual, delta, msgAndArgs...) + return true } -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func InEpsilonSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { @@ -785,7 +810,7 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, delta float64, msg expectedSlice := reflect.ValueOf(expected) for i := 0; i < actualSlice.Len(); i++ { - result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) + result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), epsilon) if !result { return result } diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/assert/forward_assertions.go b/Godeps/_workspace/src/github.com/stretchr/testify/assert/forward_assertions.go index fe6b664e07..b867e95ea5 100644 --- a/Godeps/_workspace/src/github.com/stretchr/testify/assert/forward_assertions.go +++ b/Godeps/_workspace/src/github.com/stretchr/testify/assert/forward_assertions.go @@ -1,7 +1,5 @@ package assert -import "time" - // Assertions provides assertion methods around the // TestingT interface. type Assertions struct { @@ -15,270 +13,4 @@ func New(t TestingT) *Assertions { } } -// Fail reports a failure through -func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { - return Fail(a.t, failureMessage, msgAndArgs...) -} - -// Implements asserts that an object is implemented by the specified interface. -// -// assert.Implements((*MyInterface)(nil), new(MyObject), "MyObject") -func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - return Implements(a.t, interfaceObject, object, msgAndArgs...) -} - -// IsType asserts that the specified objects are of the same type. -func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { - return IsType(a.t, expectedType, object, msgAndArgs...) -} - -// Equal asserts that two objects are equal. -// -// assert.Equal(123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Equal(expected, actual interface{}, msgAndArgs ...interface{}) bool { - return Equal(a.t, expected, actual, msgAndArgs...) -} - -// EqualValues asserts that two objects are equal or convertable to the same types -// and equal. -// -// assert.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) EqualValues(expected, actual interface{}, msgAndArgs ...interface{}) bool { - return EqualValues(a.t, expected, actual, msgAndArgs...) -} - -// Exactly asserts that two objects are equal is value and type. -// -// assert.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Exactly(expected, actual interface{}, msgAndArgs ...interface{}) bool { - return Exactly(a.t, expected, actual, msgAndArgs...) -} - -// NotNil asserts that the specified object is not nil. -// -// assert.NotNil(err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { - return NotNil(a.t, object, msgAndArgs...) -} - -// Nil asserts that the specified object is nil. -// -// assert.Nil(err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { - return Nil(a.t, object, msgAndArgs...) -} - -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or a -// slice with len == 0. -// -// assert.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { - return Empty(a.t, object, msgAndArgs...) -} - -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or a -// slice with len == 0. -// -// if assert.NotEmpty(obj) { -// assert.Equal("two", obj[1]) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { - return NotEmpty(a.t, object, msgAndArgs...) -} - -// Len asserts that the specified object has specific length. -// Len also fails if the object has a type that len() not accept. -// -// assert.Len(mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { - return Len(a.t, object, length, msgAndArgs...) -} - -// True asserts that the specified value is true. -// -// assert.True(myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { - return True(a.t, value, msgAndArgs...) -} - -// False asserts that the specified value is false. -// -// assert.False(myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { - return False(a.t, value, msgAndArgs...) -} - -// NotEqual asserts that the specified values are NOT equal. -// -// assert.NotEqual(obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotEqual(expected, actual interface{}, msgAndArgs ...interface{}) bool { - return NotEqual(a.t, expected, actual, msgAndArgs...) -} - -// Contains asserts that the specified string contains the specified substring. -// -// assert.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Contains(s, contains interface{}, msgAndArgs ...interface{}) bool { - return Contains(a.t, s, contains, msgAndArgs...) -} - -// NotContains asserts that the specified string does NOT contain the specified substring. -// -// assert.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotContains(s, contains interface{}, msgAndArgs ...interface{}) bool { - return NotContains(a.t, s, contains, msgAndArgs...) -} - -// Condition uses a Comparison to assert a complex condition. -func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { - return Condition(a.t, comp, msgAndArgs...) -} - -// Panics asserts that the code inside the specified PanicTestFunc panics. -// -// assert.Panics(func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { - return Panics(a.t, f, msgAndArgs...) -} - -// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// assert.NotPanics(func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { - return NotPanics(a.t, f, msgAndArgs...) -} - -// WithinDuration asserts that the two times are within duration delta of each other. -// -// assert.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) WithinDuration(expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { - return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) -} - -// InDelta asserts that the two numerals are within delta of each other. -// -// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) InDelta(expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - return InDelta(a.t, expected, actual, delta, msgAndArgs...) -} - -// InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) InEpsilon(expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { - return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) -} - -// NoError asserts that a function returned no error (i.e. `nil`). -// -// actualObj, err := SomeFunction() -// if assert.NoError(err) { -// assert.Equal(actualObj, expectedObj) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NoError(theError error, msgAndArgs ...interface{}) bool { - return NoError(a.t, theError, msgAndArgs...) -} - -// Error asserts that a function returned an error (i.e. not `nil`). -// -// actualObj, err := SomeFunction() -// if assert.Error(err, "An error was expected") { -// assert.Equal(err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Error(theError error, msgAndArgs ...interface{}) bool { - return Error(a.t, theError, msgAndArgs...) -} - -// EqualError asserts that a function returned an error (i.e. not `nil`) -// and that it is equal to the provided error. -// -// actualObj, err := SomeFunction() -// if assert.Error(err, "An error was expected") { -// assert.Equal(err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { - return EqualError(a.t, theError, errString, msgAndArgs...) -} - -// Regexp asserts that a specified regexp matches a string. -// -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { - return Regexp(a.t, rx, str, msgAndArgs...) -} - -// NotRegexp asserts that a specified regexp does not match a string. -// -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { - return NotRegexp(a.t, rx, str, msgAndArgs...) -} - -// Zero asserts that i is the zero value for its type and returns the truth. -func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { - return Zero(a.t, i, msgAndArgs...) -} - -// NotZero asserts that i is not the zero value for its type and returns the truth. -func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { - return NotZero(a.t, i, msgAndArgs...) -} - -// JSONEq asserts that two JSON strings are equivalent. -// -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { - return JSONEq(a.t, expected, actual, msgAndArgs...) -} +//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/assert/http_assertions.go b/Godeps/_workspace/src/github.com/stretchr/testify/assert/http_assertions.go index 437a86ce47..e1b9442b5a 100644 --- a/Godeps/_workspace/src/github.com/stretchr/testify/assert/http_assertions.go +++ b/Godeps/_workspace/src/github.com/stretchr/testify/assert/http_assertions.go @@ -104,54 +104,3 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin return !contains } - -// -// Assertions Wrappers -// - -// HTTPSuccess asserts that a specified handler returns a success status code. -// -// assert.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method, url string, values url.Values) bool { - return HTTPSuccess(a.t, handler, method, url, values) -} - -// HTTPRedirect asserts that a specified handler returns a redirect status code. -// -// assert.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method, url string, values url.Values) bool { - return HTTPRedirect(a.t, handler, method, url, values) -} - -// HTTPError asserts that a specified handler returns an error status code. -// -// assert.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method, url string, values url.Values) bool { - return HTTPError(a.t, handler, method, url, values) -} - -// HTTPBodyContains asserts that a specified handler returns a -// body that contains a string. -// -// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(a.t, handler, method, url, values, str) -} - -// HTTPBodyNotContains asserts that a specified handler returns a -// body that does not contain a string. -// -// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// -// Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(a.t, handler, method, url, values, str) -} diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/mock/doc.go b/Godeps/_workspace/src/github.com/stretchr/testify/mock/doc.go index dd385074bc..7324128ef1 100644 --- a/Godeps/_workspace/src/github.com/stretchr/testify/mock/doc.go +++ b/Godeps/_workspace/src/github.com/stretchr/testify/mock/doc.go @@ -1,4 +1,5 @@ -// Provides a system by which it is possible to mock your objects and verify calls are happening as expected. +// Package mock provides a system by which it is possible to mock your objects +// and verify calls are happening as expected. // // Example Usage // diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/mock/mock.go b/Godeps/_workspace/src/github.com/stretchr/testify/mock/mock.go index bc6c86bc50..637896b434 100644 --- a/Godeps/_workspace/src/github.com/stretchr/testify/mock/mock.go +++ b/Godeps/_workspace/src/github.com/stretchr/testify/mock/mock.go @@ -17,6 +17,7 @@ import ( type TestingT interface { Logf(format string, args ...interface{}) Errorf(format string, args ...interface{}) + FailNow() } /* @@ -64,64 +65,67 @@ func newCall(parent *Mock, methodName string, methodArguments ...interface{}) *C } } -func (self *Call) lock() { - self.Parent.mutex.Lock() +func (c *Call) lock() { + c.Parent.mutex.Lock() } -func (self *Call) unlock() { - self.Parent.mutex.Unlock() +func (c *Call) unlock() { + c.Parent.mutex.Unlock() } -func (self *Call) Return(returnArguments ...interface{}) *Call { - self.lock() - defer self.unlock() +// Return specifies the return arguments for the expectation. +// +// Mock.On("DoSomething").Return(errors.New("failed")) +func (c *Call) Return(returnArguments ...interface{}) *Call { + c.lock() + defer c.unlock() - self.ReturnArguments = returnArguments + c.ReturnArguments = returnArguments - return self + return c } // Once indicates that that the mock should only return the value once. // // Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() -func (self *Call) Once() *Call { - return self.Times(1) +func (c *Call) Once() *Call { + return c.Times(1) } // Twice indicates that that the mock should only return the value twice. // // Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() -func (self *Call) Twice() *Call { - return self.Times(2) +func (c *Call) Twice() *Call { + return c.Times(2) } // Times indicates that that the mock should only return the indicated number // of times. // // Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) -func (self *Call) Times(i int) *Call { - self.lock() - defer self.unlock() - self.Repeatability = i - return self +func (c *Call) Times(i int) *Call { + c.lock() + defer c.unlock() + c.Repeatability = i + return c } // WaitUntil sets the channel that will block the mock's return until its closed // or a message is received. // // Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) -func (self *Call) WaitUntil(w <-chan time.Time) *Call { - self.lock() - defer self.unlock() - self.WaitFor = w - return self +func (c *Call) WaitUntil(w <-chan time.Time) *Call { + c.lock() + defer c.unlock() + c.WaitFor = w + return c } // After sets how long to block until the call returns // // Mock.On("MyMethod", arg1, arg2).After(time.Second) -func (self *Call) After(d time.Duration) *Call { - return self.WaitUntil(time.After(d)) +func (c *Call) After(d time.Duration) *Call { + return c.WaitUntil(time.After(d)) } // Run sets a handler to be called before returning. It can be used when @@ -132,11 +136,11 @@ func (self *Call) After(d time.Duration) *Call { // arg := args.Get(0).(*map[string]interface{}) // arg["foo"] = "bar" // }) -func (self *Call) Run(fn func(Arguments)) *Call { - self.lock() - defer self.unlock() - self.RunFn = fn - return self +func (c *Call) Run(fn func(Arguments)) *Call { + c.lock() + defer c.unlock() + c.RunFn = fn + return c } // On chains a new expectation description onto the mocked interface. This @@ -145,8 +149,8 @@ func (self *Call) Run(fn func(Arguments)) *Call { // Mock. // On("MyMethod", 1).Return(nil). // On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) -func (self *Call) On(methodName string, arguments ...interface{}) *Call { - return self.Parent.On(methodName, arguments...) +func (c *Call) On(methodName string, arguments ...interface{}) *Call { + return c.Parent.On(methodName, arguments...) } // Mock is the workhorse used to track activity on another object. @@ -186,17 +190,17 @@ func (m *Mock) TestData() objx.Map { // being called. // // Mock.On("MyMethod", arg1, arg2) -func (self *Mock) On(methodName string, arguments ...interface{}) *Call { +func (m *Mock) On(methodName string, arguments ...interface{}) *Call { for _, arg := range arguments { if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) } } - self.mutex.Lock() - defer self.mutex.Unlock() - c := newCall(self, methodName, arguments...) - self.ExpectedCalls = append(self.ExpectedCalls, c) + m.mutex.Lock() + defer m.mutex.Unlock() + c := newCall(m, methodName, arguments...) + m.ExpectedCalls = append(m.ExpectedCalls, c) return c } @@ -222,7 +226,7 @@ func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, * func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, *Call) { diffCount := 0 - var closestCall *Call = nil + var closestCall *Call for _, call := range m.expectedCalls() { if call.Method == method { @@ -245,7 +249,7 @@ func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, * func callString(method string, arguments Arguments, includeArgumentValues bool) string { - var argValsString string = "" + var argValsString string if includeArgumentValues { var argVals []string for argIndex, arg := range arguments { @@ -303,7 +307,7 @@ func (m *Mock) Called(arguments ...interface{}) Arguments { call.Repeatability = -1 case call.Repeatability > 1: - call.Repeatability -= 1 + call.Repeatability-- } m.mutex.Unlock() } @@ -334,7 +338,7 @@ func (m *Mock) Called(arguments ...interface{}) Arguments { // // Calls may have occurred in any order. func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { - var success bool = true + var success = true for _, obj := range testObjects { mockObj := obj.(Mock) success = success && mockObj.AssertExpectations(t) @@ -345,8 +349,8 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { // AssertExpectations asserts that everything specified with On and Return was // in fact called as expected. Calls may have occurred in any order. func (m *Mock) AssertExpectations(t TestingT) bool { - var somethingMissing bool = false - var failedExpectations int = 0 + var somethingMissing bool + var failedExpectations int // iterate through each expectation expectedCalls := m.expectedCalls() @@ -376,7 +380,7 @@ func (m *Mock) AssertExpectations(t TestingT) bool { // AssertNumberOfCalls asserts that the method was called expectedCalls times. func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { - var actualCalls int = 0 + var actualCalls int for _, call := range m.calls() { if call.Method == methodName { actualCalls++ @@ -440,8 +444,8 @@ func (m *Mock) calls() []Call { type Arguments []interface{} const ( - // The "any" argument. Used in Diff and Assert when - // the argument being tested shouldn't be taken into consideration. + // Anything is used in Diff and Assert when the argument being tested + // shouldn't be taken into consideration. Anything string = "mock.Anything" ) @@ -471,9 +475,8 @@ func (f argumentMatcher) Matches(argument interface{}) bool { if reflect.TypeOf(argument).AssignableTo(expectType) { result := f.fn.Call([]reflect.Value{reflect.ValueOf(argument)}) return result[0].Bool() - } else { - return false } + return false } func (f argumentMatcher) String() string { @@ -531,10 +534,10 @@ func (args Arguments) Is(objects ...interface{}) bool { // Returns the diff string and number of differences found. func (args Arguments) Diff(objects []interface{}) (string, int) { - var output string = "\n" + var output = "\n" var differences int - var maxArgCount int = len(args) + var maxArgCount = len(args) if len(objects) > maxArgCount { maxArgCount = len(objects) } @@ -629,7 +632,7 @@ func (args Arguments) String(indexOrNil ...int) string { return strings.Join(argsStr, ",") } else if len(indexOrNil) == 1 { // Index has been specified - get the argument at that index - var index int = indexOrNil[0] + var index = indexOrNil[0] var s string var ok bool if s, ok = args.Get(index).(string); !ok { diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/LICENSE b/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/LICENSE new file mode 100644 index 0000000000..2a7cfd2bf6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2012-2013 Dave Collins + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/LICENSE b/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/LICENSE new file mode 100644 index 0000000000..c67dad612a --- /dev/null +++ b/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Patrick Mezard +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/stretchr/objx/LICENSE.md b/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/stretchr/objx/LICENSE.md new file mode 100644 index 0000000000..2199945813 --- /dev/null +++ b/Godeps/_workspace/src/github.com/stretchr/testify/vendor/github.com/stretchr/objx/LICENSE.md @@ -0,0 +1,23 @@ +objx - by Mat Ryer and Tyler Bunnell + +The MIT License (MIT) + +Copyright (c) 2014 Stretchr, Inc. + +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. diff --git a/api/handlers.go b/api/handlers.go index 19cc4bc562..ce10183ba2 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -14,7 +14,7 @@ import ( "strings" "time" - dockerfilters "github.com/docker/docker/api/types/filters" + dockerfilters "github.com/docker/engine-api/types/filters" "github.com/docker/swarm/cluster" "github.com/docker/swarm/version" "github.com/gorilla/mux" diff --git a/cluster/image.go b/cluster/image.go index 61da20306a..20fddea7cc 100644 --- a/cluster/image.go +++ b/cluster/image.go @@ -3,7 +3,7 @@ package cluster import ( "strings" - dockerfilters "github.com/docker/docker/api/types/filters" + dockerfilters "github.com/docker/engine-api/types/filters" "github.com/samalba/dockerclient" ) diff --git a/cluster/image_test.go b/cluster/image_test.go index 0e6af74899..410fba1203 100644 --- a/cluster/image_test.go +++ b/cluster/image_test.go @@ -3,7 +3,7 @@ package cluster import ( "testing" - dockerfilters "github.com/docker/docker/api/types/filters" + dockerfilters "github.com/docker/engine-api/types/filters" "github.com/samalba/dockerclient" "github.com/stretchr/testify/assert" ) From fedf7aa4cb8dd04946d5d3d341014feaf8affdbc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 11 Jan 2016 13:26:49 -0800 Subject: [PATCH 4/4] use "docker/swarm/nodes" Signed-off-by: Victor Vieux --- cli/manage.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/manage.go b/cli/manage.go index 37a2566f56..baca69b45d 100644 --- a/cli/manage.go +++ b/cli/manage.go @@ -126,6 +126,9 @@ func getDiscoveryOpt(c *cli.Context) map[string]string { kvpair := strings.SplitN(option, "=", 2) options[kvpair[0]] = kvpair[1] } + if _, ok := options["kv.path"]; !ok { + options["kv.path"] = "docker/swarm/nodes" + } return options }