// 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
}
示例#2
0
// 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
}
示例#3
0
文件: ref.go 项目: qinguoan/vulcan
// 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
}
示例#4
0
文件: util.go 项目: qinguoan/vulcan
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
}
示例#5
0
文件: store.go 项目: qinguoan/vulcan
// 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
}
示例#6
0
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
}
示例#7
0
// 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)
}
示例#8
0
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)
}
示例#9
0
// 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
		}
	}
}
示例#10
0
// 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
		}
	}
}
示例#11
0
文件: apply.go 项目: qinguoan/vulcan
// 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
}