From 43a1bd564b5dbd835631cda102f68286c0d533e9 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Fri, 3 Mar 2017 14:08:49 -0800 Subject: [PATCH] Support `--filter mode=global|replicated` for `docker service ls` This fix tries to address the request in 31325 by adding `--filter mode=global|replicated` to `docker service ls`. As `docker service ls` has a `MODE` column by default, it is natural to support `--filter mode=global|replicated` for `docker service ls`. There are multiple ways to address the issue. One way is to pass the filter of mode to SwarmKit, another way is to process the filter of mode in the daemon. This fix process the filter in the daemon. Related docs has been updated. An integration test has been added. This fix fixes 31325. Signed-off-by: Yong Tang --- api/swagger.yaml | 1 + daemon/cluster/filters.go | 16 ------------ daemon/cluster/services.go | 33 ++++++++++++++++++++++-- docs/api/version-history.md | 1 + docs/reference/commandline/service_ls.md | 13 ++++++++++ integration-cli/docker_cli_swarm_test.go | 31 ++++++++++++++++++++++ 6 files changed, 77 insertions(+), 18 deletions(-) diff --git a/api/swagger.yaml b/api/swagger.yaml index a5f38d7165..71528c16d6 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -7398,6 +7398,7 @@ paths: - `id=` - `label=` + - `mode=["replicated"|"global"]` - `name=` tags: ["Service"] /services/create: diff --git a/daemon/cluster/filters.go b/daemon/cluster/filters.go index 88668edaac..554694da2c 100644 --- a/daemon/cluster/filters.go +++ b/daemon/cluster/filters.go @@ -45,22 +45,6 @@ func newListNodesFilters(filter filters.Args) (*swarmapi.ListNodesRequest_Filter return f, nil } -func newListServicesFilters(filter filters.Args) (*swarmapi.ListServicesRequest_Filters, error) { - accepted := map[string]bool{ - "name": true, - "id": true, - "label": true, - } - if err := filter.Validate(accepted); err != nil { - return nil, err - } - return &swarmapi.ListServicesRequest_Filters{ - NamePrefixes: filter.Get("name"), - IDPrefixes: filter.Get("id"), - Labels: runconfigopts.ConvertKVStringsToMap(filter.Get("label")), - }, nil -} - func newListTasksFilters(filter filters.Args, transformFunc func(filters.Args) error) (*swarmapi.ListTasksRequest_Filters, error) { accepted := map[string]bool{ "name": true, diff --git a/daemon/cluster/services.go b/daemon/cluster/services.go index c9d0b93dbb..bdb91ea25b 100644 --- a/daemon/cluster/services.go +++ b/daemon/cluster/services.go @@ -21,6 +21,7 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stdcopy" + runconfigopts "github.com/docker/docker/runconfig/opts" swarmapi "github.com/docker/swarmkit/api" gogotypes "github.com/gogo/protobuf/types" "github.com/pkg/errors" @@ -37,10 +38,25 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv return nil, c.errNoManager(state) } - filters, err := newListServicesFilters(options.Filters) - if err != nil { + // We move the accepted filter check here as "mode" filter + // is processed in the daemon, not in SwarmKit. So it might + // be good to have accepted file check in the same file as + // the filter processing (in the for loop below). + accepted := map[string]bool{ + "name": true, + "id": true, + "label": true, + "mode": true, + } + if err := options.Filters.Validate(accepted); err != nil { return nil, err } + filters := &swarmapi.ListServicesRequest_Filters{ + NamePrefixes: options.Filters.Get("name"), + IDPrefixes: options.Filters.Get("id"), + Labels: runconfigopts.ConvertKVStringsToMap(options.Filters.Get("label")), + } + ctx, cancel := c.getRequestContext() defer cancel() @@ -54,6 +70,19 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv services := []types.Service{} for _, service := range r.Services { + if options.Filters.Include("mode") { + var mode string + switch service.Spec.GetMode().(type) { + case *swarmapi.ServiceSpec_Global: + mode = "global" + case *swarmapi.ServiceSpec_Replicated: + mode = "replicated" + } + + if !options.Filters.ExactMatch("mode", mode) { + continue + } + } services = append(services, convert.ServiceFromGRPC(*service)) } diff --git a/docs/api/version-history.md b/docs/api/version-history.md index 3a92bdbb5d..199876ca91 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -27,6 +27,7 @@ keywords: "API, Docker, rcli, REST, documentation" * `POST /build` now accepts `extrahosts` parameter to specify a host to ip mapping to use during the build. * `POST /services/create` and `POST /services/(id or name)/update` now accept a `rollback` value for `FailureAction`. * `POST /services/create` and `POST /services/(id or name)/update` now accept an optional `RollbackConfig` object which specifies rollback options. +* `GET /services` now supports a `mode` filter to filter services based on the service mode (either `global` or `replicated`). ## v1.27 API changes diff --git a/docs/reference/commandline/service_ls.md b/docs/reference/commandline/service_ls.md index 86273ff1ff..6b07b84053 100644 --- a/docs/reference/commandline/service_ls.md +++ b/docs/reference/commandline/service_ls.md @@ -60,6 +60,7 @@ The currently supported filters are: * [id](service_ls.md#id) * [label](service_ls.md#label) +* [mode](service_ls.md#mode) * [name](service_ls.md#name) #### id @@ -98,6 +99,18 @@ ID NAME MODE REPLICAS IMAGE 74nzcxxjv6fq backend replicated 3/3 redis:3.0.6 ``` +#### mode + +The `mode` filter matches on the mode (either `replicated` or `global`) of a service. + +The following filter matches only `global` services. + +```bash +$ docker service ls --filter mode=global +ID NAME MODE REPLICAS IMAGE +w7y0v2yrn620 top global 1/1 busybox +``` + #### name The `name` filter matches on all or part of a service's name. diff --git a/integration-cli/docker_cli_swarm_test.go b/integration-cli/docker_cli_swarm_test.go index 026891e3ef..a3a287b4c9 100644 --- a/integration-cli/docker_cli_swarm_test.go +++ b/integration-cli/docker_cli_swarm_test.go @@ -1797,3 +1797,34 @@ func (s *DockerSwarmSuite) TestSwarmStopSignal(c *check.C) { c.Assert(err, checker.IsNil, check.Commentf(out)) c.Assert(strings.TrimSpace(out), checker.Equals, "SIGUSR1") } + +func (s *DockerSwarmSuite) TestSwarmServiceLsFilterMode(c *check.C) { + d := s.AddDaemon(c, true, true) + + out, err := d.Cmd("service", "create", "--name", "top1", "busybox", "top") + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") + + out, err = d.Cmd("service", "create", "--name", "top2", "--mode=global", "busybox", "top") + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") + + // make sure task has been deployed. + waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 2) + + out, err = d.Cmd("service", "ls") + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "top1") + c.Assert(out, checker.Contains, "top2") + c.Assert(out, checker.Not(checker.Contains), "localnet") + + out, err = d.Cmd("service", "ls", "--filter", "mode=global") + c.Assert(out, checker.Not(checker.Contains), "top1") + c.Assert(out, checker.Contains, "top2") + c.Assert(err, checker.IsNil, check.Commentf(out)) + + out, err = d.Cmd("service", "ls", "--filter", "mode=replicated") + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "top1") + c.Assert(out, checker.Not(checker.Contains), "top2") +}