// nameIndexFunc is an index function that indexes based on an object's name func nameIndexFunc(obj interface{}) ([]string, error) { meta, err := meta.Accessor(obj) if err != nil { return []string{""}, fmt.Errorf("object has no meta: %v", err) } return []string{meta.Name()}, nil }
// watchHandler watches w and keeps *resourceVersion up to date. func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, resyncCh <-chan time.Time, stopCh <-chan struct{}) error { start := time.Now() eventCount := 0 // Stopping the watcher should be idempotent and if we return from this function there's no way // we're coming back in with the same watch interface. defer w.Stop() loop: for { select { case <-stopCh: return errorStopRequested case <-resyncCh: return errorResyncRequested case event, ok := <-w.ResultChan(): if !ok { break loop } if event.Type == watch.Error { return apierrs.FromObject(event.Object) } if e, a := r.expectedType, reflect.TypeOf(event.Object); e != nil && e != a { util.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a)) continue } meta, err := meta.Accessor(event.Object) if err != nil { util.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) continue } newResourceVersion := meta.ResourceVersion() switch event.Type { case watch.Added: r.store.Add(event.Object) case watch.Modified: r.store.Update(event.Object) case watch.Deleted: // TODO: Will any consumers need access to the "last known // state", which is passed in event.Object? If so, may need // to change this. r.store.Delete(event.Object) default: util.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) } *resourceVersion = newResourceVersion r.setLastSyncResourceVersion(newResourceVersion) eventCount++ } } watchDuration := time.Now().Sub(start) if watchDuration < 1*time.Second && eventCount == 0 { glog.V(4).Infof("%s: Unexpected watch close - watch lasted less than a second and no items received", r.name) return errors.New("very short watch") } glog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedType, eventCount) return nil }
// GetReference returns an ObjectReference which refers to the given // object, or an error if the object doesn't follow the conventions // that would allow this. // TODO: should take a meta.Interface see http://issue.k8s.io/7127 func GetReference(obj runtime.Object) (*ObjectReference, error) { if obj == nil { return nil, ErrNilObject } if ref, ok := obj.(*ObjectReference); ok { // Don't make a reference to a reference. return ref, nil } meta, err := meta.Accessor(obj) if err != nil { return nil, err } // if the object referenced is actually persisted, we can just get kind from meta // if we are building an object reference to something not yet persisted, we should fallback to scheme kind := meta.Kind() if kind == "" { _, kind, err = Scheme.ObjectVersionAndKind(obj) if err != nil { return nil, err } } // if the object referenced is actually persisted, we can also get version from meta version := meta.APIVersion() if version == "" { selfLink := meta.SelfLink() if selfLink == "" { if ForTesting_ReferencesAllowBlankSelfLinks { version = "testing" } else { return nil, ErrNoSelfLink } } else { selfLinkUrl, err := url.Parse(selfLink) if err != nil { return nil, err } // example paths: /<prefix>/<version>/* parts := strings.Split(selfLinkUrl.Path, "/") if len(parts) < 3 { return nil, fmt.Errorf("unexpected self link format: '%v'; got version '%v'", selfLink, version) } version = parts[2] } } return &ObjectReference{ Kind: kind, APIVersion: version, Name: meta.Name(), Namespace: meta.Namespace(), UID: meta.UID(), ResourceVersion: meta.ResourceVersion(), }, nil }
func NoNamespaceKeyFunc(prefix string, obj runtime.Object) (string, error) { meta, err := meta.Accessor(obj) if err != nil { return "", err } name := meta.Name() if ok, msg := validation.ValidatePathSegmentName(name, false); !ok { return "", fmt.Errorf("invalid name: %v", msg) } return prefix + "/" + meta.Name(), nil }
// MetaNamespaceKeyFunc is a convenient default KeyFunc which knows how to make // keys for API objects which implement meta.Interface. // The key uses the format <namespace>/<name> unless <namespace> is empty, then // it's just <name>. // // TODO: replace key-as-string with a key-as-struct so that this // packing/unpacking won't be necessary. func MetaNamespaceKeyFunc(obj interface{}) (string, error) { if key, ok := obj.(ExplicitKey); ok { return string(key), nil } meta, err := meta.Accessor(obj) if err != nil { return "", fmt.Errorf("object has no meta: %v", err) } if len(meta.Namespace()) > 0 { return meta.Namespace() + "/" + meta.Name(), nil } return meta.Name(), nil }
func objectToVersionedRuntimeObject(obj interface{}) (runtime.Object, uint64, error) { object, ok := obj.(runtime.Object) if !ok { return nil, 0, fmt.Errorf("obj does not implement runtime.Object interface: %v", obj) } meta, err := meta.Accessor(object) if err != nil { return nil, 0, err } resourceVersion, err := parseResourceVersion(meta.ResourceVersion()) if err != nil { return nil, 0, err } return object, resourceVersion, nil }
// ExtractFieldPathAsString extracts the field from the given object // and returns it as a string. The object must be a pointer to an // API type. // // Currently, this API is limited to supporting the fieldpaths: // // 1. metadata.name - The name of an API object // 2. metadata.namespace - The namespace of an API object func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) { accessor, err := meta.Accessor(obj) if err != nil { return "", nil } switch fieldPath { case "metadata.annotations": return formatMap(accessor.Annotations()), nil case "metadata.labels": return formatMap(accessor.Labels()), nil case "metadata.name": return accessor.Name(), nil case "metadata.namespace": return accessor.Namespace(), nil } return "", fmt.Errorf("Unsupported fieldPath: %v", fieldPath) }
func TestFiltering(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) prefixedKey := etcdtest.AddPrefix("pods") fakeClient.ExpectNotFoundGet(prefixedKey) cacher := newTestCacher(fakeClient) fakeClient.WaitForWatchCompletion() podFoo := makeTestPod("foo") podFoo.ObjectMeta.Labels = map[string]string{"filter": "foo"} podFooFiltered := makeTestPod("foo") testCases := []struct { object *api.Pod etcdResponse *etcd.Response filtered bool event watch.EventType }{ { object: podFoo, etcdResponse: &etcd.Response{ Action: "create", Node: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFoo)), CreatedIndex: 1, ModifiedIndex: 1, }, }, filtered: true, event: watch.Added, }, { object: podFooFiltered, etcdResponse: &etcd.Response{ Action: "set", Node: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFooFiltered)), CreatedIndex: 1, ModifiedIndex: 2, }, PrevNode: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFoo)), CreatedIndex: 1, ModifiedIndex: 1, }, }, filtered: true, // Deleted, because the new object doesn't match filter. event: watch.Deleted, }, { object: podFoo, etcdResponse: &etcd.Response{ Action: "set", Node: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFoo)), CreatedIndex: 1, ModifiedIndex: 3, }, PrevNode: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFooFiltered)), CreatedIndex: 1, ModifiedIndex: 2, }, }, filtered: true, // Added, because the previous object didn't match filter. event: watch.Added, }, { object: podFoo, etcdResponse: &etcd.Response{ Action: "set", Node: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFoo)), CreatedIndex: 1, ModifiedIndex: 4, }, PrevNode: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFoo)), CreatedIndex: 1, ModifiedIndex: 3, }, }, filtered: true, event: watch.Modified, }, { object: podFoo, etcdResponse: &etcd.Response{ Action: "delete", Node: &etcd.Node{ CreatedIndex: 1, ModifiedIndex: 5, }, PrevNode: &etcd.Node{ Value: string(runtime.EncodeOrDie(testapi.Default.Codec(), podFoo)), CreatedIndex: 1, ModifiedIndex: 4, }, }, filtered: true, event: watch.Deleted, }, } // Set up Watch for object "podFoo" with label filter set. selector := labels.SelectorFromSet(labels.Set{"filter": "foo"}) filter := func(obj runtime.Object) bool { metadata, err := meta.Accessor(obj) if err != nil { t.Errorf("unexpected error: %v", err) return false } return selector.Matches(labels.Set(metadata.Labels())) } watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", 1, filter) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, test := range testCases { fakeClient.WatchResponse <- test.etcdResponse if test.filtered { event := <-watcher.ResultChan() if e, a := test.event, event.Type; e != a { t.Errorf("%v %v", e, a) } // unset fields that are set by the infrastructure obj := event.Object.(*api.Pod) obj.ObjectMeta.ResourceVersion = "" obj.ObjectMeta.CreationTimestamp = unversioned.Time{} if e, a := test.object, obj; !reflect.DeepEqual(e, a) { t.Errorf("expected: %#v, got: %#v", e, a) } } } close(fakeClient.WatchResponse) }
// Returns error if ListAndWatch didn't even tried to initialize watch. func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error { var resourceVersion string resyncCh, cleanup := r.resyncChan() defer cleanup() if r.verbose { glog.Infof("watch %v: listwatch cycle started.", r.name) defer glog.Infof("watch %v: listwatch cycle exited.", r.name) } list, err := r.listerWatcher.List() if err != nil { return fmt.Errorf("%s: Failed to list %v: %v", r.name, r.expectedType, err) } meta, err := meta.Accessor(list) if err != nil { return fmt.Errorf("%s: Unable to understand list result %#v", r.name, list) } resourceVersion = meta.ResourceVersion() items, err := runtime.ExtractList(list) if err != nil { return fmt.Errorf("%s: Unable to understand list result %#v (%v)", r.name, list, err) } if err := r.syncWith(items, resourceVersion); err != nil { return fmt.Errorf("%s: Unable to sync list result: %v", r.name, err) } r.setLastSyncResourceVersion(resourceVersion) for { w, err := r.listerWatcher.Watch(resourceVersion) if err != nil { switch err { case io.EOF: // watch closed normally case io.ErrUnexpectedEOF: glog.V(1).Infof("%s: Watch for %v closed with unexpected EOF: %v", r.name, r.expectedType, err) default: util.HandleError(fmt.Errorf("%s: Failed to watch %v: %v", r.name, r.expectedType, err)) } // If this is "connection refused" error, it means that most likely apiserver is not responsive. // It doesn't make sense to re-list all objects because most likely we will be able to restart // watch where we ended. // If that's the case wait and resend watch request. if urlError, ok := err.(*url.Error); ok { if opError, ok := urlError.Err.(*net.OpError); ok { if errno, ok := opError.Err.(syscall.Errno); ok && errno == syscall.ECONNREFUSED { time.Sleep(time.Second) continue } } } return nil } if err := r.watchHandler(w, &resourceVersion, resyncCh, stopCh); err != nil { if err != errorResyncRequested && err != errorStopRequested { glog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err) } return nil } } }
// Returns error if ListAndWatch didn't even tried to initialize watch. func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error { var resourceVersion string resyncCh, cleanup := r.resyncChan() defer cleanup() list, err := r.listerWatcher.List() if err != nil { return fmt.Errorf("%s: Failed to list %v: %v", r.name, r.expectedType, err) } meta, err := meta.Accessor(list) if err != nil { return fmt.Errorf("%s: Unable to understand list result %#v", r.name, list) } resourceVersion = meta.ResourceVersion() items, err := runtime.ExtractList(list) if err != nil { return fmt.Errorf("%s: Unable to understand list result %#v (%v)", r.name, list, err) } if err := r.syncWith(items, resourceVersion); err != nil { return fmt.Errorf("%s: Unable to sync list result: %v", r.name, err) } r.setLastSyncResourceVersion(resourceVersion) for { options := api.ListOptions{ ResourceVersion: resourceVersion, // We want to avoid situations when resyncing is breaking the TCP connection // - see comment for 'timeoutForWatch()' for more details. TimeoutSeconds: r.timeoutForWatch(), } w, err := r.listerWatcher.Watch(options) if err != nil { switch err { case io.EOF: // watch closed normally case io.ErrUnexpectedEOF: glog.V(1).Infof("%s: Watch for %v closed with unexpected EOF: %v", r.name, r.expectedType, err) default: util.HandleError(fmt.Errorf("%s: Failed to watch %v: %v", r.name, r.expectedType, err)) } // If this is "connection refused" error, it means that most likely apiserver is not responsive. // It doesn't make sense to re-list all objects because most likely we will be able to restart // watch where we ended. // If that's the case wait and resend watch request. if urlError, ok := err.(*url.Error); ok { if opError, ok := urlError.Err.(*net.OpError); ok { if errno, ok := opError.Err.(syscall.Errno); ok && errno == syscall.ECONNREFUSED { time.Sleep(time.Second) continue } } } return nil } if err := r.watchHandler(w, &resourceVersion, resyncCh, stopCh); err != nil { if err != errorResyncRequested && err != errorStopRequested { glog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err) } return nil } if r.canForceResyncNow() { glog.V(4).Infof("%s: next resync planned for %#v, forcing now", r.name, r.nextResync) return nil } } }
// GetModifiedConfiguration retrieves the modified configuration of the object. // If annotate is true, it embeds the result as an anotation in the modified // configuration. If an object was read from the command input, it will use that // version of the object. Otherwise, it will use the version from the server. func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error) { // First serialize the object without the annotation to prevent recursion, // then add that serialization to it as the annotation and serialize it again. var modified []byte if info.VersionedObject != nil { // If an object was read from input, use that version. accessor, err := meta.Accessor(info.VersionedObject) if err != nil { return nil, err } // Get the current annotations from the object. annotations := accessor.Annotations() if annotations == nil { annotations = map[string]string{} } original := annotations[LastAppliedConfigAnnotation] delete(annotations, LastAppliedConfigAnnotation) accessor.SetAnnotations(annotations) modified, err = json.Marshal(info.VersionedObject) if err != nil { return nil, err } if annotate { annotations[LastAppliedConfigAnnotation] = string(modified) accessor.SetAnnotations(annotations) modified, err = json.Marshal(info.VersionedObject) if err != nil { return nil, err } } // Restore the object to its original condition. annotations[LastAppliedConfigAnnotation] = original accessor.SetAnnotations(annotations) } else { // Otherwise, use the server side version of the object. accessor := info.Mapping.MetadataAccessor // Get the current annotations from the object. annotations, err := accessor.Annotations(info.Object) if err != nil { return nil, err } if annotations == nil { annotations = map[string]string{} } original := annotations[LastAppliedConfigAnnotation] delete(annotations, LastAppliedConfigAnnotation) if err := accessor.SetAnnotations(info.Object, annotations); err != nil { return nil, err } modified, err = info.Mapping.Codec.Encode(info.Object) if err != nil { return nil, err } if annotate { annotations[LastAppliedConfigAnnotation] = string(modified) if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil { return nil, err } modified, err = info.Mapping.Codec.Encode(info.Object) if err != nil { return nil, err } } // Restore the object to its original condition. annotations[LastAppliedConfigAnnotation] = original if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil { return nil, err } } return modified, nil }