func (u *Updater) rollbackUpdate(ctx context.Context, serviceID, message string) { log.G(ctx).Debugf("starting rollback of service %s", serviceID) var service *api.Service err := u.store.Update(func(tx store.Tx) error { service = store.GetService(tx, serviceID) if service == nil { return nil } if service.UpdateStatus == nil { // The service was updated since we started this update return nil } service.UpdateStatus.State = api.UpdateStatus_ROLLBACK_STARTED service.UpdateStatus.Message = message if service.PreviousSpec == nil { return errors.New("cannot roll back service because no previous spec is available") } service.Spec = *service.PreviousSpec service.PreviousSpec = nil return store.UpdateService(tx, service) }) if err != nil { log.G(ctx).WithError(err).Errorf("failed to start rollback of service %s", serviceID) return } }
// UpdateService updates a Service referenced by ServiceID with the given ServiceSpec. // - Returns `NotFound` if the Service is not found. // - Returns `InvalidArgument` if the ServiceSpec is malformed. // - Returns `Unimplemented` if the ServiceSpec references unimplemented features. // - Returns an error if the update fails. func (s *Server) UpdateService(ctx context.Context, request *api.UpdateServiceRequest) (*api.UpdateServiceResponse, error) { if request.ServiceID == "" || request.ServiceVersion == nil { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } if err := validateServiceSpec(request.Spec); err != nil { return nil, err } var service *api.Service s.store.View(func(tx store.ReadTx) { service = store.GetService(tx, request.ServiceID) }) if service == nil { return nil, grpc.Errorf(codes.NotFound, "service %s not found", request.ServiceID) } if request.Spec.Endpoint != nil && !reflect.DeepEqual(request.Spec.Endpoint, service.Spec.Endpoint) { if err := s.checkPortConflicts(request.Spec, request.ServiceID); err != nil { return nil, err } } err := s.store.Update(func(tx store.Tx) error { service = store.GetService(tx, request.ServiceID) if service == nil { return grpc.Errorf(codes.NotFound, "service %s not found", request.ServiceID) } // temporary disable network update requestSpecNetworks := request.Spec.Task.Networks if len(requestSpecNetworks) == 0 { requestSpecNetworks = request.Spec.Networks } specNetworks := service.Spec.Task.Networks if len(specNetworks) == 0 { specNetworks = service.Spec.Networks } if !reflect.DeepEqual(requestSpecNetworks, specNetworks) { return errNetworkUpdateNotSupported } // Check to see if all the secrets being added exist as objects // in our datastore err := s.checkSecretExistence(tx, request.Spec) if err != nil { return err } // orchestrator is designed to be stateless, so it should not deal // with service mode change (comparing current config with previous config). // proper way to change service mode is to delete and re-add. if reflect.TypeOf(service.Spec.Mode) != reflect.TypeOf(request.Spec.Mode) { return errModeChangeNotAllowed } if service.Spec.Annotations.Name != request.Spec.Annotations.Name { return errRenameNotSupported } service.Meta.Version = *request.ServiceVersion service.PreviousSpec = service.Spec.Copy() service.Spec = *request.Spec.Copy() // Reset update status service.UpdateStatus = nil return store.UpdateService(tx, service) }) if err != nil { return nil, err } return &api.UpdateServiceResponse{ Service: service, }, nil }