Przeglądaj źródła

Added support for maximum replicas per node to services

Signed-off-by: Olli Janatuinen <olli.janatuinen@gmail.com>
Olli Janatuinen 6 lat temu
rodzic
commit
153171e9dd

+ 12 - 0
api/server/router/swarm/cluster_routes.go

@@ -219,6 +219,12 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
 				// API version 1.40
 				service.TaskTemplate.ContainerSpec.Sysctls = nil
 			}
+
+			if service.TaskTemplate.Placement != nil {
+				// MaxReplicas for docker swarm services weren't supported before
+				// API version 1.40
+				service.TaskTemplate.Placement.MaxReplicas = 0
+			}
 		}
 	}
 
@@ -265,6 +271,12 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
 				// API version 1.40
 				service.TaskTemplate.ContainerSpec.Sysctls = nil
 			}
+
+			if service.TaskTemplate.Placement != nil {
+				// MaxReplicas for docker swarm services weren't supported before
+				// API version 1.40
+				service.TaskTemplate.Placement.MaxReplicas = 0
+			}
 		}
 	}
 

+ 5 - 0
api/swagger.yaml

@@ -2878,6 +2878,11 @@ definitions:
                   SpreadDescriptor: "node.labels.datacenter"
               - Spread:
                   SpreadDescriptor: "node.labels.rack"
+          MaxReplicas:
+            description: "Maximum number of replicas for per node (default value is 0, which is unlimited)"
+            type: "integer"
+            format: "int64"
+            default: 0
           Platforms:
             description: |
               Platforms stores all the platforms that the service's image can

+ 1 - 0
api/types/swarm/task.go

@@ -127,6 +127,7 @@ type ResourceRequirements struct {
 type Placement struct {
 	Constraints []string              `json:",omitempty"`
 	Preferences []PlacementPreference `json:",omitempty"`
+	MaxReplicas uint64                `json:",omitempty"`
 
 	// Platforms stores all the platforms that the image can run on.
 	// This field is used in the platform filter for scheduling. If empty,

+ 2 - 0
daemon/cluster/convert/service.go

@@ -246,6 +246,7 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
 		spec.Task.Placement = &swarmapi.Placement{
 			Constraints: s.TaskTemplate.Placement.Constraints,
 			Preferences: preferences,
+			MaxReplicas: s.TaskTemplate.Placement.MaxReplicas,
 			Platforms:   platforms,
 		}
 	}
@@ -472,6 +473,7 @@ func placementFromGRPC(p *swarmapi.Placement) *types.Placement {
 	}
 	r := &types.Placement{
 		Constraints: p.Constraints,
+		MaxReplicas: p.MaxReplicas,
 	}
 
 	for _, pref := range p.Preferences {

+ 4 - 0
docs/api/version-history.md

@@ -33,6 +33,10 @@ keywords: "API, Docker, rcli, REST, documentation"
 * `GET /info` now returns information about `DataPathPort` that is currently used in swarm
 * `GET /swarm` endpoint now returns DataPathPort info
 * `POST /containers/create` now takes `KernelMemoryTCP` field to set hard limit for kernel TCP buffer memory.
+* `GET /service` now  returns `MaxReplicas` as part of the `Placement`.
+* `GET /service/{id}` now  returns `MaxReplicas` as part of the `Placement`.
+* `POST /service/create` and `POST /services/(id or name)/update` now take the field `MaxReplicas`
+  as part of the service `Placement`, allowing to specify maximum replicas per node for the service.
 
 ## V1.39 API changes
 

+ 14 - 0
integration/internal/swarm/service.go

@@ -141,6 +141,14 @@ func ServiceWithReplicas(n uint64) ServiceSpecOpt {
 	}
 }
 
+// ServiceWithMaxReplicas sets the max replicas for the service
+func ServiceWithMaxReplicas(n uint64) ServiceSpecOpt {
+	return func(spec *swarmtypes.ServiceSpec) {
+		ensurePlacement(spec)
+		spec.TaskTemplate.Placement.MaxReplicas = n
+	}
+}
+
 // ServiceWithName sets the name of the service
 func ServiceWithName(name string) ServiceSpecOpt {
 	return func(spec *swarmtypes.ServiceSpec) {
@@ -210,3 +218,9 @@ func ensureContainerSpec(spec *swarmtypes.ServiceSpec) {
 		spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{}
 	}
 }
+
+func ensurePlacement(spec *swarmtypes.ServiceSpec) {
+	if spec.TaskTemplate.Placement == nil {
+		spec.TaskTemplate.Placement = &swarmtypes.Placement{}
+	}
+}

+ 20 - 0
integration/service/create_test.go

@@ -153,6 +153,26 @@ func TestCreateServiceConflict(t *testing.T) {
 	assert.Check(t, is.Contains(string(buf), "service "+serviceName+" already exists"))
 }
 
+func TestCreateServiceMaxReplicas(t *testing.T) {
+	defer setupTest(t)()
+	d := swarm.NewSwarm(t, testEnv)
+	defer d.Stop(t)
+	client := d.NewClientT(t)
+	defer client.Close()
+
+	var maxReplicas uint64 = 2
+	serviceSpec := []swarm.ServiceSpecOpt{
+		swarm.ServiceWithReplicas(maxReplicas),
+		swarm.ServiceWithMaxReplicas(maxReplicas),
+	}
+
+	serviceID := swarm.CreateService(t, d, serviceSpec...)
+	poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, maxReplicas), swarm.ServicePoll)
+
+	_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
+	assert.NilError(t, err)
+}
+
 func TestCreateWithDuplicateNetworkNames(t *testing.T) {
 	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
 	defer setupTest(t)()