func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { rc, err := (*r.registry).GetController(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), name) } oldScale := scaleFromRC(rc) obj, err := objInfo.UpdatedObject(ctx, oldScale) if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } scale, ok := obj.(*extensions.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) } if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) } rc.Spec.Replicas = scale.Spec.Replicas rc.ResourceVersion = scale.ResourceVersion rc, err = (*r.registry).UpdateController(ctx, rc) if err != nil { return nil, false, errors.NewConflict(extensions.Resource("replicationcontrollers/scale"), scale.Name, err) } return scaleFromRC(rc), false, nil }
func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { selfSAR, ok := obj.(*authorizationapi.SelfSubjectAccessReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateSelfSubjectAccessReview(selfSAR); len(errs) > 0 { return nil, apierrors.NewInvalid(authorizationapi.Kind(selfSAR.Kind), "", errs) } userToCheck, exists := genericapirequest.UserFrom(ctx) if !exists { return nil, apierrors.NewBadRequest("no user present on request") } var authorizationAttributes authorizer.AttributesRecord if selfSAR.Spec.ResourceAttributes != nil { authorizationAttributes = authorizationutil.ResourceAttributesFrom(userToCheck, *selfSAR.Spec.ResourceAttributes) } else { authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *selfSAR.Spec.NonResourceAttributes) } allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) selfSAR.Status = authorizationapi.SubjectAccessReviewStatus{ Allowed: allowed, Reason: reason, } if evaluationErr != nil { selfSAR.Status.EvaluationError = evaluationErr.Error() } return selfSAR, nil }
func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { localSubjectAccessReview, ok := obj.(*authorizationapi.LocalSubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a LocaLocalSubjectAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateLocalSubjectAccessReview(localSubjectAccessReview); len(errs) > 0 { return nil, kapierrors.NewInvalid(authorizationapi.Kind(localSubjectAccessReview.Kind), "", errs) } namespace := genericapirequest.NamespaceValue(ctx) if len(namespace) == 0 { return nil, kapierrors.NewBadRequest(fmt.Sprintf("namespace is required on this type: %v", namespace)) } if namespace != localSubjectAccessReview.Namespace { return nil, kapierrors.NewBadRequest(fmt.Sprintf("spec.resourceAttributes.namespace must match namespace: %v", namespace)) } authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(localSubjectAccessReview.Spec) allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) localSubjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ Allowed: allowed, Reason: reason, } if evaluationErr != nil { localSubjectAccessReview.Status.EvaluationError = evaluationErr.Error() } return localSubjectAccessReview, nil }
func (c *ErrorReplicationControllers) Update(controller *api.ReplicationController) (*api.ReplicationController, error) { switch { case c.invalid: return nil, kerrors.NewInvalid(api.Kind(controller.Kind), controller.Name, nil) case c.conflict: return nil, kerrors.NewConflict(api.Resource(controller.Kind), controller.Name, nil) } return nil, errors.New("Replication controller update failure") }
func (c *ErrorJobs) Update(job *batch.Job) (*batch.Job, error) { switch { case c.invalid: return nil, kerrors.NewInvalid(api.Kind(job.Kind), job.Name, nil) case c.conflict: return nil, kerrors.NewConflict(api.Resource(job.Kind), job.Name, nil) } return nil, errors.New("Job update failure") }
func (c *ErrorDeployments) Update(deployment *extensions.Deployment) (*extensions.Deployment, error) { switch { case c.invalid: return nil, kerrors.NewInvalid(api.Kind(deployment.Kind), deployment.Name, nil) case c.conflict: return nil, kerrors.NewConflict(api.Resource(deployment.Kind), deployment.Name, nil) } return nil, errors.New("deployment update failure") }
// InterpretWatchError converts a generic error on a watch // operation into the appropriate API error. func InterpretWatchError(err error, resource schema.GroupResource, name string) error { switch { case storage.IsInvalidError(err): invalidError, _ := err.(storage.InvalidError) return errors.NewInvalid(schema.GroupKind{Group: resource.Group, Kind: resource.Resource}, name, invalidError.Errs) default: return err } }
// BeforeCreate ensures that common operations for all resources are performed on creation. It only returns // errors that can be converted to api.Status. It invokes PrepareForCreate, then GenerateName, then Validate. // It returns nil if the object should be created. func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, obj runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !ValidNamespace(ctx, objectMeta) { return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") } } else { objectMeta.Namespace = api.NamespaceNone } objectMeta.DeletionTimestamp = nil objectMeta.DeletionGracePeriodSeconds = nil strategy.PrepareForCreate(ctx, obj) FillObjectMetaSystemFields(ctx, objectMeta) if len(objectMeta.GenerateName) > 0 && len(objectMeta.Name) == 0 { objectMeta.Name = strategy.GenerateName(objectMeta.GenerateName) } // ClusterName is ignored and should not be saved objectMeta.ClusterName = "" if errs := strategy.Validate(ctx, obj); len(errs) > 0 { return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs) } // Custom validation (including name validation) passed // Now run common validation on object meta // Do this *after* custom validation so that specific error messages are shown whenever possible if errs := genericvalidation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 { return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs) } strategy.Canonicalize(obj) return nil }
func TestCheckInvalidErr(t *testing.T) { testCheckError(t, []checkErrTestCase{ { errors.NewInvalid(api.Kind("Invalid1"), "invalidation", field.ErrorList{field.Invalid(field.NewPath("field"), "single", "details")}), "The Invalid1 \"invalidation\" is invalid: field: Invalid value: \"single\": details\n", DefaultErrorExitCode, }, { errors.NewInvalid(api.Kind("Invalid2"), "invalidation", field.ErrorList{field.Invalid(field.NewPath("field1"), "multi1", "details"), field.Invalid(field.NewPath("field2"), "multi2", "details")}), "The Invalid2 \"invalidation\" is invalid: \n* field1: Invalid value: \"multi1\": details\n* field2: Invalid value: \"multi2\": details\n", DefaultErrorExitCode, }, { errors.NewInvalid(api.Kind("Invalid3"), "invalidation", field.ErrorList{}), "The Invalid3 \"invalidation\" is invalid", DefaultErrorExitCode, }, { errors.NewInvalid(api.Kind("Invalid4"), "invalidation", field.ErrorList{field.Invalid(field.NewPath("field4"), "multi4", "details"), field.Invalid(field.NewPath("field4"), "multi4", "details")}), "The Invalid4 \"invalidation\" is invalid: field4: Invalid value: \"multi4\": details\n", DefaultErrorExitCode, }, }) }
func (s strategy) Export(ctx genericapirequest.Context, obj runtime.Object, exact bool) error { t, ok := obj.(*api.Secret) if !ok { // unexpected programmer error return fmt.Errorf("unexpected object: %v", obj) } s.PrepareForCreate(ctx, obj) if exact { return nil } // secrets that are tied to the UID of a service account cannot be exported anyway if t.Type == api.SecretTypeServiceAccountToken || len(t.Annotations[api.ServiceAccountUIDKey]) > 0 { errs := []*field.Error{ field.Invalid(field.NewPath("type"), t, "can not export service account secrets"), } return errors.NewInvalid(api.Kind("Secret"), t.Name, errs) } return nil }
// Get retrieves a runtime.Object that will stream the contents of the pod log func (r *LogREST) Get(ctx genericapirequest.Context, name string, opts runtime.Object) (runtime.Object, error) { logOpts, ok := opts.(*api.PodLogOptions) if !ok { return nil, fmt.Errorf("invalid options object: %#v", opts) } if errs := validation.ValidatePodLogOptions(logOpts); len(errs) > 0 { return nil, errors.NewInvalid(api.Kind("PodLogOptions"), name, errs) } location, transport, err := pod.LogLocation(r.Store, r.KubeletConn, ctx, name, logOpts) if err != nil { return nil, err } return &genericrest.LocationStreamer{ Location: location, Transport: transport, ContentType: "text/plain", Flush: logOpts.Follow, ResponseChecker: genericrest.NewGenericHttpResponseChecker(api.Resource("pods/log"), name), }, nil }
func (r *RollbackREST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { rollback, ok := obj.(*extensions.DeploymentRollback) if !ok { return nil, errors.NewBadRequest(fmt.Sprintf("not a DeploymentRollback: %#v", obj)) } if errs := extvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 { return nil, errors.NewInvalid(extensions.Kind("DeploymentRollback"), rollback.Name, errs) } // Update the Deployment with information in DeploymentRollback to trigger rollback err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations) if err != nil { return nil, err } return &metav1.Status{ Message: fmt.Sprintf("rollback request for deployment %q succeeded", rollback.Name), Code: http.StatusOK, }, nil }
func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { rs, err := r.registry.GetReplicaSet(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(extensions.Resource("replicasets/scale"), name) } oldScale, err := scaleFromReplicaSet(rs) if err != nil { return nil, false, err } obj, err := objInfo.UpdatedObject(ctx, oldScale) if err != nil { return nil, false, err } if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } scale, ok := obj.(*extensions.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) } if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) } rs.Spec.Replicas = scale.Spec.Replicas rs.ResourceVersion = scale.ResourceVersion rs, err = r.registry.UpdateReplicaSet(ctx, rs) if err != nil { return nil, false, err } newScale, err := scaleFromReplicaSet(rs) if err != nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err)) } return newScale, false, err }
func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { deployment, err := r.registry.GetDeployment(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, errors.NewNotFound(extensions.Resource("deployments/scale"), name) } oldScale, err := scaleFromDeployment(deployment) if err != nil { return nil, false, err } obj, err := objInfo.UpdatedObject(ctx, oldScale) if err != nil { return nil, false, err } if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } scale, ok := obj.(*extensions.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", obj)) } if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { return nil, false, errors.NewInvalid(extensions.Kind("Scale"), name, errs) } deployment.Spec.Replicas = scale.Spec.Replicas deployment.ResourceVersion = scale.ResourceVersion deployment, err = r.registry.UpdateDeployment(ctx, deployment) if err != nil { return nil, false, err } newScale, err := scaleFromDeployment(deployment) if err != nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err)) } return newScale, false, nil }
func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a SubjectAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) > 0 { return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs) } authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec) allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ Allowed: allowed, Reason: reason, } if evaluationErr != nil { subjectAccessReview.Status.EvaluationError = evaluationErr.Error() } return subjectAccessReview, nil }
// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns // errors that can be converted to api.Status. It will invoke update validation with the provided existing // and updated objects. func BeforeUpdate(strategy RESTUpdateStrategy, ctx genericapirequest.Context, obj, old runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !ValidNamespace(ctx, objectMeta) { return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") } } else { objectMeta.Namespace = api.NamespaceNone } // Ensure requests cannot update generation oldMeta, err := metav1.ObjectMetaFor(old) if err != nil { return err } objectMeta.Generation = oldMeta.Generation strategy.PrepareForUpdate(ctx, obj, old) // ClusterName is ignored and should not be saved objectMeta.ClusterName = "" // Ensure some common fields, like UID, are validated for all resources. errs, err := validateCommonFields(obj, old) if err != nil { return errors.NewInternalError(err) } errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...) if len(errs) > 0 { return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs) } strategy.Canonicalize(obj) return nil }
func runEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions, editMode EditMode) error { o, err := getPrinter(cmd) if err != nil { return err } mapper, resourceMapper, r, cmdNamespace, err := getMapperAndResult(f, args, options, editMode) if err != nil { return err } clientConfig, err := f.ClientConfig() if err != nil { return err } encoder := f.JSONEncoder() defaultVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } normalEditInfos, err := r.Infos() if err != nil { return err } var ( windowsLineEndings = cmdutil.GetFlagBool(cmd, "windows-line-endings") edit = editor.NewDefaultEditor(f.EditorEnvs()) ) editFn := func(info *resource.Info, err error) error { var ( results = editResults{} original = []byte{} edited = []byte{} file string ) containsError := false var infos []*resource.Info for { switch editMode { case NormalEditMode: infos = normalEditInfos case EditBeforeCreateMode: infos = []*resource.Info{info} default: err = fmt.Errorf("Not supported edit mode %q", editMode) } originalObj, err := resource.AsVersionedObject(infos, false, defaultVersion, encoder) if err != nil { return err } objToEdit := originalObj // generate the file to edit buf := &bytes.Buffer{} var w io.Writer = buf if windowsLineEndings { w = crlf.NewCRLFWriter(w) } if o.addHeader { results.header.writeTo(w) } if !containsError { if err := o.printer.PrintObj(objToEdit, w); err != nil { return preservedFile(err, results.file, errOut) } original = buf.Bytes() } else { // In case of an error, preserve the edited file. // Remove the comments (header) from it since we already // have included the latest header in the buffer above. buf.Write(manualStrip(edited)) } // launch the editor editedDiff := edited edited, file, err = edit.LaunchTempFile(fmt.Sprintf("%s-edit-", filepath.Base(os.Args[0])), o.ext, buf) if err != nil { return preservedFile(err, results.file, errOut) } if editMode == NormalEditMode || containsError { if bytes.Equal(stripComments(editedDiff), stripComments(edited)) { // Ugly hack right here. We will hit this either (1) when we try to // save the same changes we tried to save in the previous iteration // which means our changes are invalid or (2) when we exit the second // time. The second case is more usual so we can probably live with it. // TODO: A less hacky fix would be welcome :) return preservedFile(fmt.Errorf("%s", "Edit cancelled, no valid changes were saved."), file, errOut) } } // cleanup any file from the previous pass if len(results.file) > 0 { os.Remove(results.file) } glog.V(4).Infof("User edited:\n%s", string(edited)) // Apply validation schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return preservedFile(err, file, errOut) } err = schema.ValidateBytes(stripComments(edited)) if err != nil { results = editResults{ file: file, } containsError = true fmt.Fprintln(out, results.addError(errors.NewInvalid(api.Kind(""), "", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0])) continue } // Compare content without comments if bytes.Equal(stripComments(original), stripComments(edited)) { os.Remove(file) fmt.Fprintln(errOut, "Edit cancelled, no changes made.") return nil } lines, err := hasLines(bytes.NewBuffer(edited)) if err != nil { return preservedFile(err, file, errOut) } if !lines { os.Remove(file) fmt.Fprintln(errOut, "Edit cancelled, saved file was empty.") return nil } results = editResults{ file: file, } // parse the edited file updates, err := resourceMapper.InfoForData(edited, "edited-file") if err != nil { // syntax error containsError = true results.header.reasons = append(results.header.reasons, editReason{head: fmt.Sprintf("The edited file had a syntax error: %v", err)}) continue } // not a syntax error as it turns out... containsError = false namespaceVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) // need to make sure the original namespace wasn't changed while editing if err = namespaceVisitor.Visit(resource.RequireNamespace(cmdNamespace)); err != nil { return preservedFile(err, file, errOut) } // iterate through all items to apply annotations mutatedObjects, err := visitAnnotation(cmd, f, updates, resourceMapper, encoder) if err != nil { return preservedFile(err, file, errOut) } // if we mutated a list in the visitor, persist the changes on the overall object if meta.IsListType(updates.Object) { meta.SetList(updates.Object, mutatedObjects) } switch editMode { case NormalEditMode: err = visitToPatch(originalObj, updates, mapper, resourceMapper, encoder, out, errOut, defaultVersion, &results, file) case EditBeforeCreateMode: err = visitToCreate(updates, mapper, resourceMapper, out, errOut, defaultVersion, &results, file) default: err = fmt.Errorf("Not supported edit mode %q", editMode) } if err != nil { return preservedFile(err, results.file, errOut) } // Handle all possible errors // // 1. retryable: propose kubectl replace -f // 2. notfound: indicate the location of the saved configuration of the deleted resource // 3. invalid: retry those on the spot by looping ie. reloading the editor if results.retryable > 0 { fmt.Fprintf(errOut, "You can run `%s replace -f %s` to try this update again.\n", filepath.Base(os.Args[0]), file) return cmdutil.ErrExit } if results.notfound > 0 { fmt.Fprintf(errOut, "The edits you made on deleted resources have been saved to %q\n", file) return cmdutil.ErrExit } if len(results.edit) == 0 { if results.notfound == 0 { os.Remove(file) } else { fmt.Fprintf(out, "The edits you made on deleted resources have been saved to %q\n", file) } return nil } if len(results.header.reasons) > 0 { containsError = true } } } switch editMode { // If doing normal edit we cannot use Visit because we need to edit a list for convenience. Ref: #20519 case NormalEditMode: return editFn(nil, nil) // If doing an edit before created, we don't want a list and instead want the normal behavior as kubectl create. case EditBeforeCreateMode: return r.Visit(editFn) default: return fmt.Errorf("Not supported edit mode %q", editMode) } }
func (rs *REST) healthCheckNodePortUpdate(oldService, service *api.Service) (bool, error) { // Health Check Node Port handling during updates // // Case 1. Transition from globalTraffic to OnlyLocal for the ESIPP annotation // // Allocate a health check node port or attempt to reserve the user-specified one, if provided. // Insert health check node port as an annotation into the service's annotations // // Case 2. Transition from OnlyLocal to Global for the ESIPP annotation // // Free the existing healthCheckNodePort and clear the health check nodePort annotation // // Case 3. No change (Global ---stays--> Global) but prevent invalid annotation manipulations // // Reject insertion of the "service.alpha.kubernetes.io/healthcheck-nodeport" annotation // // Case 4. No change (OnlyLocal ---stays--> OnlyLocal) but prevent invalid annotation manipulations // // Reject deletion of the "service.alpha.kubernetes.io/healthcheck-nodeport" annotation // Reject changing the value of the healthCheckNodePort annotation // oldServiceHasHealthCheckNodePort := shouldCheckOrAssignHealthCheckNodePort(oldService) oldHealthCheckNodePort := apiservice.GetServiceHealthCheckNodePort(oldService) assignHealthCheckNodePort := shouldCheckOrAssignHealthCheckNodePort(service) requestedHealthCheckNodePort := apiservice.GetServiceHealthCheckNodePort(service) switch { case !oldServiceHasHealthCheckNodePort && assignHealthCheckNodePort: glog.Infof("Transition from Global LB service to OnlyLocal service") if requestedHealthCheckNodePort > 0 { // If the request has a health check nodePort in mind, attempt to reserve it err := rs.serviceNodePorts.Allocate(int(requestedHealthCheckNodePort)) if err != nil { errmsg := fmt.Sprintf("Failed to allocate requested HealthCheck nodePort %v:%v", requestedHealthCheckNodePort, err) el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"), apiservice.BetaAnnotationHealthCheckNodePort, errmsg)} return false, errors.NewInvalid(api.Kind("Service"), service.Name, el) } glog.Infof("Reserved user requested nodePort: %d", requestedHealthCheckNodePort) } else { // If the request has no health check nodePort specified, allocate any healthCheckNodePort, err := rs.serviceNodePorts.AllocateNext() if err != nil { // TODO: what error should be returned here? It's not a // field-level validation failure (the field is valid), and it's // not really an internal error. return false, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) } // Insert the newly allocated health check port as an annotation (plan of record for Alpha) service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort] = fmt.Sprintf("%d", healthCheckNodePort) glog.Infof("Reserved health check nodePort: %d", healthCheckNodePort) } case oldServiceHasHealthCheckNodePort && !assignHealthCheckNodePort: glog.Infof("Transition from OnlyLocal LB service to Global service") err := rs.serviceNodePorts.Release(int(oldHealthCheckNodePort)) if err != nil { glog.Warningf("Error releasing service health check %s node port %d: %v", service.Name, oldHealthCheckNodePort, err) return false, errors.NewInternalError(fmt.Errorf("failed to free health check nodePort: %v", err)) } else { delete(service.Annotations, apiservice.BetaAnnotationHealthCheckNodePort) delete(service.Annotations, apiservice.AlphaAnnotationHealthCheckNodePort) glog.Infof("Freed health check nodePort: %d", oldHealthCheckNodePort) } case !oldServiceHasHealthCheckNodePort && !assignHealthCheckNodePort: if _, ok := service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort]; ok { glog.Warningf("Attempt to insert health check node port annotation DENIED") el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"), apiservice.BetaAnnotationHealthCheckNodePort, "Cannot insert healthcheck nodePort annotation")} return false, errors.NewInvalid(api.Kind("Service"), service.Name, el) } case oldServiceHasHealthCheckNodePort && assignHealthCheckNodePort: if _, ok := service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort]; !ok { glog.Warningf("Attempt to delete health check node port annotation DENIED") el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"), apiservice.BetaAnnotationHealthCheckNodePort, "Cannot delete healthcheck nodePort annotation")} return false, errors.NewInvalid(api.Kind("Service"), service.Name, el) } if oldHealthCheckNodePort != requestedHealthCheckNodePort { glog.Warningf("Attempt to change value of health check node port annotation DENIED") el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"), apiservice.BetaAnnotationHealthCheckNodePort, "Cannot change healthcheck nodePort during update")} return false, errors.NewInvalid(api.Kind("Service"), service.Name, el) } } return true, nil }
// Update performs an atomic update and set of the object. Returns the result of the update // or an error. If the registry allows create-on-update, the create flow will be executed. // A bool is returned along with the object and any errors, to indicate object creation. func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { key, err := e.KeyFunc(ctx, name) if err != nil { return nil, false, err } var ( creatingObj runtime.Object creating = false ) storagePreconditions := &storage.Preconditions{} if preconditions := objInfo.Preconditions(); preconditions != nil { storagePreconditions.UID = preconditions.UID } out := e.NewFunc() // deleteObj is only used in case a deletion is carried out var deleteObj runtime.Object err = e.Storage.GuaranteedUpdate(ctx, key, out, true, storagePreconditions, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { // Given the existing object, get the new object obj, err := objInfo.UpdatedObject(ctx, existing) if err != nil { return nil, nil, err } // If AllowUnconditionalUpdate() is true and the object specified by the user does not have a resource version, // then we populate it with the latest version. // Else, we check that the version specified by the user matches the version of latest storage object. resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, nil, err } doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() version, err := e.Storage.Versioner().ObjectResourceVersion(existing) if err != nil { return nil, nil, err } if version == 0 { if !e.UpdateStrategy.AllowCreateOnUpdate() { return nil, nil, kubeerr.NewNotFound(e.QualifiedResource, name) } creating = true creatingObj = obj if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { return nil, nil, err } ttl, err := e.calculateTTL(obj, 0, false) if err != nil { return nil, nil, err } return obj, &ttl, nil } creating = false creatingObj = nil if doUnconditionalUpdate { // Update the object's resource version to match the latest storage object's resource version. err = e.Storage.Versioner().UpdateObject(obj, res.ResourceVersion) if err != nil { return nil, nil, err } } else { // Check if the object's resource version matches the latest resource version. newVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, nil, err } if newVersion == 0 { // TODO: The Invalid error should has a field for Resource. // After that field is added, we should fill the Resource and // leave the Kind field empty. See the discussion in #18526. qualifiedKind := schema.GroupKind{Group: e.QualifiedResource.Group, Kind: e.QualifiedResource.Resource} fieldErrList := field.ErrorList{field.Invalid(field.NewPath("metadata").Child("resourceVersion"), newVersion, "must be specified for an update")} return nil, nil, kubeerr.NewInvalid(qualifiedKind, name, fieldErrList) } if newVersion != version { return nil, nil, kubeerr.NewConflict(e.QualifiedResource, name, fmt.Errorf(OptimisticLockErrorMsg)) } } if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil { return nil, nil, err } delete := e.shouldDelete(ctx, key, obj, existing) if delete { deleteObj = obj return nil, nil, errEmptiedFinalizers } ttl, err := e.calculateTTL(obj, res.TTL, true) if err != nil { return nil, nil, err } if int64(ttl) != res.TTL { return obj, &ttl, nil } return obj, nil, nil }) if err != nil { // delete the object if err == errEmptiedFinalizers { return e.deleteForEmptyFinalizers(ctx, name, key, deleteObj, storagePreconditions) } if creating { err = storeerr.InterpretCreateError(err, e.QualifiedResource, name) err = rest.CheckGeneratedNameError(e.CreateStrategy, err, creatingObj) } else { err = storeerr.InterpretUpdateError(err, e.QualifiedResource, name) } return nil, false, err } if creating { if e.AfterCreate != nil { if err := e.AfterCreate(out); err != nil { return nil, false, err } } } else { if e.AfterUpdate != nil { if err := e.AfterUpdate(out); err != nil { return nil, false, err } } } if e.Decorator != nil { if err := e.Decorator(out); err != nil { return nil, false, err } } return out, creating, nil }
func (rs *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { oldService, err := rs.registry.GetService(ctx, name, &metav1.GetOptions{}) if err != nil { return nil, false, err } obj, err := objInfo.UpdatedObject(ctx, oldService) if err != nil { return nil, false, err } service := obj.(*api.Service) if !rest.ValidNamespace(ctx, &service.ObjectMeta) { return nil, false, errors.NewConflict(api.Resource("services"), service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context")) } // Copy over non-user fields // TODO: make this a merge function if errs := validation.ValidateServiceUpdate(service, oldService); len(errs) > 0 { return nil, false, errors.NewInvalid(api.Kind("Service"), service.Name, errs) } nodePortOp := portallocator.StartOperation(rs.serviceNodePorts) defer nodePortOp.Finish() assignNodePorts := shouldAssignNodePorts(service) oldNodePorts := CollectServiceNodePorts(oldService) newNodePorts := []int{} if assignNodePorts { for i := range service.Spec.Ports { servicePort := &service.Spec.Ports[i] nodePort := int(servicePort.NodePort) if nodePort != 0 { if !contains(oldNodePorts, nodePort) { err := nodePortOp.Allocate(nodePort) if err != nil { el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), nodePort, err.Error())} return nil, false, errors.NewInvalid(api.Kind("Service"), service.Name, el) } } } else { nodePort, err = nodePortOp.AllocateNext() if err != nil { // TODO: what error should be returned here? It's not a // field-level validation failure (the field is valid), and it's // not really an internal error. return nil, false, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) } servicePort.NodePort = int32(nodePort) } // Detect duplicate node ports; this should have been caught by validation, so we panic if contains(newNodePorts, nodePort) { panic("duplicate node port") } newNodePorts = append(newNodePorts, nodePort) } } else { // Validate should have validated that nodePort == 0 } // The comparison loops are O(N^2), but we don't expect N to be huge // (there's a hard-limit at 2^16, because they're ports; and even 4 ports would be a lot) for _, oldNodePort := range oldNodePorts { if !contains(newNodePorts, oldNodePort) { continue } nodePortOp.ReleaseDeferred(oldNodePort) } // Remove any LoadBalancerStatus now if Type != LoadBalancer; // although loadbalancer delete is actually asynchronous, we don't need to expose the user to that complexity. if service.Spec.Type != api.ServiceTypeLoadBalancer { service.Status.LoadBalancer = api.LoadBalancerStatus{} } success, err := rs.healthCheckNodePortUpdate(oldService, service) if !success { return nil, false, err } out, err := rs.registry.UpdateService(ctx, service) if err == nil { el := nodePortOp.Commit() if el != nil { // problems should be fixed by an eventual reconciliation / restart glog.Errorf("error(s) committing NodePorts changes: %v", el) } } return out, false, err }
func (rs *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { service := obj.(*api.Service) if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, err } // TODO: this should probably move to strategy.PrepareForCreate() releaseServiceIP := false defer func() { if releaseServiceIP { if api.IsServiceIPSet(service) { rs.serviceIPs.Release(net.ParseIP(service.Spec.ClusterIP)) } } }() nodePortOp := portallocator.StartOperation(rs.serviceNodePorts) defer nodePortOp.Finish() if api.IsServiceIPRequested(service) { // Allocate next available. ip, err := rs.serviceIPs.AllocateNext() if err != nil { // TODO: what error should be returned here? It's not a // field-level validation failure (the field is valid), and it's // not really an internal error. return nil, errors.NewInternalError(fmt.Errorf("failed to allocate a serviceIP: %v", err)) } service.Spec.ClusterIP = ip.String() releaseServiceIP = true } else if api.IsServiceIPSet(service) { // Try to respect the requested IP. if err := rs.serviceIPs.Allocate(net.ParseIP(service.Spec.ClusterIP)); err != nil { // TODO: when validation becomes versioned, this gets more complicated. el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIP"), service.Spec.ClusterIP, err.Error())} return nil, errors.NewInvalid(api.Kind("Service"), service.Name, el) } releaseServiceIP = true } assignNodePorts := shouldAssignNodePorts(service) svcPortToNodePort := map[int]int{} for i := range service.Spec.Ports { servicePort := &service.Spec.Ports[i] allocatedNodePort := svcPortToNodePort[int(servicePort.Port)] if allocatedNodePort == 0 { // This will only scan forward in the service.Spec.Ports list because any matches // before the current port would have been found in svcPortToNodePort. This is really // looking for any user provided values. np := findRequestedNodePort(int(servicePort.Port), service.Spec.Ports) if np != 0 { err := nodePortOp.Allocate(np) if err != nil { // TODO: when validation becomes versioned, this gets more complicated. el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), np, err.Error())} return nil, errors.NewInvalid(api.Kind("Service"), service.Name, el) } servicePort.NodePort = int32(np) svcPortToNodePort[int(servicePort.Port)] = np } else if assignNodePorts { nodePort, err := nodePortOp.AllocateNext() if err != nil { // TODO: what error should be returned here? It's not a // field-level validation failure (the field is valid), and it's // not really an internal error. return nil, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) } servicePort.NodePort = int32(nodePort) svcPortToNodePort[int(servicePort.Port)] = nodePort } } else if int(servicePort.NodePort) != allocatedNodePort { if servicePort.NodePort == 0 { servicePort.NodePort = int32(allocatedNodePort) } else { err := nodePortOp.Allocate(int(servicePort.NodePort)) if err != nil { // TODO: when validation becomes versioned, this gets more complicated. el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), servicePort.NodePort, err.Error())} return nil, errors.NewInvalid(api.Kind("Service"), service.Name, el) } } } } if shouldCheckOrAssignHealthCheckNodePort(service) { var healthCheckNodePort int var err error if l, ok := service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort]; ok { healthCheckNodePort, err = strconv.Atoi(l) if err != nil || healthCheckNodePort <= 0 { return nil, errors.NewInternalError(fmt.Errorf("Failed to parse annotation %v: %v", apiservice.BetaAnnotationHealthCheckNodePort, err)) } } if healthCheckNodePort > 0 { // If the request has a health check nodePort in mind, attempt to reserve it err := nodePortOp.Allocate(int(healthCheckNodePort)) if err != nil { return nil, errors.NewInternalError(fmt.Errorf("Failed to allocate requested HealthCheck nodePort %v: %v", healthCheckNodePort, err)) } } else { // If the request has no health check nodePort specified, allocate any healthCheckNodePort, err = nodePortOp.AllocateNext() if err != nil { // TODO: what error should be returned here? It's not a // field-level validation failure (the field is valid), and it's // not really an internal error. return nil, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) } // Insert the newly allocated health check port as an annotation (plan of record for Alpha) service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort] = fmt.Sprintf("%d", healthCheckNodePort) } } out, err := rs.registry.CreateService(ctx, service) if err != nil { err = rest.CheckGeneratedNameError(Strategy, err, service) } if err == nil { el := nodePortOp.Commit() if el != nil { // these should be caught by an eventual reconciliation / restart glog.Errorf("error(s) committing service node-ports changes: %v", el) } releaseServiceIP = false } return out, err }