Add api side validation and defaults for init and
join requests.
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit fb3eb1c27ef5520571c599ead8a72b343748db39)
| ... | ... |
@@ -13,16 +13,17 @@ import ( |
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 | 15 |
type initOptions struct {
|
| 16 |
+ swarmOptions |
|
| 16 | 17 |
listenAddr NodeAddrOption |
| 17 |
- autoAccept AutoAcceptOption |
|
| 18 | 18 |
forceNewCluster bool |
| 19 |
- secret string |
|
| 20 | 19 |
} |
| 21 | 20 |
|
| 22 | 21 |
func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 23 | 22 |
opts := initOptions{
|
| 24 | 23 |
listenAddr: NewListenAddrOption(), |
| 25 |
- autoAccept: NewAutoAcceptOption(), |
|
| 24 |
+ swarmOptions: swarmOptions{
|
|
| 25 |
+ autoAccept: NewAutoAcceptOption(), |
|
| 26 |
+ }, |
|
| 26 | 27 |
} |
| 27 | 28 |
|
| 28 | 29 |
cmd := &cobra.Command{
|
| ... | ... |
@@ -36,9 +37,8 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 36 | 36 |
|
| 37 | 37 |
flags := cmd.Flags() |
| 38 | 38 |
flags.Var(&opts.listenAddr, "listen-addr", "Listen address") |
| 39 |
- flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager, or none)") |
|
| 40 |
- flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to accept nodes into cluster") |
|
| 41 | 39 |
flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state.") |
| 40 |
+ addSwarmFlags(flags, &opts.swarmOptions) |
|
| 42 | 41 |
return cmd |
| 43 | 42 |
} |
| 44 | 43 |
|
| ... | ... |
@@ -49,13 +49,9 @@ func runInit(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts initOptions |
| 49 | 49 |
req := swarm.InitRequest{
|
| 50 | 50 |
ListenAddr: opts.listenAddr.String(), |
| 51 | 51 |
ForceNewCluster: opts.forceNewCluster, |
| 52 |
+ Spec: opts.swarmOptions.ToSpec(), |
|
| 52 | 53 |
} |
| 53 | 54 |
|
| 54 |
- if flags.Changed(flagSecret) {
|
|
| 55 |
- req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret) |
|
| 56 |
- } else {
|
|
| 57 |
- req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil) |
|
| 58 |
- } |
|
| 59 | 55 |
nodeID, err := client.SwarmInit(ctx, req) |
| 60 | 56 |
if err != nil {
|
| 61 | 57 |
return err |
| ... | ... |
@@ -2,16 +2,16 @@ package swarm |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
- "net" |
|
| 6 |
- "strconv" |
|
| 7 | 5 |
"strings" |
| 6 |
+ "time" |
|
| 8 | 7 |
|
| 8 |
+ "github.com/docker/docker/opts" |
|
| 9 | 9 |
"github.com/docker/engine-api/types/swarm" |
| 10 |
+ "github.com/spf13/pflag" |
|
| 10 | 11 |
) |
| 11 | 12 |
|
| 12 | 13 |
const ( |
| 13 |
- defaultListenAddr = "0.0.0.0" |
|
| 14 |
- defaultListenPort uint16 = 2377 |
|
| 14 |
+ defaultListenAddr = "0.0.0.0:2377" |
|
| 15 | 15 |
// WORKER constant for worker name |
| 16 | 16 |
WORKER = "WORKER" |
| 17 | 17 |
// MANAGER constant for manager name |
| ... | ... |
@@ -32,10 +32,17 @@ var ( |
| 32 | 32 |
} |
| 33 | 33 |
) |
| 34 | 34 |
|
| 35 |
+type swarmOptions struct {
|
|
| 36 |
+ autoAccept AutoAcceptOption |
|
| 37 |
+ secret string |
|
| 38 |
+ taskHistoryLimit int64 |
|
| 39 |
+ dispatcherHeartbeat time.Duration |
|
| 40 |
+ nodeCertExpiry time.Duration |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 35 | 43 |
// NodeAddrOption is a pflag.Value for listen and remote addresses |
| 36 | 44 |
type NodeAddrOption struct {
|
| 37 | 45 |
addr string |
| 38 |
- port uint16 |
|
| 39 | 46 |
} |
| 40 | 47 |
|
| 41 | 48 |
// String prints the representation of this flag |
| ... | ... |
@@ -45,25 +52,11 @@ func (a *NodeAddrOption) String() string {
|
| 45 | 45 |
|
| 46 | 46 |
// Set the value for this flag |
| 47 | 47 |
func (a *NodeAddrOption) Set(value string) error {
|
| 48 |
- if !strings.Contains(value, ":") {
|
|
| 49 |
- a.addr = value |
|
| 50 |
- return nil |
|
| 51 |
- } |
|
| 52 |
- |
|
| 53 |
- host, port, err := net.SplitHostPort(value) |
|
| 54 |
- if err != nil {
|
|
| 55 |
- return fmt.Errorf("Invalid url, %v", err)
|
|
| 56 |
- } |
|
| 57 |
- |
|
| 58 |
- portInt, err := strconv.ParseUint(port, 10, 16) |
|
| 48 |
+ addr, err := opts.ParseTCPAddr(value, a.addr) |
|
| 59 | 49 |
if err != nil {
|
| 60 |
- return fmt.Errorf("invalid url, %v", err)
|
|
| 61 |
- } |
|
| 62 |
- a.port = uint16(portInt) |
|
| 63 |
- |
|
| 64 |
- if host != "" {
|
|
| 65 |
- a.addr = host |
|
| 50 |
+ return err |
|
| 66 | 51 |
} |
| 52 |
+ a.addr = addr |
|
| 67 | 53 |
return nil |
| 68 | 54 |
} |
| 69 | 55 |
|
| ... | ... |
@@ -74,17 +67,17 @@ func (a *NodeAddrOption) Type() string {
|
| 74 | 74 |
|
| 75 | 75 |
// Value returns the value of this option as addr:port |
| 76 | 76 |
func (a *NodeAddrOption) Value() string {
|
| 77 |
- return net.JoinHostPort(a.addr, strconv.Itoa(int(a.port))) |
|
| 77 |
+ return strings.TrimPrefix(a.addr, "tcp://") |
|
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 | 80 |
// NewNodeAddrOption returns a new node address option |
| 81 |
-func NewNodeAddrOption(host string, port uint16) NodeAddrOption {
|
|
| 82 |
- return NodeAddrOption{addr: host, port: port}
|
|
| 81 |
+func NewNodeAddrOption(addr string) NodeAddrOption {
|
|
| 82 |
+ return NodeAddrOption{addr}
|
|
| 83 | 83 |
} |
| 84 | 84 |
|
| 85 | 85 |
// NewListenAddrOption returns a NodeAddrOption with default values |
| 86 | 86 |
func NewListenAddrOption() NodeAddrOption {
|
| 87 |
- return NewNodeAddrOption(defaultListenAddr, defaultListenPort) |
|
| 87 |
+ return NewNodeAddrOption(defaultListenAddr) |
|
| 88 | 88 |
} |
| 89 | 89 |
|
| 90 | 90 |
// AutoAcceptOption is a value type for auto-accept policy |
| ... | ... |
@@ -148,3 +141,24 @@ func (o *AutoAcceptOption) Policies(secret *string) []swarm.Policy {
|
| 148 | 148 |
func NewAutoAcceptOption() AutoAcceptOption {
|
| 149 | 149 |
return AutoAcceptOption{values: make(map[string]bool)}
|
| 150 | 150 |
} |
| 151 |
+ |
|
| 152 |
+func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
|
|
| 153 |
+ flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)") |
|
| 154 |
+ flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to accept nodes into cluster") |
|
| 155 |
+ flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit") |
|
| 156 |
+ flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period") |
|
| 157 |
+ flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates") |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func (opts *swarmOptions) ToSpec() swarm.Spec {
|
|
| 161 |
+ spec := swarm.Spec{}
|
|
| 162 |
+ if opts.secret != "" {
|
|
| 163 |
+ spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret) |
|
| 164 |
+ } else {
|
|
| 165 |
+ spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil) |
|
| 166 |
+ } |
|
| 167 |
+ spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit |
|
| 168 |
+ spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds()) |
|
| 169 |
+ spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry |
|
| 170 |
+ return spec |
|
| 171 |
+} |
| ... | ... |
@@ -8,31 +8,33 @@ import ( |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 | 10 |
func TestNodeAddrOptionSetHostAndPort(t *testing.T) {
|
| 11 |
- opt := NewNodeAddrOption("old", 123)
|
|
| 11 |
+ opt := NewNodeAddrOption("old:123")
|
|
| 12 | 12 |
addr := "newhost:5555" |
| 13 | 13 |
assert.NilError(t, opt.Set(addr)) |
| 14 |
- assert.Equal(t, opt.addr, "newhost") |
|
| 15 |
- assert.Equal(t, opt.port, uint16(5555)) |
|
| 16 | 14 |
assert.Equal(t, opt.Value(), addr) |
| 17 | 15 |
} |
| 18 | 16 |
|
| 19 | 17 |
func TestNodeAddrOptionSetHostOnly(t *testing.T) {
|
| 20 | 18 |
opt := NewListenAddrOption() |
| 21 | 19 |
assert.NilError(t, opt.Set("newhost"))
|
| 22 |
- assert.Equal(t, opt.addr, "newhost") |
|
| 23 |
- assert.Equal(t, opt.port, defaultListenPort) |
|
| 20 |
+ assert.Equal(t, opt.Value(), "newhost:2377") |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func TestNodeAddrOptionSetHostOnlyIPv6(t *testing.T) {
|
|
| 24 |
+ opt := NewListenAddrOption() |
|
| 25 |
+ assert.NilError(t, opt.Set("::1"))
|
|
| 26 |
+ assert.Equal(t, opt.Value(), "[::1]:2377") |
|
| 24 | 27 |
} |
| 25 | 28 |
|
| 26 | 29 |
func TestNodeAddrOptionSetPortOnly(t *testing.T) {
|
| 27 | 30 |
opt := NewListenAddrOption() |
| 28 | 31 |
assert.NilError(t, opt.Set(":4545"))
|
| 29 |
- assert.Equal(t, opt.addr, defaultListenAddr) |
|
| 30 |
- assert.Equal(t, opt.port, uint16(4545)) |
|
| 32 |
+ assert.Equal(t, opt.Value(), "0.0.0.0:4545") |
|
| 31 | 33 |
} |
| 32 | 34 |
|
| 33 | 35 |
func TestNodeAddrOptionSetInvalidFormat(t *testing.T) {
|
| 34 | 36 |
opt := NewListenAddrOption() |
| 35 |
- assert.Error(t, opt.Set("http://localhost:4545"), "Invalid url")
|
|
| 37 |
+ assert.Error(t, opt.Set("http://localhost:4545"), "Invalid")
|
|
| 36 | 38 |
} |
| 37 | 39 |
|
| 38 | 40 |
func TestAutoAcceptOptionSetWorker(t *testing.T) {
|
| ... | ... |
@@ -2,7 +2,6 @@ package swarm |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
- "time" |
|
| 6 | 5 |
|
| 7 | 6 |
"golang.org/x/net/context" |
| 8 | 7 |
|
| ... | ... |
@@ -13,16 +12,8 @@ import ( |
| 13 | 13 |
"github.com/spf13/pflag" |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
-type updateOptions struct {
|
|
| 17 |
- autoAccept AutoAcceptOption |
|
| 18 |
- secret string |
|
| 19 |
- taskHistoryLimit int64 |
|
| 20 |
- dispatcherHeartbeat time.Duration |
|
| 21 |
- nodeCertExpiry time.Duration |
|
| 22 |
-} |
|
| 23 |
- |
|
| 24 | 16 |
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 25 |
- opts := updateOptions{autoAccept: NewAutoAcceptOption()}
|
|
| 17 |
+ opts := swarmOptions{autoAccept: NewAutoAcceptOption()}
|
|
| 26 | 18 |
|
| 27 | 19 |
cmd := &cobra.Command{
|
| 28 | 20 |
Use: "update", |
| ... | ... |
@@ -33,16 +24,11 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 33 | 33 |
}, |
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 |
- flags := cmd.Flags() |
|
| 37 |
- flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)") |
|
| 38 |
- flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to accept nodes into cluster") |
|
| 39 |
- flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit") |
|
| 40 |
- flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period") |
|
| 41 |
- flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates") |
|
| 36 |
+ addSwarmFlags(cmd.Flags(), &opts) |
|
| 42 | 37 |
return cmd |
| 43 | 38 |
} |
| 44 | 39 |
|
| 45 |
-func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOptions) error {
|
|
| 40 |
+func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts swarmOptions) error {
|
|
| 46 | 41 |
client := dockerCli.Client() |
| 47 | 42 |
ctx := context.Background() |
| 48 | 43 |
|
| ... | ... |
@@ -13,10 +13,12 @@ import ( |
| 13 | 13 |
"google.golang.org/grpc" |
| 14 | 14 |
|
| 15 | 15 |
"github.com/Sirupsen/logrus" |
| 16 |
+ "github.com/docker/distribution/digest" |
|
| 16 | 17 |
"github.com/docker/docker/daemon/cluster/convert" |
| 17 | 18 |
executorpkg "github.com/docker/docker/daemon/cluster/executor" |
| 18 | 19 |
"github.com/docker/docker/daemon/cluster/executor/container" |
| 19 | 20 |
"github.com/docker/docker/errors" |
| 21 |
+ "github.com/docker/docker/opts" |
|
| 20 | 22 |
"github.com/docker/docker/pkg/ioutils" |
| 21 | 23 |
"github.com/docker/docker/runconfig" |
| 22 | 24 |
apitypes "github.com/docker/engine-api/types" |
| ... | ... |
@@ -30,6 +32,7 @@ const swarmDirName = "swarm" |
| 30 | 30 |
const controlSocket = "control.sock" |
| 31 | 31 |
const swarmConnectTimeout = 20 * time.Second |
| 32 | 32 |
const stateFile = "docker-state.json" |
| 33 |
+const defaultAddr = "0.0.0.0:2377" |
|
| 33 | 34 |
|
| 34 | 35 |
const ( |
| 35 | 36 |
initialReconnectDelay = 100 * time.Millisecond |
| ... | ... |
@@ -51,6 +54,26 @@ var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join
|
| 51 | 51 |
// ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached. |
| 52 | 52 |
var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current Swarm status of your node.")
|
| 53 | 53 |
|
| 54 |
+// defaultSpec contains some sane defaults if cluster options are missing on init |
|
| 55 |
+var defaultSpec = types.Spec{
|
|
| 56 |
+ Raft: types.RaftConfig{
|
|
| 57 |
+ SnapshotInterval: 10000, |
|
| 58 |
+ KeepOldSnapshots: 0, |
|
| 59 |
+ LogEntriesForSlowFollowers: 500, |
|
| 60 |
+ HeartbeatTick: 1, |
|
| 61 |
+ ElectionTick: 3, |
|
| 62 |
+ }, |
|
| 63 |
+ CAConfig: types.CAConfig{
|
|
| 64 |
+ NodeCertExpiry: 90 * 24 * time.Hour, |
|
| 65 |
+ }, |
|
| 66 |
+ Dispatcher: types.DispatcherConfig{
|
|
| 67 |
+ HeartbeatPeriod: uint64((5 * time.Second).Nanoseconds()), |
|
| 68 |
+ }, |
|
| 69 |
+ Orchestration: types.OrchestrationConfig{
|
|
| 70 |
+ TaskHistoryRetentionLimit: 10, |
|
| 71 |
+ }, |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 54 | 74 |
type state struct {
|
| 55 | 75 |
ListenAddr string |
| 56 | 76 |
} |
| ... | ... |
@@ -282,6 +305,12 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
| 282 | 282 |
c.conn = nil |
| 283 | 283 |
c.ready = false |
| 284 | 284 |
} |
| 285 |
+ |
|
| 286 |
+ if err := validateAndSanitizeInitRequest(&req); err != nil {
|
|
| 287 |
+ c.Unlock() |
|
| 288 |
+ return "", err |
|
| 289 |
+ } |
|
| 290 |
+ |
|
| 285 | 291 |
// todo: check current state existing |
| 286 | 292 |
n, ctx, err := c.startNewNode(req.ForceNewCluster, req.ListenAddr, "", "", "", false) |
| 287 | 293 |
if err != nil {
|
| ... | ... |
@@ -292,7 +321,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
| 292 | 292 |
|
| 293 | 293 |
select {
|
| 294 | 294 |
case <-n.Ready(): |
| 295 |
- if err := initAcceptancePolicy(n, req.Spec.AcceptancePolicy); err != nil {
|
|
| 295 |
+ if err := initClusterSpec(n, req.Spec); err != nil {
|
|
| 296 | 296 |
return "", err |
| 297 | 297 |
} |
| 298 | 298 |
go c.reconnectOnFailure(ctx) |
| ... | ... |
@@ -319,10 +348,11 @@ func (c *Cluster) Join(req types.JoinRequest) error {
|
| 319 | 319 |
c.Unlock() |
| 320 | 320 |
return errSwarmExists(node) |
| 321 | 321 |
} |
| 322 |
- // todo: check current state existing |
|
| 323 |
- if len(req.RemoteAddrs) == 0 {
|
|
| 324 |
- return fmt.Errorf("at least 1 RemoteAddr is required to join")
|
|
| 322 |
+ if err := validateAndSanitizeJoinRequest(&req); err != nil {
|
|
| 323 |
+ c.Unlock() |
|
| 324 |
+ return err |
|
| 325 | 325 |
} |
| 326 |
+ // todo: check current state existing |
|
| 326 | 327 |
n, ctx, err := c.startNewNode(false, req.ListenAddr, req.RemoteAddrs[0], req.Secret, req.CACertHash, req.Manager) |
| 327 | 328 |
if err != nil {
|
| 328 | 329 |
c.Unlock() |
| ... | ... |
@@ -1030,6 +1060,76 @@ func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, |
| 1030 | 1030 |
return |
| 1031 | 1031 |
} |
| 1032 | 1032 |
|
| 1033 |
+func validateAndSanitizeInitRequest(req *types.InitRequest) error {
|
|
| 1034 |
+ var err error |
|
| 1035 |
+ req.ListenAddr, err = validateAddr(req.ListenAddr) |
|
| 1036 |
+ if err != nil {
|
|
| 1037 |
+ return fmt.Errorf("invalid ListenAddr %q: %v", req.ListenAddr, err)
|
|
| 1038 |
+ } |
|
| 1039 |
+ |
|
| 1040 |
+ spec := &req.Spec |
|
| 1041 |
+ // provide sane defaults instead of erroring |
|
| 1042 |
+ if spec.Name == "" {
|
|
| 1043 |
+ spec.Name = "default" |
|
| 1044 |
+ } |
|
| 1045 |
+ if spec.Raft.SnapshotInterval == 0 {
|
|
| 1046 |
+ spec.Raft.SnapshotInterval = defaultSpec.Raft.SnapshotInterval |
|
| 1047 |
+ } |
|
| 1048 |
+ if spec.Raft.LogEntriesForSlowFollowers == 0 {
|
|
| 1049 |
+ spec.Raft.LogEntriesForSlowFollowers = defaultSpec.Raft.LogEntriesForSlowFollowers |
|
| 1050 |
+ } |
|
| 1051 |
+ if spec.Raft.ElectionTick == 0 {
|
|
| 1052 |
+ spec.Raft.ElectionTick = defaultSpec.Raft.ElectionTick |
|
| 1053 |
+ } |
|
| 1054 |
+ if spec.Raft.HeartbeatTick == 0 {
|
|
| 1055 |
+ spec.Raft.HeartbeatTick = defaultSpec.Raft.HeartbeatTick |
|
| 1056 |
+ } |
|
| 1057 |
+ if spec.Dispatcher.HeartbeatPeriod == 0 {
|
|
| 1058 |
+ spec.Dispatcher.HeartbeatPeriod = defaultSpec.Dispatcher.HeartbeatPeriod |
|
| 1059 |
+ } |
|
| 1060 |
+ if spec.CAConfig.NodeCertExpiry == 0 {
|
|
| 1061 |
+ spec.CAConfig.NodeCertExpiry = defaultSpec.CAConfig.NodeCertExpiry |
|
| 1062 |
+ } |
|
| 1063 |
+ if spec.Orchestration.TaskHistoryRetentionLimit == 0 {
|
|
| 1064 |
+ spec.Orchestration.TaskHistoryRetentionLimit = defaultSpec.Orchestration.TaskHistoryRetentionLimit |
|
| 1065 |
+ } |
|
| 1066 |
+ return nil |
|
| 1067 |
+} |
|
| 1068 |
+ |
|
| 1069 |
+func validateAndSanitizeJoinRequest(req *types.JoinRequest) error {
|
|
| 1070 |
+ var err error |
|
| 1071 |
+ req.ListenAddr, err = validateAddr(req.ListenAddr) |
|
| 1072 |
+ if err != nil {
|
|
| 1073 |
+ return fmt.Errorf("invalid ListenAddr %q: %v", req.ListenAddr, err)
|
|
| 1074 |
+ } |
|
| 1075 |
+ if len(req.RemoteAddrs) == 0 {
|
|
| 1076 |
+ return fmt.Errorf("at least 1 RemoteAddr is required to join")
|
|
| 1077 |
+ } |
|
| 1078 |
+ for i := range req.RemoteAddrs {
|
|
| 1079 |
+ req.RemoteAddrs[i], err = validateAddr(req.RemoteAddrs[i]) |
|
| 1080 |
+ if err != nil {
|
|
| 1081 |
+ return fmt.Errorf("invalid remoteAddr %q: %v", req.RemoteAddrs[i], err)
|
|
| 1082 |
+ } |
|
| 1083 |
+ } |
|
| 1084 |
+ if req.CACertHash != "" {
|
|
| 1085 |
+ if _, err := digest.ParseDigest(req.CACertHash); err != nil {
|
|
| 1086 |
+ return fmt.Errorf("invalid CACertHash %q, %v", req.CACertHash, err)
|
|
| 1087 |
+ } |
|
| 1088 |
+ } |
|
| 1089 |
+ return nil |
|
| 1090 |
+} |
|
| 1091 |
+ |
|
| 1092 |
+func validateAddr(addr string) (string, error) {
|
|
| 1093 |
+ if addr == "" {
|
|
| 1094 |
+ return addr, fmt.Errorf("invalid empty address")
|
|
| 1095 |
+ } |
|
| 1096 |
+ newaddr, err := opts.ParseTCPAddr(addr, defaultAddr) |
|
| 1097 |
+ if err != nil {
|
|
| 1098 |
+ return addr, nil |
|
| 1099 |
+ } |
|
| 1100 |
+ return strings.TrimPrefix(newaddr, "tcp://"), nil |
|
| 1101 |
+} |
|
| 1102 |
+ |
|
| 1033 | 1103 |
func errSwarmExists(node *swarmagent.Node) error {
|
| 1034 | 1104 |
if node.NodeMembership() != swarmapi.NodeMembershipAccepted {
|
| 1035 | 1105 |
return ErrPendingSwarmExists |
| ... | ... |
@@ -1037,7 +1137,7 @@ func errSwarmExists(node *swarmagent.Node) error {
|
| 1037 | 1037 |
return ErrSwarmExists |
| 1038 | 1038 |
} |
| 1039 | 1039 |
|
| 1040 |
-func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.AcceptancePolicy) error {
|
|
| 1040 |
+func initClusterSpec(node *swarmagent.Node, spec types.Spec) error {
|
|
| 1041 | 1041 |
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) |
| 1042 | 1042 |
for conn := range node.ListenControlSocket(ctx) {
|
| 1043 | 1043 |
if ctx.Err() != nil {
|
| ... | ... |
@@ -1061,15 +1161,14 @@ func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.Acceptan |
| 1061 | 1061 |
cluster = lcr.Clusters[0] |
| 1062 | 1062 |
break |
| 1063 | 1063 |
} |
| 1064 |
- spec := &cluster.Spec |
|
| 1065 |
- |
|
| 1066 |
- if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy, nil); err != nil {
|
|
| 1064 |
+ newspec, err := convert.SwarmSpecToGRPCandMerge(spec, &cluster.Spec) |
|
| 1065 |
+ if err != nil {
|
|
| 1067 | 1066 |
return fmt.Errorf("error updating cluster settings: %v", err)
|
| 1068 | 1067 |
} |
| 1069 |
- _, err := client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
|
|
| 1068 |
+ _, err = client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
|
|
| 1070 | 1069 |
ClusterID: cluster.ID, |
| 1071 | 1070 |
ClusterVersion: &cluster.Meta.Version, |
| 1072 |
- Spec: spec, |
|
| 1071 |
+ Spec: &newspec, |
|
| 1073 | 1072 |
}) |
| 1074 | 1073 |
if err != nil {
|
| 1075 | 1074 |
return fmt.Errorf("error updating cluster settings: %v", err)
|
| ... | ... |
@@ -656,6 +656,24 @@ loop0: |
| 656 | 656 |
} |
| 657 | 657 |
} |
| 658 | 658 |
|
| 659 |
+func (s *DockerSwarmSuite) TestApiSwarmInvalidAddress(c *check.C) {
|
|
| 660 |
+ d := s.AddDaemon(c, false, false) |
|
| 661 |
+ req := swarm.InitRequest{
|
|
| 662 |
+ ListenAddr: "", |
|
| 663 |
+ } |
|
| 664 |
+ status, _, err := d.SockRequest("POST", "/swarm/init", req)
|
|
| 665 |
+ c.Assert(err, checker.IsNil) |
|
| 666 |
+ c.Assert(status, checker.Equals, http.StatusInternalServerError) |
|
| 667 |
+ |
|
| 668 |
+ req2 := swarm.JoinRequest{
|
|
| 669 |
+ ListenAddr: "0.0.0.0:2377", |
|
| 670 |
+ RemoteAddrs: []string{""},
|
|
| 671 |
+ } |
|
| 672 |
+ status, _, err = d.SockRequest("POST", "/swarm/join", req2)
|
|
| 673 |
+ c.Assert(err, checker.IsNil) |
|
| 674 |
+ c.Assert(status, checker.Equals, http.StatusInternalServerError) |
|
| 675 |
+} |
|
| 676 |
+ |
|
| 659 | 677 |
func simpleTestService(s *swarm.Service) {
|
| 660 | 678 |
var ureplicas uint64 |
| 661 | 679 |
ureplicas = 1 |
| ... | ... |
@@ -74,3 +74,63 @@ func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) {
|
| 74 | 74 |
spec = getSpec() |
| 75 | 75 |
c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) |
| 76 | 76 |
} |
| 77 |
+ |
|
| 78 |
+func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
|
|
| 79 |
+ d := s.AddDaemon(c, false, false) |
|
| 80 |
+ |
|
| 81 |
+ getSpec := func() swarm.Spec {
|
|
| 82 |
+ out, err := d.Cmd("swarm", "inspect")
|
|
| 83 |
+ c.Assert(err, checker.IsNil) |
|
| 84 |
+ var sw []swarm.Swarm |
|
| 85 |
+ c.Assert(json.Unmarshal([]byte(out), &sw), checker.IsNil) |
|
| 86 |
+ c.Assert(len(sw), checker.Equals, 1) |
|
| 87 |
+ return sw[0].Spec |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ out, err := d.Cmd("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", "--auto-accept", "manager", "--auto-accept", "worker", "--secret", "foo")
|
|
| 91 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
|
| 92 |
+ |
|
| 93 |
+ spec := getSpec() |
|
| 94 |
+ c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) |
|
| 95 |
+ c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, uint64(11*time.Second)) |
|
| 96 |
+ |
|
| 97 |
+ c.Assert(spec.AcceptancePolicy.Policies, checker.HasLen, 2) |
|
| 98 |
+ |
|
| 99 |
+ for _, p := range spec.AcceptancePolicy.Policies {
|
|
| 100 |
+ c.Assert(p.Autoaccept, checker.Equals, true) |
|
| 101 |
+ c.Assert(p.Secret, checker.NotNil) |
|
| 102 |
+ c.Assert(*p.Secret, checker.Not(checker.Equals), "") |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ c.Assert(d.Leave(true), checker.IsNil) |
|
| 106 |
+ |
|
| 107 |
+ out, err = d.Cmd("swarm", "init", "--auto-accept", "none")
|
|
| 108 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
|
| 109 |
+ |
|
| 110 |
+ spec = getSpec() |
|
| 111 |
+ c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 90*24*time.Hour) |
|
| 112 |
+ c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, uint64(5*time.Second)) |
|
| 113 |
+ |
|
| 114 |
+ c.Assert(spec.AcceptancePolicy.Policies, checker.HasLen, 2) |
|
| 115 |
+ |
|
| 116 |
+ for _, p := range spec.AcceptancePolicy.Policies {
|
|
| 117 |
+ c.Assert(p.Autoaccept, checker.Equals, false) |
|
| 118 |
+ c.Assert(p.Secret, checker.IsNil) |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func (s *DockerSwarmSuite) TestSwarmInitIPv6(c *check.C) {
|
|
| 124 |
+ testRequires(c, IPv6) |
|
| 125 |
+ d1 := s.AddDaemon(c, false, false) |
|
| 126 |
+ out, err := d1.Cmd("swarm", "init", "--listen-addr", "::1")
|
|
| 127 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
|
| 128 |
+ |
|
| 129 |
+ d2 := s.AddDaemon(c, false, false) |
|
| 130 |
+ out, err = d2.Cmd("swarm", "join", "::1")
|
|
| 131 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
|
| 132 |
+ |
|
| 133 |
+ out, err = d2.Cmd("info")
|
|
| 134 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
|
| 135 |
+ c.Assert(out, checker.Contains, "Swarm: active") |
|
| 136 |
+} |
| ... | ... |
@@ -70,7 +70,7 @@ func parseDockerDaemonHost(addr string) (string, error) {
|
| 70 | 70 |
|
| 71 | 71 |
switch addrParts[0] {
|
| 72 | 72 |
case "tcp": |
| 73 |
- return parseTCPAddr(addrParts[1], DefaultTCPHost) |
|
| 73 |
+ return ParseTCPAddr(addrParts[1], DefaultTCPHost) |
|
| 74 | 74 |
case "unix": |
| 75 | 75 |
return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
|
| 76 | 76 |
case "npipe": |
| ... | ... |
@@ -97,12 +97,12 @@ func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
|
| 97 | 97 |
return fmt.Sprintf("%s://%s", proto, addr), nil
|
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 |
-// parseTCPAddr parses and validates that the specified address is a valid TCP |
|
| 100 |
+// ParseTCPAddr parses and validates that the specified address is a valid TCP |
|
| 101 | 101 |
// address. It returns a formatted TCP address, either using the address parsed |
| 102 | 102 |
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. |
| 103 | 103 |
// tryAddr is expected to have already been Trim()'d |
| 104 | 104 |
// defaultAddr must be in the full `tcp://host:port` form |
| 105 |
-func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
|
|
| 105 |
+func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
|
|
| 106 | 106 |
if tryAddr == "" || tryAddr == "tcp://" {
|
| 107 | 107 |
return defaultAddr, nil |
| 108 | 108 |
} |
| ... | ... |
@@ -127,9 +127,12 @@ func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
|
| 127 | 127 |
if err != nil {
|
| 128 | 128 |
return "", err |
| 129 | 129 |
} |
| 130 |
- |
|
| 131 | 130 |
host, port, err := net.SplitHostPort(u.Host) |
| 132 | 131 |
if err != nil {
|
| 132 |
+ // try port addition once |
|
| 133 |
+ host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort)) |
|
| 134 |
+ } |
|
| 135 |
+ if err != nil {
|
|
| 133 | 136 |
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
|
| 134 | 137 |
} |
| 135 | 138 |
|
| ... | ... |
@@ -130,12 +130,12 @@ func TestParseTCP(t *testing.T) {
|
| 130 | 130 |
"localhost:5555/path": "tcp://localhost:5555/path", |
| 131 | 131 |
} |
| 132 | 132 |
for invalidAddr, expectedError := range invalids {
|
| 133 |
- if addr, err := parseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError {
|
|
| 133 |
+ if addr, err := ParseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError {
|
|
| 134 | 134 |
t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr)
|
| 135 | 135 |
} |
| 136 | 136 |
} |
| 137 | 137 |
for validAddr, expectedAddr := range valids {
|
| 138 |
- if addr, err := parseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr {
|
|
| 138 |
+ if addr, err := ParseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr {
|
|
| 139 | 139 |
t.Errorf("%v -> expected %v, got %v and addr %v", validAddr, expectedAddr, err, addr)
|
| 140 | 140 |
} |
| 141 | 141 |
} |