github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/controlapi/service.go (about) 1 package controlapi 2 3 import ( 4 "context" 5 "errors" 6 "reflect" 7 "strings" 8 "time" 9 10 "github.com/docker/distribution/reference" 11 "github.com/docker/swarmkit/api" 12 "github.com/docker/swarmkit/api/defaults" 13 "github.com/docker/swarmkit/api/genericresource" 14 "github.com/docker/swarmkit/api/naming" 15 "github.com/docker/swarmkit/identity" 16 "github.com/docker/swarmkit/manager/allocator" 17 "github.com/docker/swarmkit/manager/constraint" 18 "github.com/docker/swarmkit/manager/state/store" 19 "github.com/docker/swarmkit/protobuf/ptypes" 20 "github.com/docker/swarmkit/template" 21 gogotypes "github.com/gogo/protobuf/types" 22 "google.golang.org/grpc/codes" 23 "google.golang.org/grpc/status" 24 ) 25 26 var ( 27 errNetworkUpdateNotSupported = errors.New("networks must be migrated to TaskSpec before being changed") 28 errRenameNotSupported = errors.New("renaming services is not supported") 29 errModeChangeNotAllowed = errors.New("service mode change is not allowed") 30 ) 31 32 const minimumDuration = 1 * time.Millisecond 33 34 func validateResources(r *api.Resources) error { 35 if r == nil { 36 return nil 37 } 38 39 if r.NanoCPUs != 0 && r.NanoCPUs < 1e6 { 40 return status.Errorf(codes.InvalidArgument, "invalid cpu value %g: Must be at least %g", float64(r.NanoCPUs)/1e9, 1e6/1e9) 41 } 42 43 if r.MemoryBytes != 0 && r.MemoryBytes < 4*1024*1024 { 44 return status.Errorf(codes.InvalidArgument, "invalid memory value %d: Must be at least 4MiB", r.MemoryBytes) 45 } 46 if err := genericresource.ValidateTask(r); err != nil { 47 return nil 48 } 49 return nil 50 } 51 52 func validateResourceRequirements(r *api.ResourceRequirements) error { 53 if r == nil { 54 return nil 55 } 56 if err := validateResources(r.Limits); err != nil { 57 return err 58 } 59 return validateResources(r.Reservations) 60 } 61 62 func validateRestartPolicy(rp *api.RestartPolicy) error { 63 if rp == nil { 64 return nil 65 } 66 67 if rp.Delay != nil { 68 delay, err := gogotypes.DurationFromProto(rp.Delay) 69 if err != nil { 70 return err 71 } 72 if delay < 0 { 73 return status.Errorf(codes.InvalidArgument, "TaskSpec: restart-delay cannot be negative") 74 } 75 } 76 77 if rp.Window != nil { 78 win, err := gogotypes.DurationFromProto(rp.Window) 79 if err != nil { 80 return err 81 } 82 if win < 0 { 83 return status.Errorf(codes.InvalidArgument, "TaskSpec: restart-window cannot be negative") 84 } 85 } 86 87 return nil 88 } 89 90 func validatePlacement(placement *api.Placement) error { 91 if placement == nil { 92 return nil 93 } 94 _, err := constraint.Parse(placement.Constraints) 95 return err 96 } 97 98 func validateUpdate(uc *api.UpdateConfig) error { 99 if uc == nil { 100 return nil 101 } 102 103 if uc.Delay < 0 { 104 return status.Errorf(codes.InvalidArgument, "TaskSpec: update-delay cannot be negative") 105 } 106 107 if uc.Monitor != nil { 108 monitor, err := gogotypes.DurationFromProto(uc.Monitor) 109 if err != nil { 110 return err 111 } 112 if monitor < 0 { 113 return status.Errorf(codes.InvalidArgument, "TaskSpec: update-monitor cannot be negative") 114 } 115 } 116 117 if uc.MaxFailureRatio < 0 || uc.MaxFailureRatio > 1 { 118 return status.Errorf(codes.InvalidArgument, "TaskSpec: update-maxfailureratio cannot be less than 0 or bigger than 1") 119 } 120 121 return nil 122 } 123 124 func validateContainerSpec(taskSpec api.TaskSpec) error { 125 // Building a empty/dummy Task to validate the templating and 126 // the resulting container spec as well. This is a *best effort* 127 // validation. 128 container, err := template.ExpandContainerSpec(&api.NodeDescription{ 129 Hostname: "nodeHostname", 130 Platform: &api.Platform{ 131 OS: "os", 132 Architecture: "architecture", 133 }, 134 }, &api.Task{ 135 Spec: taskSpec, 136 ServiceID: "serviceid", 137 Slot: 1, 138 NodeID: "nodeid", 139 Networks: []*api.NetworkAttachment{}, 140 Annotations: api.Annotations{ 141 Name: "taskname", 142 }, 143 ServiceAnnotations: api.Annotations{ 144 Name: "servicename", 145 }, 146 Endpoint: &api.Endpoint{}, 147 LogDriver: taskSpec.LogDriver, 148 }) 149 if err != nil { 150 return status.Errorf(codes.InvalidArgument, err.Error()) 151 } 152 153 if err := validateImage(container.Image); err != nil { 154 return err 155 } 156 157 if err := validateMounts(container.Mounts); err != nil { 158 return err 159 } 160 161 return validateHealthCheck(container.Healthcheck) 162 } 163 164 // validateImage validates image name in containerSpec 165 func validateImage(image string) error { 166 if image == "" { 167 return status.Errorf(codes.InvalidArgument, "ContainerSpec: image reference must be provided") 168 } 169 170 if _, err := reference.ParseNormalizedNamed(image); err != nil { 171 return status.Errorf(codes.InvalidArgument, "ContainerSpec: %q is not a valid repository/tag", image) 172 } 173 return nil 174 } 175 176 // validateMounts validates if there are duplicate mounts in containerSpec 177 func validateMounts(mounts []api.Mount) error { 178 mountMap := make(map[string]bool) 179 for _, mount := range mounts { 180 if _, exists := mountMap[mount.Target]; exists { 181 return status.Errorf(codes.InvalidArgument, "ContainerSpec: duplicate mount point: %s", mount.Target) 182 } 183 mountMap[mount.Target] = true 184 } 185 186 return nil 187 } 188 189 // validateHealthCheck validates configs about container's health check 190 func validateHealthCheck(hc *api.HealthConfig) error { 191 if hc == nil { 192 return nil 193 } 194 195 if hc.Interval != nil { 196 interval, err := gogotypes.DurationFromProto(hc.Interval) 197 if err != nil { 198 return err 199 } 200 if interval != 0 && interval < minimumDuration { 201 return status.Errorf(codes.InvalidArgument, "ContainerSpec: Interval in HealthConfig cannot be less than %s", minimumDuration) 202 } 203 } 204 205 if hc.Timeout != nil { 206 timeout, err := gogotypes.DurationFromProto(hc.Timeout) 207 if err != nil { 208 return err 209 } 210 if timeout != 0 && timeout < minimumDuration { 211 return status.Errorf(codes.InvalidArgument, "ContainerSpec: Timeout in HealthConfig cannot be less than %s", minimumDuration) 212 } 213 } 214 215 if hc.StartPeriod != nil { 216 sp, err := gogotypes.DurationFromProto(hc.StartPeriod) 217 if err != nil { 218 return err 219 } 220 if sp != 0 && sp < minimumDuration { 221 return status.Errorf(codes.InvalidArgument, "ContainerSpec: StartPeriod in HealthConfig cannot be less than %s", minimumDuration) 222 } 223 } 224 225 if hc.Retries < 0 { 226 return status.Errorf(codes.InvalidArgument, "ContainerSpec: Retries in HealthConfig cannot be negative") 227 } 228 229 return nil 230 } 231 232 func validateGenericRuntimeSpec(taskSpec api.TaskSpec) error { 233 generic := taskSpec.GetGeneric() 234 235 if len(generic.Kind) < 3 { 236 return status.Errorf(codes.InvalidArgument, "Generic runtime: Invalid name %q", generic.Kind) 237 } 238 239 reservedNames := []string{"container", "attachment"} 240 for _, n := range reservedNames { 241 if strings.ToLower(generic.Kind) == n { 242 return status.Errorf(codes.InvalidArgument, "Generic runtime: %q is a reserved name", generic.Kind) 243 } 244 } 245 246 payload := generic.Payload 247 248 if payload == nil { 249 return status.Errorf(codes.InvalidArgument, "Generic runtime is missing payload") 250 } 251 252 if payload.TypeUrl == "" { 253 return status.Errorf(codes.InvalidArgument, "Generic runtime is missing payload type") 254 } 255 256 if len(payload.Value) == 0 { 257 return status.Errorf(codes.InvalidArgument, "Generic runtime has an empty payload") 258 } 259 260 return nil 261 } 262 263 func validateTaskSpec(taskSpec api.TaskSpec) error { 264 if err := validateResourceRequirements(taskSpec.Resources); err != nil { 265 return err 266 } 267 268 if err := validateRestartPolicy(taskSpec.Restart); err != nil { 269 return err 270 } 271 272 if err := validatePlacement(taskSpec.Placement); err != nil { 273 return err 274 } 275 276 // Check to see if the secret reference portion of the spec is valid 277 if err := validateSecretRefsSpec(taskSpec); err != nil { 278 return err 279 } 280 281 // Check to see if the config reference portion of the spec is valid 282 if err := validateConfigRefsSpec(taskSpec); err != nil { 283 return err 284 } 285 286 if taskSpec.GetRuntime() == nil { 287 return status.Errorf(codes.InvalidArgument, "TaskSpec: missing runtime") 288 } 289 290 switch taskSpec.GetRuntime().(type) { 291 case *api.TaskSpec_Container: 292 if err := validateContainerSpec(taskSpec); err != nil { 293 return err 294 } 295 case *api.TaskSpec_Generic: 296 if err := validateGenericRuntimeSpec(taskSpec); err != nil { 297 return err 298 } 299 default: 300 return status.Errorf(codes.Unimplemented, "RuntimeSpec: unimplemented runtime in service spec") 301 } 302 303 return nil 304 } 305 306 func validateEndpointSpec(epSpec *api.EndpointSpec) error { 307 // Endpoint spec is optional 308 if epSpec == nil { 309 return nil 310 } 311 312 type portSpec struct { 313 publishedPort uint32 314 protocol api.PortConfig_Protocol 315 } 316 317 portSet := make(map[portSpec]struct{}) 318 for _, port := range epSpec.Ports { 319 // Publish mode = "ingress" represents Routing-Mesh and current implementation 320 // of routing-mesh relies on IPVS based load-balancing with input=published-port. 321 // But Endpoint-Spec mode of DNSRR relies on multiple A records and cannot be used 322 // with routing-mesh (PublishMode="ingress") which cannot rely on DNSRR. 323 // But PublishMode="host" doesn't provide Routing-Mesh and the DNSRR is applicable 324 // for the backend network and hence we accept that configuration. 325 326 if epSpec.Mode == api.ResolutionModeDNSRoundRobin && port.PublishMode == api.PublishModeIngress { 327 return status.Errorf(codes.InvalidArgument, "EndpointSpec: port published with ingress mode can't be used with dnsrr mode") 328 } 329 330 // If published port is not specified, it does not conflict 331 // with any others. 332 if port.PublishedPort == 0 { 333 continue 334 } 335 336 portSpec := portSpec{publishedPort: port.PublishedPort, protocol: port.Protocol} 337 if _, ok := portSet[portSpec]; ok { 338 return status.Errorf(codes.InvalidArgument, "EndpointSpec: duplicate published ports provided") 339 } 340 341 portSet[portSpec] = struct{}{} 342 } 343 344 return nil 345 } 346 347 // validateSecretRefsSpec finds if the secrets passed in spec are valid and have no 348 // conflicting targets. 349 func validateSecretRefsSpec(spec api.TaskSpec) error { 350 container := spec.GetContainer() 351 if container == nil { 352 return nil 353 } 354 355 // Keep a map to track all the targets that will be exposed 356 // The string returned is only used for logging. It could as well be struct{}{} 357 existingTargets := make(map[string]string) 358 for _, secretRef := range container.Secrets { 359 // SecretID and SecretName are mandatory, we have invalid references without them 360 if secretRef.SecretID == "" || secretRef.SecretName == "" { 361 return status.Errorf(codes.InvalidArgument, "malformed secret reference") 362 } 363 364 // Every secret reference requires a Target 365 if secretRef.GetTarget() == nil { 366 return status.Errorf(codes.InvalidArgument, "malformed secret reference, no target provided") 367 } 368 369 // If this is a file target, we will ensure filename uniqueness 370 if secretRef.GetFile() != nil { 371 fileName := secretRef.GetFile().Name 372 if fileName == "" { 373 return status.Errorf(codes.InvalidArgument, "malformed file secret reference, invalid target file name provided") 374 } 375 // If this target is already in use, we have conflicting targets 376 if prevSecretName, ok := existingTargets[fileName]; ok { 377 return status.Errorf(codes.InvalidArgument, "secret references '%s' and '%s' have a conflicting target: '%s'", prevSecretName, secretRef.SecretName, fileName) 378 } 379 380 existingTargets[fileName] = secretRef.SecretName 381 } 382 } 383 384 return nil 385 } 386 387 // validateConfigRefsSpec finds if the configs passed in spec are valid and have no 388 // conflicting targets. 389 func validateConfigRefsSpec(spec api.TaskSpec) error { 390 container := spec.GetContainer() 391 if container == nil { 392 return nil 393 } 394 395 // check if we're using a config as a CredentialSpec -- if so, we need to 396 // verify 397 var ( 398 credSpecConfig string 399 credSpecConfigFound bool 400 ) 401 if p := container.Privileges; p != nil { 402 if cs := p.CredentialSpec; cs != nil { 403 // if there is no config in the credspec, then this will just be 404 // assigned to emptystring anyway, so we don't need to check 405 // existence. 406 credSpecConfig = cs.GetConfig() 407 } 408 } 409 410 // Keep a map to track all the targets that will be exposed 411 // The string returned is only used for logging. It could as well be struct{}{} 412 existingTargets := make(map[string]string) 413 for _, configRef := range container.Configs { 414 // ConfigID and ConfigName are mandatory, we have invalid references without them 415 if configRef.ConfigID == "" || configRef.ConfigName == "" { 416 return status.Errorf(codes.InvalidArgument, "malformed config reference") 417 } 418 419 // Every config reference requires a Target 420 if configRef.GetTarget() == nil { 421 return status.Errorf(codes.InvalidArgument, "malformed config reference, no target provided") 422 } 423 424 // If this is a file target, we will ensure filename uniqueness 425 if configRef.GetFile() != nil { 426 fileName := configRef.GetFile().Name 427 // Validate the file name 428 if fileName == "" { 429 return status.Errorf(codes.InvalidArgument, "malformed file config reference, invalid target file name provided") 430 } 431 432 // If this target is already in use, we have conflicting targets 433 if prevConfigName, ok := existingTargets[fileName]; ok { 434 return status.Errorf(codes.InvalidArgument, "config references '%s' and '%s' have a conflicting target: '%s'", prevConfigName, configRef.ConfigName, fileName) 435 } 436 437 existingTargets[fileName] = configRef.ConfigName 438 } 439 440 if configRef.GetRuntime() != nil { 441 if configRef.ConfigID == credSpecConfig { 442 credSpecConfigFound = true 443 } 444 } 445 } 446 447 if credSpecConfig != "" && !credSpecConfigFound { 448 return status.Errorf( 449 codes.InvalidArgument, 450 "CredentialSpec references config '%s', but that config isn't in config references with RuntimeTarget", 451 credSpecConfig, 452 ) 453 } 454 455 return nil 456 } 457 458 func (s *Server) validateNetworks(networks []*api.NetworkAttachmentConfig) error { 459 for _, na := range networks { 460 var network *api.Network 461 s.store.View(func(tx store.ReadTx) { 462 network = store.GetNetwork(tx, na.Target) 463 }) 464 if network == nil { 465 continue 466 } 467 if allocator.IsIngressNetwork(network) { 468 return status.Errorf(codes.InvalidArgument, 469 "Service cannot be explicitly attached to the ingress network %q", network.Spec.Annotations.Name) 470 } 471 } 472 return nil 473 } 474 475 func validateMode(s *api.ServiceSpec) error { 476 m := s.GetMode() 477 switch mode := m.(type) { 478 case *api.ServiceSpec_Replicated: 479 if int64(mode.Replicated.Replicas) < 0 { 480 return status.Errorf(codes.InvalidArgument, "Number of replicas must be non-negative") 481 } 482 case *api.ServiceSpec_Global: 483 case *api.ServiceSpec_ReplicatedJob: 484 // this check shouldn't be required as the point of uint64 is to 485 // constrain the possible values to positive numbers, but it almost 486 // certainly is required because there are almost certainly blind casts 487 // from int64 to uint64, and uint64(-1) is almost certain to crash the 488 // cluster because of how large it is. 489 if int64(mode.ReplicatedJob.MaxConcurrent) < 0 { 490 return status.Errorf( 491 codes.InvalidArgument, 492 "Maximum concurrent jobs must not be negative", 493 ) 494 } 495 496 if int64(mode.ReplicatedJob.TotalCompletions) < 0 { 497 return status.Errorf( 498 codes.InvalidArgument, 499 "Total completed jobs must not be negative", 500 ) 501 } 502 case *api.ServiceSpec_GlobalJob: 503 default: 504 return status.Errorf(codes.InvalidArgument, "Unrecognized service mode") 505 } 506 507 return nil 508 } 509 510 func validateJob(spec *api.ServiceSpec) error { 511 if spec.Update != nil { 512 return status.Errorf(codes.InvalidArgument, "Jobs may not have an update config") 513 } 514 return nil 515 } 516 517 func validateServiceSpec(spec *api.ServiceSpec) error { 518 if spec == nil { 519 return status.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) 520 } 521 if err := validateAnnotations(spec.Annotations); err != nil { 522 return err 523 } 524 if err := validateTaskSpec(spec.Task); err != nil { 525 return err 526 } 527 err := validateMode(spec) 528 if err != nil { 529 return err 530 } 531 532 // job-mode services are validated differently. most notably, they do not 533 // have UpdateConfigs, which is why this case statement skips update 534 // validation. 535 if isJobSpec(spec) { 536 if err := validateJob(spec); err != nil { 537 return err 538 } 539 } else { 540 if err := validateUpdate(spec.Update); err != nil { 541 return err 542 } 543 } 544 545 return validateEndpointSpec(spec.Endpoint) 546 } 547 548 func isJobSpec(spec *api.ServiceSpec) bool { 549 mode := spec.GetMode() 550 _, isGlobalJob := mode.(*api.ServiceSpec_GlobalJob) 551 _, isReplicatedJob := mode.(*api.ServiceSpec_ReplicatedJob) 552 return isGlobalJob || isReplicatedJob 553 } 554 555 // checkPortConflicts does a best effort to find if the passed in spec has port 556 // conflicts with existing services. 557 // `serviceID string` is the service ID of the spec in service update. If 558 // `serviceID` is not "", then conflicts check will be skipped against this 559 // service (the service being updated). 560 func (s *Server) checkPortConflicts(spec *api.ServiceSpec, serviceID string) error { 561 if spec.Endpoint == nil { 562 return nil 563 } 564 565 type portSpec struct { 566 protocol api.PortConfig_Protocol 567 publishedPort uint32 568 } 569 570 pcToStruct := func(pc *api.PortConfig) portSpec { 571 return portSpec{ 572 protocol: pc.Protocol, 573 publishedPort: pc.PublishedPort, 574 } 575 } 576 577 ingressPorts := make(map[portSpec]struct{}) 578 hostModePorts := make(map[portSpec]struct{}) 579 for _, pc := range spec.Endpoint.Ports { 580 if pc.PublishedPort == 0 { 581 continue 582 } 583 switch pc.PublishMode { 584 case api.PublishModeIngress: 585 ingressPorts[pcToStruct(pc)] = struct{}{} 586 case api.PublishModeHost: 587 hostModePorts[pcToStruct(pc)] = struct{}{} 588 } 589 } 590 if len(ingressPorts) == 0 && len(hostModePorts) == 0 { 591 return nil 592 } 593 594 var ( 595 services []*api.Service 596 err error 597 ) 598 599 s.store.View(func(tx store.ReadTx) { 600 services, err = store.FindServices(tx, store.All) 601 }) 602 if err != nil { 603 return err 604 } 605 606 isPortInUse := func(pc *api.PortConfig, service *api.Service) error { 607 if pc.PublishedPort == 0 { 608 return nil 609 } 610 611 switch pc.PublishMode { 612 case api.PublishModeHost: 613 if _, ok := ingressPorts[pcToStruct(pc)]; ok { 614 return status.Errorf(codes.InvalidArgument, "port '%d' is already in use by service '%s' (%s) as a host-published port", pc.PublishedPort, service.Spec.Annotations.Name, service.ID) 615 } 616 617 // Multiple services with same port in host publish mode can 618 // coexist - this is handled by the scheduler. 619 return nil 620 case api.PublishModeIngress: 621 _, ingressConflict := ingressPorts[pcToStruct(pc)] 622 _, hostModeConflict := hostModePorts[pcToStruct(pc)] 623 if ingressConflict || hostModeConflict { 624 return status.Errorf(codes.InvalidArgument, "port '%d' is already in use by service '%s' (%s) as an ingress port", pc.PublishedPort, service.Spec.Annotations.Name, service.ID) 625 } 626 } 627 628 return nil 629 } 630 631 for _, service := range services { 632 // If service ID is the same (and not "") then this is an update 633 if serviceID != "" && serviceID == service.ID { 634 continue 635 } 636 if service.Spec.Endpoint != nil { 637 for _, pc := range service.Spec.Endpoint.Ports { 638 if err := isPortInUse(pc, service); err != nil { 639 return err 640 } 641 } 642 } 643 if service.Endpoint != nil { 644 for _, pc := range service.Endpoint.Ports { 645 if err := isPortInUse(pc, service); err != nil { 646 return err 647 } 648 } 649 } 650 } 651 return nil 652 } 653 654 // checkSecretExistence finds if the secret exists 655 func (s *Server) checkSecretExistence(tx store.Tx, spec *api.ServiceSpec) error { 656 container := spec.Task.GetContainer() 657 if container == nil { 658 return nil 659 } 660 661 var failedSecrets []string 662 for _, secretRef := range container.Secrets { 663 secret := store.GetSecret(tx, secretRef.SecretID) 664 // Check to see if the secret exists and secretRef.SecretName matches the actual secretName 665 if secret == nil || secret.Spec.Annotations.Name != secretRef.SecretName { 666 failedSecrets = append(failedSecrets, secretRef.SecretName) 667 } 668 } 669 670 if len(failedSecrets) > 0 { 671 secretStr := "secrets" 672 if len(failedSecrets) == 1 { 673 secretStr = "secret" 674 } 675 676 return status.Errorf(codes.InvalidArgument, "%s not found: %v", secretStr, strings.Join(failedSecrets, ", ")) 677 678 } 679 680 return nil 681 } 682 683 // checkConfigExistence finds if the config exists 684 func (s *Server) checkConfigExistence(tx store.Tx, spec *api.ServiceSpec) error { 685 container := spec.Task.GetContainer() 686 if container == nil { 687 return nil 688 } 689 690 var failedConfigs []string 691 for _, configRef := range container.Configs { 692 config := store.GetConfig(tx, configRef.ConfigID) 693 // Check to see if the config exists and configRef.ConfigName matches the actual configName 694 if config == nil || config.Spec.Annotations.Name != configRef.ConfigName { 695 failedConfigs = append(failedConfigs, configRef.ConfigName) 696 } 697 } 698 699 if len(failedConfigs) > 0 { 700 configStr := "configs" 701 if len(failedConfigs) == 1 { 702 configStr = "config" 703 } 704 705 return status.Errorf(codes.InvalidArgument, "%s not found: %v", configStr, strings.Join(failedConfigs, ", ")) 706 707 } 708 709 return nil 710 } 711 712 // CreateService creates and returns a Service based on the provided ServiceSpec. 713 // - Returns `InvalidArgument` if the ServiceSpec is malformed. 714 // - Returns `Unimplemented` if the ServiceSpec references unimplemented features. 715 // - Returns `AlreadyExists` if the ServiceID conflicts. 716 // - Returns an error if the creation fails. 717 func (s *Server) CreateService(ctx context.Context, request *api.CreateServiceRequest) (*api.CreateServiceResponse, error) { 718 if err := validateServiceSpec(request.Spec); err != nil { 719 return nil, err 720 } 721 722 if err := s.validateNetworks(request.Spec.Task.Networks); err != nil { 723 return nil, err 724 } 725 726 if err := s.checkPortConflicts(request.Spec, ""); err != nil { 727 return nil, err 728 } 729 730 // TODO(aluzzardi): Consider using `Name` as a primary key to handle 731 // duplicate creations. See #65 732 service := &api.Service{ 733 ID: identity.NewID(), 734 Spec: *request.Spec, 735 SpecVersion: &api.Version{}, 736 } 737 738 if isJobSpec(request.Spec) { 739 service.JobStatus = &api.JobStatus{ 740 LastExecution: gogotypes.TimestampNow(), 741 } 742 } 743 744 if allocator.IsIngressNetworkNeeded(service) { 745 if _, err := allocator.GetIngressNetwork(s.store); err == allocator.ErrNoIngress { 746 return nil, status.Errorf(codes.FailedPrecondition, "service needs ingress network, but no ingress network is present") 747 } 748 } 749 750 err := s.store.Update(func(tx store.Tx) error { 751 // Check to see if all the secrets being added exist as objects 752 // in our datastore 753 err := s.checkSecretExistence(tx, request.Spec) 754 if err != nil { 755 return err 756 } 757 err = s.checkConfigExistence(tx, request.Spec) 758 if err != nil { 759 return err 760 } 761 762 return store.CreateService(tx, service) 763 }) 764 switch err { 765 case store.ErrNameConflict: 766 // Enhance the name-confict error to include the service name. The original 767 // `ErrNameConflict` error-message is included for backward-compatibility 768 // with older consumers of the API performing string-matching. 769 return nil, status.Errorf(codes.AlreadyExists, "%s: service %s already exists", err.Error(), request.Spec.Annotations.Name) 770 case nil: 771 return &api.CreateServiceResponse{Service: service}, nil 772 default: 773 return nil, err 774 } 775 } 776 777 // GetService returns a Service given a ServiceID. 778 // - Returns `InvalidArgument` if ServiceID is not provided. 779 // - Returns `NotFound` if the Service is not found. 780 func (s *Server) GetService(ctx context.Context, request *api.GetServiceRequest) (*api.GetServiceResponse, error) { 781 if request.ServiceID == "" { 782 return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) 783 } 784 785 var service *api.Service 786 s.store.View(func(tx store.ReadTx) { 787 service = store.GetService(tx, request.ServiceID) 788 }) 789 if service == nil { 790 return nil, status.Errorf(codes.NotFound, "service %s not found", request.ServiceID) 791 } 792 793 if request.InsertDefaults { 794 service.Spec = *defaults.InterpolateService(&service.Spec) 795 } 796 797 return &api.GetServiceResponse{ 798 Service: service, 799 }, nil 800 } 801 802 // UpdateService updates a Service referenced by ServiceID with the given ServiceSpec. 803 // - Returns `NotFound` if the Service is not found. 804 // - Returns `InvalidArgument` if the ServiceSpec is malformed. 805 // - Returns `Unimplemented` if the ServiceSpec references unimplemented features. 806 // - Returns an error if the update fails. 807 func (s *Server) UpdateService(ctx context.Context, request *api.UpdateServiceRequest) (*api.UpdateServiceResponse, error) { 808 if request.ServiceID == "" || request.ServiceVersion == nil { 809 return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) 810 } 811 if err := validateServiceSpec(request.Spec); err != nil { 812 return nil, err 813 } 814 815 if err := s.validateNetworks(request.Spec.Task.Networks); err != nil { 816 return nil, err 817 } 818 819 var service *api.Service 820 s.store.View(func(tx store.ReadTx) { 821 service = store.GetService(tx, request.ServiceID) 822 }) 823 if service == nil { 824 return nil, status.Errorf(codes.NotFound, "service %s not found", request.ServiceID) 825 } 826 827 if request.Spec.Endpoint != nil && !reflect.DeepEqual(request.Spec.Endpoint, service.Spec.Endpoint) { 828 if err := s.checkPortConflicts(request.Spec, request.ServiceID); err != nil { 829 return nil, err 830 } 831 } 832 833 err := s.store.Update(func(tx store.Tx) error { 834 service = store.GetService(tx, request.ServiceID) 835 if service == nil { 836 return status.Errorf(codes.NotFound, "service %s not found", request.ServiceID) 837 } 838 839 // It's not okay to update Service.Spec.Networks on its own. 840 // However, if Service.Spec.Task.Networks is also being 841 // updated, that's okay (for example when migrating from the 842 // deprecated Spec.Networks field to Spec.Task.Networks). 843 if (len(request.Spec.Networks) != 0 || len(service.Spec.Networks) != 0) && 844 !reflect.DeepEqual(request.Spec.Networks, service.Spec.Networks) && 845 reflect.DeepEqual(request.Spec.Task.Networks, service.Spec.Task.Networks) { 846 return status.Errorf(codes.Unimplemented, errNetworkUpdateNotSupported.Error()) 847 } 848 849 // Check to see if all the secrets being added exist as objects 850 // in our datastore 851 err := s.checkSecretExistence(tx, request.Spec) 852 if err != nil { 853 return err 854 } 855 856 err = s.checkConfigExistence(tx, request.Spec) 857 if err != nil { 858 return err 859 } 860 861 // orchestrator is designed to be stateless, so it should not deal 862 // with service mode change (comparing current config with previous config). 863 // proper way to change service mode is to delete and re-add. 864 if reflect.TypeOf(service.Spec.Mode) != reflect.TypeOf(request.Spec.Mode) { 865 return status.Errorf(codes.Unimplemented, errModeChangeNotAllowed.Error()) 866 } 867 868 if service.Spec.Annotations.Name != request.Spec.Annotations.Name { 869 return status.Errorf(codes.Unimplemented, errRenameNotSupported.Error()) 870 } 871 872 service.Meta.Version = *request.ServiceVersion 873 874 // if the service has a JobStatus, that means it must be a Job, and we 875 // should increment the JobIteration 876 if service.JobStatus != nil { 877 service.JobStatus.JobIteration.Index = service.JobStatus.JobIteration.Index + 1 878 service.JobStatus.LastExecution = gogotypes.TimestampNow() 879 } 880 881 if request.Rollback == api.UpdateServiceRequest_PREVIOUS { 882 if service.PreviousSpec == nil { 883 return status.Errorf(codes.FailedPrecondition, "service %s does not have a previous spec", request.ServiceID) 884 } 885 886 curSpec := service.Spec.Copy() 887 curSpecVersion := service.SpecVersion 888 service.Spec = *service.PreviousSpec.Copy() 889 service.SpecVersion = service.PreviousSpecVersion.Copy() 890 service.PreviousSpec = curSpec 891 service.PreviousSpecVersion = curSpecVersion 892 893 service.UpdateStatus = &api.UpdateStatus{ 894 State: api.UpdateStatus_ROLLBACK_STARTED, 895 Message: "manually requested rollback", 896 StartedAt: ptypes.MustTimestampProto(time.Now()), 897 } 898 } else { 899 service.PreviousSpec = service.Spec.Copy() 900 service.PreviousSpecVersion = service.SpecVersion 901 service.Spec = *request.Spec.Copy() 902 // Set spec version. Note that this will not match the 903 // service's Meta.Version after the store update. The 904 // versions for the spec and the service itself are not 905 // meant to be directly comparable. 906 service.SpecVersion = service.Meta.Version.Copy() 907 908 // Reset update status 909 service.UpdateStatus = nil 910 } 911 912 if allocator.IsIngressNetworkNeeded(service) { 913 if _, err := allocator.GetIngressNetwork(s.store); err == allocator.ErrNoIngress { 914 return status.Errorf(codes.FailedPrecondition, "service needs ingress network, but no ingress network is present") 915 } 916 } 917 918 return store.UpdateService(tx, service) 919 }) 920 if err != nil { 921 return nil, err 922 } 923 924 return &api.UpdateServiceResponse{ 925 Service: service, 926 }, nil 927 } 928 929 // RemoveService removes a Service referenced by ServiceID. 930 // - Returns `InvalidArgument` if ServiceID is not provided. 931 // - Returns `NotFound` if the Service is not found. 932 // - Returns an error if the deletion fails. 933 func (s *Server) RemoveService(ctx context.Context, request *api.RemoveServiceRequest) (*api.RemoveServiceResponse, error) { 934 if request.ServiceID == "" { 935 return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) 936 } 937 938 err := s.store.Update(func(tx store.Tx) error { 939 return store.DeleteService(tx, request.ServiceID) 940 }) 941 if err != nil { 942 if err == store.ErrNotExist { 943 return nil, status.Errorf(codes.NotFound, "service %s not found", request.ServiceID) 944 } 945 return nil, err 946 } 947 return &api.RemoveServiceResponse{}, nil 948 } 949 950 func filterServices(candidates []*api.Service, filters ...func(*api.Service) bool) []*api.Service { 951 result := []*api.Service{} 952 953 for _, c := range candidates { 954 match := true 955 for _, f := range filters { 956 if !f(c) { 957 match = false 958 break 959 } 960 } 961 if match { 962 result = append(result, c) 963 } 964 } 965 966 return result 967 } 968 969 // ListServices returns a list of all services. 970 func (s *Server) ListServices(ctx context.Context, request *api.ListServicesRequest) (*api.ListServicesResponse, error) { 971 var ( 972 services []*api.Service 973 err error 974 ) 975 976 s.store.View(func(tx store.ReadTx) { 977 switch { 978 case request.Filters != nil && len(request.Filters.Names) > 0: 979 services, err = store.FindServices(tx, buildFilters(store.ByName, request.Filters.Names)) 980 case request.Filters != nil && len(request.Filters.NamePrefixes) > 0: 981 services, err = store.FindServices(tx, buildFilters(store.ByNamePrefix, request.Filters.NamePrefixes)) 982 case request.Filters != nil && len(request.Filters.IDPrefixes) > 0: 983 services, err = store.FindServices(tx, buildFilters(store.ByIDPrefix, request.Filters.IDPrefixes)) 984 case request.Filters != nil && len(request.Filters.Runtimes) > 0: 985 services, err = store.FindServices(tx, buildFilters(store.ByRuntime, request.Filters.Runtimes)) 986 default: 987 services, err = store.FindServices(tx, store.All) 988 } 989 }) 990 if err != nil { 991 switch err { 992 case store.ErrInvalidFindBy: 993 return nil, status.Errorf(codes.InvalidArgument, err.Error()) 994 default: 995 return nil, err 996 } 997 } 998 999 if request.Filters != nil { 1000 services = filterServices(services, 1001 func(e *api.Service) bool { 1002 return filterContains(e.Spec.Annotations.Name, request.Filters.Names) 1003 }, 1004 func(e *api.Service) bool { 1005 return filterContainsPrefix(e.Spec.Annotations.Name, request.Filters.NamePrefixes) 1006 }, 1007 func(e *api.Service) bool { 1008 return filterContainsPrefix(e.ID, request.Filters.IDPrefixes) 1009 }, 1010 func(e *api.Service) bool { 1011 return filterMatchLabels(e.Spec.Annotations.Labels, request.Filters.Labels) 1012 }, 1013 func(e *api.Service) bool { 1014 if len(request.Filters.Runtimes) == 0 { 1015 return true 1016 } 1017 r, err := naming.Runtime(e.Spec.Task) 1018 if err != nil { 1019 return false 1020 } 1021 return filterContains(r, request.Filters.Runtimes) 1022 }, 1023 ) 1024 } 1025 1026 return &api.ListServicesResponse{ 1027 Services: services, 1028 }, nil 1029 } 1030 1031 // ListServiceStatuses returns a `ListServiceStatusesResponse` with the status 1032 // of the requested services, formed by computing the number of running vs 1033 // desired tasks. It is provided as a shortcut or helper method, which allows a 1034 // client to avoid having to calculate this value by listing all Tasks. If any 1035 // service requested does not exist, it will be returned but with empty status 1036 // values. 1037 func (s *Server) ListServiceStatuses(ctx context.Context, req *api.ListServiceStatusesRequest) (*api.ListServiceStatusesResponse, error) { 1038 resp := &api.ListServiceStatusesResponse{} 1039 if req == nil { 1040 return resp, nil 1041 } 1042 1043 s.store.View(func(tx store.ReadTx) { 1044 for _, id := range req.Services { 1045 status := &api.ListServiceStatusesResponse_ServiceStatus{ 1046 ServiceID: id, 1047 } 1048 // no matter what, add this status to the list. 1049 resp.Statuses = append(resp.Statuses, status) 1050 1051 tasks, findErr := store.FindTasks(tx, store.ByServiceID(id)) 1052 if findErr != nil { 1053 // if there is another kind of error here (not sure what it 1054 // could be) then still return 0/0 for this service. 1055 continue 1056 } 1057 1058 // use a boolean to see global vs replicated. this avoids us having to 1059 // iterate the task list twice. 1060 global := false 1061 // jobIteration is the iteration that the Job is currently 1062 // operating on, to distinguish Tasks in old executions from tasks 1063 // in the current one. if nil, service is not a Job 1064 var jobIteration *api.Version 1065 service := store.GetService(tx, id) 1066 // a service might be deleted, but it may still have tasks. in that 1067 // case, we will be using 0 as the desired task count. 1068 if service != nil { 1069 // figure out how many tasks the service requires. for replicated 1070 // services, this is easy: we can just check the replicas field. for 1071 // global services, this is a bit harder and we'll need to do some 1072 // numbercrunchin 1073 if replicated := service.Spec.GetReplicated(); replicated != nil { 1074 status.DesiredTasks = replicated.Replicas 1075 } else if replicatedJob := service.Spec.GetReplicatedJob(); replicatedJob != nil { 1076 status.DesiredTasks = replicatedJob.MaxConcurrent 1077 } else { 1078 // global applies to both GlobalJob and regular Global 1079 global = true 1080 } 1081 1082 if service.JobStatus != nil { 1083 jobIteration = &service.JobStatus.JobIteration 1084 } 1085 } 1086 1087 // now, figure out how many tasks are running. Pretty easy, and 1088 // universal across both global and replicated services 1089 for _, task := range tasks { 1090 // if the service is a Job, jobIteration will be non-nil. This 1091 // means we should check if the task belongs to the current job 1092 // iteration. If not, skip accounting the task. 1093 if jobIteration != nil { 1094 if task.JobIteration == nil || task.JobIteration.Index != jobIteration.Index { 1095 continue 1096 } 1097 1098 // additionally, since we've verified that the service is a 1099 // job and the task belongs to this iteration, we should 1100 // increment CompletedTasks 1101 if task.Status.State == api.TaskStateCompleted { 1102 status.CompletedTasks++ 1103 } 1104 } 1105 if task.Status.State == api.TaskStateRunning { 1106 status.RunningTasks++ 1107 } 1108 1109 // if the service is global, a shortcut for figuring out the 1110 // number of tasks desired is to look at all tasks, and take a 1111 // count of the ones whose desired state is not Shutdown. 1112 if global && task.DesiredState == api.TaskStateRunning { 1113 status.DesiredTasks++ 1114 } 1115 1116 // for jobs, this is any task with desired state Completed 1117 // which is not actually in that state. 1118 if global && task.Status.State != api.TaskStateCompleted && task.DesiredState == api.TaskStateCompleted { 1119 status.DesiredTasks++ 1120 } 1121 } 1122 } 1123 }) 1124 1125 return resp, nil 1126 }