Browse code

Let swarmkit handle cluster defaults in `swarm init` if not specified

This fix tries to address the issue raised in 24958 where previously
`docker swarm init` will automatically fill in all the default value
(instead of letting swarmkit to handle the default).

This fix update the `swarm init` so that initial value are passed only
when a flag change has been detected.

This fix fixes 24958.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/08/26 13:08:53
Showing 7 changed files
... ...
@@ -39,7 +39,7 @@ type Spec struct {
39 39
 type OrchestrationConfig struct {
40 40
 	// TaskHistoryRetentionLimit is the number of historic tasks to keep per instance or
41 41
 	// node. If negative, never remove completed or failed tasks.
42
-	TaskHistoryRetentionLimit int64 `json:",omitempty"`
42
+	TaskHistoryRetentionLimit *int64 `json:",omitempty"`
43 43
 }
44 44
 
45 45
 // TaskDefaults parameterizes cluster-level task creation with default values.
... ...
@@ -59,7 +59,7 @@ func runInit(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts initOption
59 59
 		ListenAddr:      opts.listenAddr.String(),
60 60
 		AdvertiseAddr:   opts.advertiseAddr,
61 61
 		ForceNewCluster: opts.forceNewCluster,
62
-		Spec:            opts.swarmOptions.ToSpec(),
62
+		Spec:            opts.swarmOptions.ToSpec(flags),
63 63
 	}
64 64
 
65 65
 	nodeID, err := client.SwarmInit(ctx, req)
... ...
@@ -169,11 +169,20 @@ func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
169 169
 	flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
170 170
 }
171 171
 
172
-func (opts *swarmOptions) ToSpec() swarm.Spec {
172
+func (opts *swarmOptions) ToSpec(flags *pflag.FlagSet) swarm.Spec {
173 173
 	spec := swarm.Spec{}
174
-	spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
175
-	spec.Dispatcher.HeartbeatPeriod = opts.dispatcherHeartbeat
176
-	spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
177
-	spec.CAConfig.ExternalCAs = opts.externalCA.Value()
174
+
175
+	if flags.Changed(flagTaskHistoryLimit) {
176
+		spec.Orchestration.TaskHistoryRetentionLimit = &opts.taskHistoryLimit
177
+	}
178
+	if flags.Changed(flagDispatcherHeartbeat) {
179
+		spec.Dispatcher.HeartbeatPeriod = opts.dispatcherHeartbeat
180
+	}
181
+	if flags.Changed(flagCertExpiry) {
182
+		spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
183
+	}
184
+	if flags.Changed(flagExternalCA) {
185
+		spec.CAConfig.ExternalCAs = opts.externalCA.Value()
186
+	}
178 187
 	return spec
179 188
 }
... ...
@@ -58,7 +58,8 @@ func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
58 58
 	spec := &swarm.Spec
59 59
 
60 60
 	if flags.Changed(flagTaskHistoryLimit) {
61
-		spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64(flagTaskHistoryLimit)
61
+		taskHistoryRetentionLimit, _ := flags.GetInt64(flagTaskHistoryLimit)
62
+		spec.Orchestration.TaskHistoryRetentionLimit = &taskHistoryRetentionLimit
62 63
 	}
63 64
 
64 65
 	if flags.Changed(flagDispatcherHeartbeat) {
... ...
@@ -107,7 +107,11 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
107 107
 			fmt.Fprintf(dockerCli.Out(), " Managers: %d\n", info.Swarm.Managers)
108 108
 			fmt.Fprintf(dockerCli.Out(), " Nodes: %d\n", info.Swarm.Nodes)
109 109
 			fmt.Fprintf(dockerCli.Out(), " Orchestration:\n")
110
-			fmt.Fprintf(dockerCli.Out(), "  Task History Retention Limit: %d\n", info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit)
110
+			taskHistoryRetentionLimit := int64(0)
111
+			if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil {
112
+				taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit
113
+			}
114
+			fmt.Fprintf(dockerCli.Out(), "  Task History Retention Limit: %d\n", taskHistoryRetentionLimit)
111 115
 			fmt.Fprintf(dockerCli.Out(), " Raft:\n")
112 116
 			fmt.Fprintf(dockerCli.Out(), "  Snapshot Interval: %d\n", info.Swarm.Cluster.Spec.Raft.SnapshotInterval)
113 117
 			fmt.Fprintf(dockerCli.Out(), "  Heartbeat Tick: %d\n", info.Swarm.Cluster.Spec.Raft.HeartbeatTick)
... ...
@@ -55,26 +55,6 @@ var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join
55 55
 // ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
56 56
 var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. The attempt to join the swarm will continue in the background. Use the \"docker info\" command to see the current swarm status of your node.")
57 57
 
58
-// defaultSpec contains some sane defaults if cluster options are missing on init
59
-var defaultSpec = types.Spec{
60
-	Raft: types.RaftConfig{
61
-		SnapshotInterval:           10000,
62
-		KeepOldSnapshots:           0,
63
-		LogEntriesForSlowFollowers: 500,
64
-		HeartbeatTick:              1,
65
-		ElectionTick:               3,
66
-	},
67
-	CAConfig: types.CAConfig{
68
-		NodeCertExpiry: 90 * 24 * time.Hour,
69
-	},
70
-	Dispatcher: types.DispatcherConfig{
71
-		HeartbeatPeriod: 5 * time.Second,
72
-	},
73
-	Orchestration: types.OrchestrationConfig{
74
-		TaskHistoryRetentionLimit: 10,
75
-	},
76
-}
77
-
78 58
 type state struct {
79 59
 	// LocalAddr is this machine's local IP or hostname, if specified.
80 60
 	LocalAddr string
... ...
@@ -676,7 +656,10 @@ func (c *Cluster) Update(version uint64, spec types.Spec, flags types.UpdateFlag
676 676
 		return err
677 677
 	}
678 678
 
679
-	swarmSpec, err := convert.SwarmSpecToGRPC(spec)
679
+	// In update, client should provide the complete spec of the swarm, including
680
+	// Name and Labels. If a field is specified with 0 or nil, then the default value
681
+	// will be used to swarmkit.
682
+	clusterSpec, err := convert.SwarmSpecToGRPC(spec)
680 683
 	if err != nil {
681 684
 		return err
682 685
 	}
... ...
@@ -685,7 +668,7 @@ func (c *Cluster) Update(version uint64, spec types.Spec, flags types.UpdateFlag
685 685
 		ctx,
686 686
 		&swarmapi.UpdateClusterRequest{
687 687
 			ClusterID: swarm.ID,
688
-			Spec:      &swarmSpec,
688
+			Spec:      &clusterSpec,
689 689
 			ClusterVersion: &swarmapi.Version{
690 690
 				Index: version,
691 691
 			},
... ...
@@ -1517,32 +1500,6 @@ func validateAndSanitizeInitRequest(req *types.InitRequest) error {
1517 1517
 		return fmt.Errorf("invalid ListenAddr %q: %v", req.ListenAddr, err)
1518 1518
 	}
1519 1519
 
1520
-	spec := &req.Spec
1521
-	// provide sane defaults instead of erroring
1522
-	if spec.Name == "" {
1523
-		spec.Name = "default"
1524
-	}
1525
-	if spec.Raft.SnapshotInterval == 0 {
1526
-		spec.Raft.SnapshotInterval = defaultSpec.Raft.SnapshotInterval
1527
-	}
1528
-	if spec.Raft.LogEntriesForSlowFollowers == 0 {
1529
-		spec.Raft.LogEntriesForSlowFollowers = defaultSpec.Raft.LogEntriesForSlowFollowers
1530
-	}
1531
-	if spec.Raft.ElectionTick == 0 {
1532
-		spec.Raft.ElectionTick = defaultSpec.Raft.ElectionTick
1533
-	}
1534
-	if spec.Raft.HeartbeatTick == 0 {
1535
-		spec.Raft.HeartbeatTick = defaultSpec.Raft.HeartbeatTick
1536
-	}
1537
-	if spec.Dispatcher.HeartbeatPeriod == 0 {
1538
-		spec.Dispatcher.HeartbeatPeriod = defaultSpec.Dispatcher.HeartbeatPeriod
1539
-	}
1540
-	if spec.CAConfig.NodeCertExpiry == 0 {
1541
-		spec.CAConfig.NodeCertExpiry = defaultSpec.CAConfig.NodeCertExpiry
1542
-	}
1543
-	if spec.Orchestration.TaskHistoryRetentionLimit == 0 {
1544
-		spec.Orchestration.TaskHistoryRetentionLimit = defaultSpec.Orchestration.TaskHistoryRetentionLimit
1545
-	}
1546 1520
 	return nil
1547 1521
 }
1548 1522
 
... ...
@@ -1599,14 +1556,20 @@ func initClusterSpec(node *node, spec types.Spec) error {
1599 1599
 				cluster = lcr.Clusters[0]
1600 1600
 				break
1601 1601
 			}
1602
-			newspec, err := convert.SwarmSpecToGRPC(spec)
1602
+			// In init, we take the initial default values from swarmkit, and merge
1603
+			// any non nil or 0 value from spec to GRPC spec. This will leave the
1604
+			// default value alone.
1605
+			// Note that this is different from Update(), as in Update() we expect
1606
+			// user to specify the complete spec of the cluster (as they already know
1607
+			// the existing one and knows which field to update)
1608
+			clusterSpec, err := convert.MergeSwarmSpecToGRPC(spec, cluster.Spec)
1603 1609
 			if err != nil {
1604 1610
 				return fmt.Errorf("error updating cluster settings: %v", err)
1605 1611
 			}
1606 1612
 			_, err = client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
1607 1613
 				ClusterID:      cluster.ID,
1608 1614
 				ClusterVersion: &cluster.Meta.Version,
1609
-				Spec:           &newspec,
1615
+				Spec:           &clusterSpec,
1610 1616
 			})
1611 1617
 			if err != nil {
1612 1618
 				return fmt.Errorf("error updating cluster settings: %v", err)
... ...
@@ -17,7 +17,7 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
17 17
 			ID: c.ID,
18 18
 			Spec: types.Spec{
19 19
 				Orchestration: types.OrchestrationConfig{
20
-					TaskHistoryRetentionLimit: c.Spec.Orchestration.TaskHistoryRetentionLimit,
20
+					TaskHistoryRetentionLimit: &c.Spec.Orchestration.TaskHistoryRetentionLimit,
21 21
 				},
22 22
 				Raft: types.RaftConfig{
23 23
 					SnapshotInterval:           c.Spec.Raft.SnapshotInterval,
... ...
@@ -61,27 +61,44 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
61 61
 
62 62
 // SwarmSpecToGRPC converts a Spec to a grpc ClusterSpec.
63 63
 func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
64
-	spec := swarmapi.ClusterSpec{
65
-		Annotations: swarmapi.Annotations{
66
-			Name:   s.Name,
67
-			Labels: s.Labels,
68
-		},
69
-		Orchestration: swarmapi.OrchestrationConfig{
70
-			TaskHistoryRetentionLimit: s.Orchestration.TaskHistoryRetentionLimit,
71
-		},
72
-		Raft: swarmapi.RaftConfig{
73
-			SnapshotInterval:           s.Raft.SnapshotInterval,
74
-			KeepOldSnapshots:           s.Raft.KeepOldSnapshots,
75
-			LogEntriesForSlowFollowers: s.Raft.LogEntriesForSlowFollowers,
76
-			HeartbeatTick:              uint32(s.Raft.HeartbeatTick),
77
-			ElectionTick:               uint32(s.Raft.ElectionTick),
78
-		},
79
-		Dispatcher: swarmapi.DispatcherConfig{
80
-			HeartbeatPeriod: ptypes.DurationProto(time.Duration(s.Dispatcher.HeartbeatPeriod)),
81
-		},
82
-		CAConfig: swarmapi.CAConfig{
83
-			NodeCertExpiry: ptypes.DurationProto(s.CAConfig.NodeCertExpiry),
84
-		},
64
+	return MergeSwarmSpecToGRPC(s, swarmapi.ClusterSpec{})
65
+}
66
+
67
+// MergeSwarmSpecToGRPC merges a Spec with an initial grpc ClusterSpec
68
+func MergeSwarmSpecToGRPC(s types.Spec, spec swarmapi.ClusterSpec) (swarmapi.ClusterSpec, error) {
69
+	// We take the initSpec (either created from scratch, or returned by swarmkit),
70
+	// and will only change the value if the one taken from types.Spec is not nil or 0.
71
+	// In other words, if the value taken from types.Spec is nil or 0, we will maintain the status quo.
72
+	if s.Annotations.Name != "" {
73
+		spec.Annotations.Name = s.Annotations.Name
74
+	}
75
+	if len(s.Annotations.Labels) != 0 {
76
+		spec.Annotations.Labels = s.Annotations.Labels
77
+	}
78
+
79
+	if s.Orchestration.TaskHistoryRetentionLimit != nil {
80
+		spec.Orchestration.TaskHistoryRetentionLimit = *s.Orchestration.TaskHistoryRetentionLimit
81
+	}
82
+	if s.Raft.SnapshotInterval != 0 {
83
+		spec.Raft.SnapshotInterval = s.Raft.SnapshotInterval
84
+	}
85
+	if s.Raft.KeepOldSnapshots != 0 {
86
+		spec.Raft.KeepOldSnapshots = s.Raft.KeepOldSnapshots
87
+	}
88
+	if s.Raft.LogEntriesForSlowFollowers != 0 {
89
+		spec.Raft.LogEntriesForSlowFollowers = s.Raft.LogEntriesForSlowFollowers
90
+	}
91
+	if s.Raft.HeartbeatTick != 0 {
92
+		spec.Raft.HeartbeatTick = uint32(s.Raft.HeartbeatTick)
93
+	}
94
+	if s.Raft.ElectionTick != 0 {
95
+		spec.Raft.ElectionTick = uint32(s.Raft.ElectionTick)
96
+	}
97
+	if s.Dispatcher.HeartbeatPeriod != 0 {
98
+		spec.Dispatcher.HeartbeatPeriod = ptypes.DurationProto(time.Duration(s.Dispatcher.HeartbeatPeriod))
99
+	}
100
+	if s.CAConfig.NodeCertExpiry != 0 {
101
+		spec.CAConfig.NodeCertExpiry = ptypes.DurationProto(s.CAConfig.NodeCertExpiry)
85 102
 	}
86 103
 
87 104
 	for _, ca := range s.CAConfig.ExternalCAs {