This parameter controls the order of operations when rolling out an update task. Either the old task is stopped before starting the new one, or the new task is started first, and the running tasks will briefly overlap. This commit adds Rollout to the API, and --update-order / --rollback-order flags to the CLI. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com> Upstream-commit: 9b54994a8ada6ae15a4d2c3b925568e2061200ad Component: engine
459 lines
13 KiB
Go
459 lines
13 KiB
Go
package convert
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
types "github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/pkg/namesgenerator"
|
|
swarmapi "github.com/docker/swarmkit/api"
|
|
gogotypes "github.com/gogo/protobuf/types"
|
|
)
|
|
|
|
// ServiceFromGRPC converts a grpc Service to a Service.
|
|
func ServiceFromGRPC(s swarmapi.Service) types.Service {
|
|
service := types.Service{
|
|
ID: s.ID,
|
|
Spec: *serviceSpecFromGRPC(&s.Spec),
|
|
PreviousSpec: serviceSpecFromGRPC(s.PreviousSpec),
|
|
|
|
Endpoint: endpointFromGRPC(s.Endpoint),
|
|
}
|
|
|
|
// Meta
|
|
service.Version.Index = s.Meta.Version.Index
|
|
service.CreatedAt, _ = gogotypes.TimestampFromProto(s.Meta.CreatedAt)
|
|
service.UpdatedAt, _ = gogotypes.TimestampFromProto(s.Meta.UpdatedAt)
|
|
|
|
// UpdateStatus
|
|
if s.UpdateStatus != nil {
|
|
service.UpdateStatus = &types.UpdateStatus{}
|
|
switch s.UpdateStatus.State {
|
|
case swarmapi.UpdateStatus_UPDATING:
|
|
service.UpdateStatus.State = types.UpdateStateUpdating
|
|
case swarmapi.UpdateStatus_PAUSED:
|
|
service.UpdateStatus.State = types.UpdateStatePaused
|
|
case swarmapi.UpdateStatus_COMPLETED:
|
|
service.UpdateStatus.State = types.UpdateStateCompleted
|
|
case swarmapi.UpdateStatus_ROLLBACK_STARTED:
|
|
service.UpdateStatus.State = types.UpdateStateRollbackStarted
|
|
case swarmapi.UpdateStatus_ROLLBACK_PAUSED:
|
|
service.UpdateStatus.State = types.UpdateStateRollbackPaused
|
|
case swarmapi.UpdateStatus_ROLLBACK_COMPLETED:
|
|
service.UpdateStatus.State = types.UpdateStateRollbackCompleted
|
|
}
|
|
|
|
startedAt, _ := gogotypes.TimestampFromProto(s.UpdateStatus.StartedAt)
|
|
if !startedAt.IsZero() {
|
|
service.UpdateStatus.StartedAt = &startedAt
|
|
}
|
|
|
|
completedAt, _ := gogotypes.TimestampFromProto(s.UpdateStatus.CompletedAt)
|
|
if !completedAt.IsZero() {
|
|
service.UpdateStatus.CompletedAt = &completedAt
|
|
}
|
|
|
|
service.UpdateStatus.Message = s.UpdateStatus.Message
|
|
}
|
|
|
|
return service
|
|
}
|
|
|
|
func serviceSpecFromGRPC(spec *swarmapi.ServiceSpec) *types.ServiceSpec {
|
|
if spec == nil {
|
|
return nil
|
|
}
|
|
|
|
serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
|
|
for _, n := range spec.Networks {
|
|
serviceNetworks = append(serviceNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
|
}
|
|
|
|
convertedSpec := &types.ServiceSpec{
|
|
Annotations: annotationsFromGRPC(spec.Annotations),
|
|
TaskTemplate: taskSpecFromGRPC(spec.Task),
|
|
Networks: serviceNetworks,
|
|
EndpointSpec: endpointSpecFromGRPC(spec.Endpoint),
|
|
}
|
|
|
|
// UpdateConfig
|
|
convertedSpec.UpdateConfig = updateConfigFromGRPC(spec.Update)
|
|
convertedSpec.RollbackConfig = updateConfigFromGRPC(spec.Rollback)
|
|
|
|
// Mode
|
|
switch t := spec.GetMode().(type) {
|
|
case *swarmapi.ServiceSpec_Global:
|
|
convertedSpec.Mode.Global = &types.GlobalService{}
|
|
case *swarmapi.ServiceSpec_Replicated:
|
|
convertedSpec.Mode.Replicated = &types.ReplicatedService{
|
|
Replicas: &t.Replicated.Replicas,
|
|
}
|
|
}
|
|
|
|
return convertedSpec
|
|
}
|
|
|
|
// ServiceSpecToGRPC converts a ServiceSpec to a grpc ServiceSpec.
|
|
func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
|
|
name := s.Name
|
|
if name == "" {
|
|
name = namesgenerator.GetRandomName(0)
|
|
}
|
|
|
|
serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
|
|
for _, n := range s.Networks {
|
|
serviceNetworks = append(serviceNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
|
}
|
|
|
|
taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
|
|
for _, n := range s.TaskTemplate.Networks {
|
|
taskNetworks = append(taskNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
|
}
|
|
|
|
spec := swarmapi.ServiceSpec{
|
|
Annotations: swarmapi.Annotations{
|
|
Name: name,
|
|
Labels: s.Labels,
|
|
},
|
|
Task: swarmapi.TaskSpec{
|
|
Resources: resourcesToGRPC(s.TaskTemplate.Resources),
|
|
LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
|
|
Networks: taskNetworks,
|
|
ForceUpdate: s.TaskTemplate.ForceUpdate,
|
|
},
|
|
Networks: serviceNetworks,
|
|
}
|
|
|
|
containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)
|
|
if err != nil {
|
|
return swarmapi.ServiceSpec{}, err
|
|
}
|
|
spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
|
|
|
|
restartPolicy, err := restartPolicyToGRPC(s.TaskTemplate.RestartPolicy)
|
|
if err != nil {
|
|
return swarmapi.ServiceSpec{}, err
|
|
}
|
|
spec.Task.Restart = restartPolicy
|
|
|
|
if s.TaskTemplate.Placement != nil {
|
|
var preferences []*swarmapi.PlacementPreference
|
|
for _, pref := range s.TaskTemplate.Placement.Preferences {
|
|
if pref.Spread != nil {
|
|
preferences = append(preferences, &swarmapi.PlacementPreference{
|
|
Preference: &swarmapi.PlacementPreference_Spread{
|
|
Spread: &swarmapi.SpreadOver{
|
|
SpreadDescriptor: pref.Spread.SpreadDescriptor,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
spec.Task.Placement = &swarmapi.Placement{
|
|
Constraints: s.TaskTemplate.Placement.Constraints,
|
|
Preferences: preferences,
|
|
}
|
|
}
|
|
|
|
spec.Update, err = updateConfigToGRPC(s.UpdateConfig)
|
|
if err != nil {
|
|
return swarmapi.ServiceSpec{}, err
|
|
}
|
|
spec.Rollback, err = updateConfigToGRPC(s.RollbackConfig)
|
|
if err != nil {
|
|
return swarmapi.ServiceSpec{}, err
|
|
}
|
|
|
|
if s.EndpointSpec != nil {
|
|
if s.EndpointSpec.Mode != "" &&
|
|
s.EndpointSpec.Mode != types.ResolutionModeVIP &&
|
|
s.EndpointSpec.Mode != types.ResolutionModeDNSRR {
|
|
return swarmapi.ServiceSpec{}, fmt.Errorf("invalid resolution mode: %q", s.EndpointSpec.Mode)
|
|
}
|
|
|
|
spec.Endpoint = &swarmapi.EndpointSpec{}
|
|
|
|
spec.Endpoint.Mode = swarmapi.EndpointSpec_ResolutionMode(swarmapi.EndpointSpec_ResolutionMode_value[strings.ToUpper(string(s.EndpointSpec.Mode))])
|
|
|
|
for _, portConfig := range s.EndpointSpec.Ports {
|
|
spec.Endpoint.Ports = append(spec.Endpoint.Ports, &swarmapi.PortConfig{
|
|
Name: portConfig.Name,
|
|
Protocol: swarmapi.PortConfig_Protocol(swarmapi.PortConfig_Protocol_value[strings.ToUpper(string(portConfig.Protocol))]),
|
|
PublishMode: swarmapi.PortConfig_PublishMode(swarmapi.PortConfig_PublishMode_value[strings.ToUpper(string(portConfig.PublishMode))]),
|
|
TargetPort: portConfig.TargetPort,
|
|
PublishedPort: portConfig.PublishedPort,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Mode
|
|
if s.Mode.Global != nil && s.Mode.Replicated != nil {
|
|
return swarmapi.ServiceSpec{}, fmt.Errorf("cannot specify both replicated mode and global mode")
|
|
}
|
|
|
|
if s.Mode.Global != nil {
|
|
spec.Mode = &swarmapi.ServiceSpec_Global{
|
|
Global: &swarmapi.GlobalService{},
|
|
}
|
|
} else if s.Mode.Replicated != nil && s.Mode.Replicated.Replicas != nil {
|
|
spec.Mode = &swarmapi.ServiceSpec_Replicated{
|
|
Replicated: &swarmapi.ReplicatedService{Replicas: *s.Mode.Replicated.Replicas},
|
|
}
|
|
} else {
|
|
spec.Mode = &swarmapi.ServiceSpec_Replicated{
|
|
Replicated: &swarmapi.ReplicatedService{Replicas: 1},
|
|
}
|
|
}
|
|
|
|
return spec, nil
|
|
}
|
|
|
|
func annotationsFromGRPC(ann swarmapi.Annotations) types.Annotations {
|
|
a := types.Annotations{
|
|
Name: ann.Name,
|
|
Labels: ann.Labels,
|
|
}
|
|
|
|
if a.Labels == nil {
|
|
a.Labels = make(map[string]string)
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
func resourcesFromGRPC(res *swarmapi.ResourceRequirements) *types.ResourceRequirements {
|
|
var resources *types.ResourceRequirements
|
|
if res != nil {
|
|
resources = &types.ResourceRequirements{}
|
|
if res.Limits != nil {
|
|
resources.Limits = &types.Resources{
|
|
NanoCPUs: res.Limits.NanoCPUs,
|
|
MemoryBytes: res.Limits.MemoryBytes,
|
|
}
|
|
}
|
|
if res.Reservations != nil {
|
|
resources.Reservations = &types.Resources{
|
|
NanoCPUs: res.Reservations.NanoCPUs,
|
|
MemoryBytes: res.Reservations.MemoryBytes,
|
|
}
|
|
}
|
|
}
|
|
|
|
return resources
|
|
}
|
|
|
|
func resourcesToGRPC(res *types.ResourceRequirements) *swarmapi.ResourceRequirements {
|
|
var reqs *swarmapi.ResourceRequirements
|
|
if res != nil {
|
|
reqs = &swarmapi.ResourceRequirements{}
|
|
if res.Limits != nil {
|
|
reqs.Limits = &swarmapi.Resources{
|
|
NanoCPUs: res.Limits.NanoCPUs,
|
|
MemoryBytes: res.Limits.MemoryBytes,
|
|
}
|
|
}
|
|
if res.Reservations != nil {
|
|
reqs.Reservations = &swarmapi.Resources{
|
|
NanoCPUs: res.Reservations.NanoCPUs,
|
|
MemoryBytes: res.Reservations.MemoryBytes,
|
|
}
|
|
|
|
}
|
|
}
|
|
return reqs
|
|
}
|
|
|
|
func restartPolicyFromGRPC(p *swarmapi.RestartPolicy) *types.RestartPolicy {
|
|
var rp *types.RestartPolicy
|
|
if p != nil {
|
|
rp = &types.RestartPolicy{}
|
|
|
|
switch p.Condition {
|
|
case swarmapi.RestartOnNone:
|
|
rp.Condition = types.RestartPolicyConditionNone
|
|
case swarmapi.RestartOnFailure:
|
|
rp.Condition = types.RestartPolicyConditionOnFailure
|
|
case swarmapi.RestartOnAny:
|
|
rp.Condition = types.RestartPolicyConditionAny
|
|
default:
|
|
rp.Condition = types.RestartPolicyConditionAny
|
|
}
|
|
|
|
if p.Delay != nil {
|
|
delay, _ := gogotypes.DurationFromProto(p.Delay)
|
|
rp.Delay = &delay
|
|
}
|
|
if p.Window != nil {
|
|
window, _ := gogotypes.DurationFromProto(p.Window)
|
|
rp.Window = &window
|
|
}
|
|
|
|
rp.MaxAttempts = &p.MaxAttempts
|
|
}
|
|
return rp
|
|
}
|
|
|
|
func restartPolicyToGRPC(p *types.RestartPolicy) (*swarmapi.RestartPolicy, error) {
|
|
var rp *swarmapi.RestartPolicy
|
|
if p != nil {
|
|
rp = &swarmapi.RestartPolicy{}
|
|
|
|
switch p.Condition {
|
|
case types.RestartPolicyConditionNone:
|
|
rp.Condition = swarmapi.RestartOnNone
|
|
case types.RestartPolicyConditionOnFailure:
|
|
rp.Condition = swarmapi.RestartOnFailure
|
|
case types.RestartPolicyConditionAny:
|
|
rp.Condition = swarmapi.RestartOnAny
|
|
default:
|
|
if string(p.Condition) != "" {
|
|
return nil, fmt.Errorf("invalid RestartCondition: %q", p.Condition)
|
|
}
|
|
rp.Condition = swarmapi.RestartOnAny
|
|
}
|
|
|
|
if p.Delay != nil {
|
|
rp.Delay = gogotypes.DurationProto(*p.Delay)
|
|
}
|
|
if p.Window != nil {
|
|
rp.Window = gogotypes.DurationProto(*p.Window)
|
|
}
|
|
if p.MaxAttempts != nil {
|
|
rp.MaxAttempts = *p.MaxAttempts
|
|
|
|
}
|
|
}
|
|
return rp, nil
|
|
}
|
|
|
|
func placementFromGRPC(p *swarmapi.Placement) *types.Placement {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
r := &types.Placement{
|
|
Constraints: p.Constraints,
|
|
}
|
|
|
|
for _, pref := range p.Preferences {
|
|
if spread := pref.GetSpread(); spread != nil {
|
|
r.Preferences = append(r.Preferences, types.PlacementPreference{
|
|
Spread: &types.SpreadOver{
|
|
SpreadDescriptor: spread.SpreadDescriptor,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func driverFromGRPC(p *swarmapi.Driver) *types.Driver {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
return &types.Driver{
|
|
Name: p.Name,
|
|
Options: p.Options,
|
|
}
|
|
}
|
|
|
|
func driverToGRPC(p *types.Driver) *swarmapi.Driver {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
return &swarmapi.Driver{
|
|
Name: p.Name,
|
|
Options: p.Options,
|
|
}
|
|
}
|
|
|
|
func updateConfigFromGRPC(updateConfig *swarmapi.UpdateConfig) *types.UpdateConfig {
|
|
if updateConfig == nil {
|
|
return nil
|
|
}
|
|
|
|
converted := &types.UpdateConfig{
|
|
Parallelism: updateConfig.Parallelism,
|
|
MaxFailureRatio: updateConfig.MaxFailureRatio,
|
|
}
|
|
|
|
converted.Delay = updateConfig.Delay
|
|
if updateConfig.Monitor != nil {
|
|
converted.Monitor, _ = gogotypes.DurationFromProto(updateConfig.Monitor)
|
|
}
|
|
|
|
switch updateConfig.FailureAction {
|
|
case swarmapi.UpdateConfig_PAUSE:
|
|
converted.FailureAction = types.UpdateFailureActionPause
|
|
case swarmapi.UpdateConfig_CONTINUE:
|
|
converted.FailureAction = types.UpdateFailureActionContinue
|
|
case swarmapi.UpdateConfig_ROLLBACK:
|
|
converted.FailureAction = types.UpdateFailureActionRollback
|
|
}
|
|
|
|
switch updateConfig.Order {
|
|
case swarmapi.UpdateConfig_STOP_FIRST:
|
|
converted.Order = types.UpdateOrderStopFirst
|
|
case swarmapi.UpdateConfig_START_FIRST:
|
|
converted.Order = types.UpdateOrderStartFirst
|
|
}
|
|
|
|
return converted
|
|
}
|
|
|
|
func updateConfigToGRPC(updateConfig *types.UpdateConfig) (*swarmapi.UpdateConfig, error) {
|
|
if updateConfig == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
converted := &swarmapi.UpdateConfig{
|
|
Parallelism: updateConfig.Parallelism,
|
|
Delay: updateConfig.Delay,
|
|
MaxFailureRatio: updateConfig.MaxFailureRatio,
|
|
}
|
|
|
|
switch updateConfig.FailureAction {
|
|
case types.UpdateFailureActionPause, "":
|
|
converted.FailureAction = swarmapi.UpdateConfig_PAUSE
|
|
case types.UpdateFailureActionContinue:
|
|
converted.FailureAction = swarmapi.UpdateConfig_CONTINUE
|
|
case types.UpdateFailureActionRollback:
|
|
converted.FailureAction = swarmapi.UpdateConfig_ROLLBACK
|
|
default:
|
|
return nil, fmt.Errorf("unrecognized update failure action %s", updateConfig.FailureAction)
|
|
}
|
|
if updateConfig.Monitor != 0 {
|
|
converted.Monitor = gogotypes.DurationProto(updateConfig.Monitor)
|
|
}
|
|
|
|
switch updateConfig.Order {
|
|
case types.UpdateOrderStopFirst, "":
|
|
converted.Order = swarmapi.UpdateConfig_STOP_FIRST
|
|
case types.UpdateOrderStartFirst:
|
|
converted.Order = swarmapi.UpdateConfig_START_FIRST
|
|
default:
|
|
return nil, fmt.Errorf("unrecognized update order %s", updateConfig.Order)
|
|
}
|
|
|
|
return converted, nil
|
|
}
|
|
|
|
func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) types.TaskSpec {
|
|
taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(taskSpec.Networks))
|
|
for _, n := range taskSpec.Networks {
|
|
taskNetworks = append(taskNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
|
}
|
|
|
|
return types.TaskSpec{
|
|
ContainerSpec: containerSpecFromGRPC(taskSpec.GetContainer()),
|
|
Resources: resourcesFromGRPC(taskSpec.Resources),
|
|
RestartPolicy: restartPolicyFromGRPC(taskSpec.Restart),
|
|
Placement: placementFromGRPC(taskSpec.Placement),
|
|
LogDriver: driverFromGRPC(taskSpec.LogDriver),
|
|
Networks: taskNetworks,
|
|
ForceUpdate: taskSpec.ForceUpdate,
|
|
}
|
|
}
|