func getAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) { event, ok := obj.(*api.Event) if !ok { return nil, nil, errors.NewInternalError(fmt.Errorf("object is not of type event: %#v", obj)) } l := event.Labels if l == nil { l = labels.Set{} } objectMetaFieldsSet := generic.ObjectMetaFieldsSet(event.ObjectMeta, true) specificFieldsSet := fields.Set{ "involvedObject.kind": event.InvolvedObject.Kind, "involvedObject.namespace": event.InvolvedObject.Namespace, "involvedObject.name": event.InvolvedObject.Name, "involvedObject.uid": string(event.InvolvedObject.UID), "involvedObject.apiVersion": event.InvolvedObject.APIVersion, "involvedObject.resourceVersion": event.InvolvedObject.ResourceVersion, "involvedObject.fieldPath": event.InvolvedObject.FieldPath, "reason": event.Reason, "source": event.Source.Component, "type": event.Type, } return l, generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet), 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 api.Context, obj, old runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !api.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 } strategy.PrepareForUpdate(obj, old) // 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 }
// NewNotFound is a utility function to return a well-formatted admission control error response func NewNotFound(a Attributes) error { name, resource, err := extractResourceName(a) if err != nil { return apierrors.NewInternalError(err) } return apierrors.NewNotFound(resource, name) }
// NewForbidden is a utility function to return a well-formatted admission control error response func NewForbidden(a Attributes, internalError error) error { // do not double wrap an error of same type if apierrors.IsForbidden(internalError) { return internalError } name, resource, err := extractResourceName(a) if err != nil { return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err})) } return apierrors.NewForbidden(resource, name, internalError) }
func hasUID(obj runtime.Object) (bool, error) { if obj == nil { return false, nil } accessor, err := meta.Accessor(obj) if err != nil { return false, errors.NewInternalError(err) } if len(accessor.GetUID()) == 0 { return false, nil } return true, nil }
// InterpretDeleteError converts a generic error on a delete // operation into the appropriate API error. func InterpretDeleteError(err error, qualifiedResource unversioned.GroupResource, name string) error { switch { case storage.IsNotFound(err): return errors.NewNotFound(qualifiedResource, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "delete", 2) // TODO: make configurable or handled at a higher level case storage.IsTestFailed(err), storage.IsNodeExist(err): return errors.NewConflict(qualifiedResource, name, err) case storage.IsInternalError(err): return errors.NewInternalError(err) default: return err } }
func TestGenericHttpResponseChecker(t *testing.T) { responseChecker := NewGenericHttpResponseChecker(api.Resource("pods"), "foo") tests := []struct { resp *http.Response expectError bool expected error name string }{ { resp: &http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString("Success")), StatusCode: http.StatusOK, }, expectError: false, name: "ok", }, { resp: &http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString("Invalid request.")), StatusCode: http.StatusBadRequest, }, expectError: true, expected: errors.NewBadRequest("Invalid request."), name: "bad request", }, { resp: &http.Response{ Body: ioutil.NopCloser(bytes.NewBufferString("Pod does not exist.")), StatusCode: http.StatusInternalServerError, }, expectError: true, expected: errors.NewInternalError(fmt.Errorf("%s", "Pod does not exist.")), name: "internal server error", }, } for _, test := range tests { err := responseChecker.Check(test.resp) if test.expectError && err == nil { t.Error("unexpected non-error") } if !test.expectError && err != nil { t.Errorf("unexpected error: %v", err) } if test.expectError && !reflect.DeepEqual(err, test.expected) { t.Errorf("expected: %s, saw: %s", test.expected, err) } } }
// UpdateREST registers the REST handlers for this APIGroupVersion to an existing web service // in the restful Container. It will use the prefix (root/version) to find the existing // web service. If a web service does not exist within the container to support the prefix // this method will return an error. func (g *APIGroupVersion) UpdateREST(container *restful.Container) error { installer := g.newInstaller() var ws *restful.WebService = nil for i, s := range container.RegisteredWebServices() { if s.RootPath() == installer.prefix { ws = container.RegisteredWebServices()[i] break } } if ws == nil { return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix)) } apiResources, registrationErrors := installer.Install(ws) AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, apiResources) return utilerrors.NewAggregate(registrationErrors) }
func (rs *REST) Create(ctx api.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) for i := range service.Spec.Ports { servicePort := &service.Spec.Ports[i] if servicePort.NodePort != 0 { 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) } } 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) } } 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 }
func (rs *REST) Update(ctx api.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { oldService, err := rs.registry.GetService(ctx, name) 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 !api.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{} } 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 }
// Serve serves a series of encoded events via HTTP with Transfer-Encoding: chunked // or over a websocket connection. func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { w = httplog.Unlogged(w) if wsstream.IsWebSocketRequest(req) { w.Header().Set("Content-Type", s.mediaType) websocket.Handler(s.HandleWS).ServeHTTP(w, req) return } cn, ok := w.(http.CloseNotifier) if !ok { err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w) utilruntime.HandleError(err) s.scope.err(errors.NewInternalError(err), w, req) return } flusher, ok := w.(http.Flusher) if !ok { err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w) utilruntime.HandleError(err) s.scope.err(errors.NewInternalError(err), w, req) return } framer := s.framer.NewFrameWriter(w) if framer == nil { // programmer error err := fmt.Errorf("no stream framing support is available for media type %q", s.mediaType) utilruntime.HandleError(err) s.scope.err(errors.NewBadRequest(err.Error()), w, req) return } e := streaming.NewEncoder(framer, s.encoder) // ensure the connection times out timeoutCh, cleanup := s.t.TimeoutCh() defer cleanup() defer s.watching.Stop() // begin the stream w.Header().Set("Content-Type", s.mediaType) w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) flusher.Flush() var unknown runtime.Unknown internalEvent := &versioned.InternalEvent{} buf := &bytes.Buffer{} ch := s.watching.ResultChan() for { select { case <-cn.CloseNotify(): return case <-timeoutCh: return case event, ok := <-ch: if !ok { // End of results. return } obj := event.Object s.fixup(obj) if err := s.embeddedEncoder.Encode(obj, buf); err != nil { // unexpected error utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err)) return } // ContentType is not required here because we are defaulting to the serializer // type unknown.Raw = buf.Bytes() event.Object = &unknown // the internal event will be versioned by the encoder *internalEvent = versioned.InternalEvent(event) if err := e.Encode(internalEvent); err != nil { utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v (%#v)", err, e)) // client disconnect. return } if len(ch) == 0 { flusher.Flush() } buf.Reset() } } }
// Delete enforces life-cycle rules for namespace termination func (r *REST) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) { nsObj, err := r.Get(ctx, name) if err != nil { return nil, err } namespace := nsObj.(*api.Namespace) // Ensure we have a UID precondition if options == nil { options = api.NewDeleteOptions(0) } if options.Preconditions == nil { options.Preconditions = &api.Preconditions{} } if options.Preconditions.UID == nil { options.Preconditions.UID = &namespace.UID } else if *options.Preconditions.UID != namespace.UID { err = apierrors.NewConflict( api.Resource("namespaces"), name, fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *options.Preconditions.UID, namespace.UID), ) return nil, err } // upon first request to delete, we switch the phase to start namespace termination // TODO: enhance graceful deletion's calls to DeleteStrategy to allow phase change and finalizer patterns if namespace.DeletionTimestamp.IsZero() { key, err := r.Store.KeyFunc(ctx, name) if err != nil { return nil, err } preconditions := storage.Preconditions{UID: options.Preconditions.UID} out := r.Store.NewFunc() err = r.Store.Storage.GuaranteedUpdate( ctx, key, out, false, &preconditions, storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) { existingNamespace, ok := existing.(*api.Namespace) if !ok { // wrong type return nil, fmt.Errorf("expected *api.Namespace, got %v", existing) } // Set the deletion timestamp if needed if existingNamespace.DeletionTimestamp.IsZero() { now := unversioned.Now() existingNamespace.DeletionTimestamp = &now } // Set the namespace phase to terminating, if needed if existingNamespace.Status.Phase != api.NamespaceTerminating { existingNamespace.Status.Phase = api.NamespaceTerminating } return existingNamespace, nil }), ) if err != nil { err = storageerr.InterpretGetError(err, api.Resource("namespaces"), name) err = storageerr.InterpretUpdateError(err, api.Resource("namespaces"), name) if _, ok := err.(*apierrors.StatusError); !ok { err = apierrors.NewInternalError(err) } return nil, err } return out, nil } // prior to final deletion, we must ensure that finalizers is empty if len(namespace.Spec.Finalizers) != 0 { err = apierrors.NewConflict(api.Resource("namespaces"), namespace.Name, fmt.Errorf("The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system.")) return nil, err } return r.Store.Delete(ctx, name, options) }