mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
API: add "Swarm" header to _ping endpoint
This adds an additional "Swarm" header to the _ping endpoint response, which allows a client to detect if Swarm is enabled on the daemon, without having to call additional endpoints. This change is not versioned in the API, and will be returned irregardless of the API version that is used. Clients should fall back to using other endpoints to get this information if the header is not present. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
0729fbd343
commit
adf4bf772d
6 changed files with 107 additions and 0 deletions
|
@ -38,3 +38,8 @@ type Backend interface {
|
|||
type ClusterBackend interface {
|
||||
Info() swarm.Info
|
||||
}
|
||||
|
||||
// StatusProvider provides methods to get the swarm status of the current node.
|
||||
type StatusProvider interface {
|
||||
Status() string
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
timetypes "github.com/docker/docker/api/types/time"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
|
@ -34,6 +35,9 @@ func (s *systemRouter) pingHandler(ctx context.Context, w http.ResponseWriter, r
|
|||
if bv := builderVersion; bv != "" {
|
||||
w.Header().Set("Builder-Version", string(bv))
|
||||
}
|
||||
|
||||
w.Header().Set("Swarm", s.swarmStatus())
|
||||
|
||||
if r.Method == http.MethodHead {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("Content-Length", "0")
|
||||
|
@ -43,6 +47,15 @@ func (s *systemRouter) pingHandler(ctx context.Context, w http.ResponseWriter, r
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *systemRouter) swarmStatus() string {
|
||||
if s.cluster != nil {
|
||||
if p, ok := s.cluster.(StatusProvider); ok {
|
||||
return p.Status()
|
||||
}
|
||||
}
|
||||
return string(swarm.LocalNodeStateInactive)
|
||||
}
|
||||
|
||||
func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
info := s.backend.SystemInfo()
|
||||
|
||||
|
|
|
@ -8377,6 +8377,13 @@ paths:
|
|||
Docker-Experimental:
|
||||
type: "boolean"
|
||||
description: "If the server is running with experimental mode enabled"
|
||||
Swarm:
|
||||
type: "string"
|
||||
enum: ["inactive", "pending", "error", "locked", "active/worker", "active/manager"]
|
||||
description: |
|
||||
Contains information about Swarm status of the daemon,
|
||||
and if the daemon is acting as a manager or worker node.
|
||||
default: "inactive"
|
||||
Cache-Control:
|
||||
type: "string"
|
||||
default: "no-cache, no-store, must-revalidate"
|
||||
|
@ -8416,6 +8423,13 @@ paths:
|
|||
Docker-Experimental:
|
||||
type: "boolean"
|
||||
description: "If the server is running with experimental mode enabled"
|
||||
Swarm:
|
||||
type: "string"
|
||||
enum: ["inactive", "pending", "error", "locked", "active/worker", "active/manager"]
|
||||
description: |
|
||||
Contains information about Swarm status of the daemon,
|
||||
and if the daemon is acting as a manager or worker node.
|
||||
default: "inactive"
|
||||
Cache-Control:
|
||||
type: "string"
|
||||
default: "no-cache, no-store, must-revalidate"
|
||||
|
|
|
@ -492,6 +492,23 @@ func (c *Cluster) Info() types.Info {
|
|||
return info
|
||||
}
|
||||
|
||||
// Status returns a textual representation of the node's swarm status and role (manager/worker)
|
||||
func (c *Cluster) Status() string {
|
||||
c.mu.RLock()
|
||||
s := c.currentNodeState()
|
||||
c.mu.RUnlock()
|
||||
|
||||
state := string(s.status)
|
||||
if s.status == types.LocalNodeStateActive {
|
||||
if s.IsActiveManager() || s.IsManager() {
|
||||
state += "/manager"
|
||||
} else {
|
||||
state += "/worker"
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func validateAndSanitizeInitRequest(req *types.InitRequest) error {
|
||||
var err error
|
||||
req.ListenAddr, err = validateAddr(req.ListenAddr)
|
||||
|
|
|
@ -50,6 +50,21 @@ keywords: "API, Docker, rcli, REST, documentation"
|
|||
if they are not set.
|
||||
* `GET /info` now omits the `KernelMemory` and `KernelMemoryTCP` if they are not
|
||||
supported by the host or host's configuration (if cgroups v2 are in use).
|
||||
* `GET /_ping` and `HEAD /_ping` now return a `Swarm` header, which allows a
|
||||
client to detect if Swarm is enabled on the daemon, without having to call
|
||||
additional endpoints.
|
||||
This change is not versioned, and affects all API versions if the daemon has
|
||||
this patch. Clients must consider this header "optional", and fall back to
|
||||
using other endpoints to get this information if the header is not present.
|
||||
|
||||
The `Swarm` header can contain one of the following values:
|
||||
|
||||
- "inactive"
|
||||
- "pending"
|
||||
- "error"
|
||||
- "locked"
|
||||
- "active/worker"
|
||||
- "active/manager"
|
||||
|
||||
## v1.41 API changes
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package system // import "github.com/docker/docker/integration/system"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/testutil/daemon"
|
||||
"github.com/docker/docker/testutil/request"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/skip"
|
||||
|
@ -50,6 +53,46 @@ func TestPingHead(t *testing.T) {
|
|||
assert.Check(t, hdr(res, "API-Version") != "")
|
||||
}
|
||||
|
||||
func TestPingSwarmHeader(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRemoteDaemon)
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
||||
|
||||
defer setupTest(t)()
|
||||
d := daemon.New(t)
|
||||
d.Start(t)
|
||||
defer d.Stop(t)
|
||||
client := d.NewClientT(t)
|
||||
defer client.Close()
|
||||
ctx := context.TODO()
|
||||
|
||||
t.Run("before swarm init", func(t *testing.T) {
|
||||
res, _, err := request.Get("/_ping")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, res.StatusCode, http.StatusOK)
|
||||
assert.Equal(t, hdr(res, "Swarm"), "inactive")
|
||||
})
|
||||
|
||||
_, err := client.SwarmInit(ctx, swarm.InitRequest{ListenAddr: "127.0.0.1", AdvertiseAddr: "127.0.0.1:2377"})
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("after swarm init", func(t *testing.T) {
|
||||
res, _, err := request.Get("/_ping", request.Host(d.Sock()))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, res.StatusCode, http.StatusOK)
|
||||
assert.Equal(t, hdr(res, "Swarm"), "active/manager")
|
||||
})
|
||||
|
||||
err = client.SwarmLeave(ctx, true)
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("after swarm leave", func(t *testing.T) {
|
||||
res, _, err := request.Get("/_ping", request.Host(d.Sock()))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, res.StatusCode, http.StatusOK)
|
||||
assert.Equal(t, hdr(res, "Swarm"), "inactive")
|
||||
})
|
||||
}
|
||||
|
||||
func hdr(res *http.Response, name string) string {
|
||||
val, ok := res.Header[http.CanonicalHeaderKey(name)]
|
||||
if !ok || len(val) == 0 {
|
||||
|
|
Loading…
Add table
Reference in a new issue