// Index returns a list of items that match on the index function // Index is thread-safe so long as you treat all items as immutable func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) { c.lock.RLock() defer c.lock.RUnlock() indexFunc := c.indexers[indexName] if indexFunc == nil { return nil, fmt.Errorf("Index with name %s does not exist", indexName) } indexKeys, err := indexFunc(obj) if err != nil { return nil, err } index := c.indices[indexName] // need to de-dupe the return list. Since multiple keys are allowed, this can happen. returnKeySet := sets.String{} for _, indexKey := range indexKeys { set := index[indexKey] for _, key := range set.UnsortedList() { returnKeySet.Insert(key) } } list := make([]interface{}, 0, returnKeySet.Len()) for absoluteKey := range returnKeySet { list = append(list, c.items[absoluteKey]) } return list, nil }
func (r *Requirement) Values() sets.String { ret := sets.String{} for i := range r.strValues { ret.Insert(r.strValues[i]) } return ret }
// Test public interface func doTestIndex(t *testing.T, indexer Indexer) { mkObj := func(id string, val string) testStoreObject { return testStoreObject{id: id, val: val} } // Test Index expected := map[string]sets.String{} expected["b"] = sets.NewString("a", "c") expected["f"] = sets.NewString("e") expected["h"] = sets.NewString("g") indexer.Add(mkObj("a", "b")) indexer.Add(mkObj("c", "b")) indexer.Add(mkObj("e", "f")) indexer.Add(mkObj("g", "h")) { for k, v := range expected { found := sets.String{} indexResults, err := indexer.Index("by_val", mkObj("", k)) if err != nil { t.Errorf("Unexpected error %v", err) } for _, item := range indexResults { found.Insert(item.(testStoreObject).id) } items := v.List() if !found.HasAll(items...) { t.Errorf("missing items, index %s, expected %v but found %v", k, items, found.List()) } } } }
// NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of // preference. // - If version is provided and the server does not support it, // return an error. func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion, clientRegisteredGVs []schema.GroupVersion) (*schema.GroupVersion, error) { clientVersions := sets.String{} for _, gv := range clientRegisteredGVs { clientVersions.Insert(gv.String()) } groups, err := client.ServerGroups() if err != nil { // This is almost always a connection error, and higher level code should treat this as a generic error, // not a negotiation specific error. return nil, err } versions := metav1.ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) } // If version explicitly requested verify that both client and server support it. // If server does not support warn, but try to negotiate a lower version. if requiredGV != nil { if !clientVersions.Has(requiredGV.String()) { return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", requiredGV, clientVersions) } // If the server supports no versions, then we should just use the preferredGV // This can happen because discovery fails due to 403 Forbidden errors if len(serverVersions) == 0 { return requiredGV, nil } if serverVersions.Has(requiredGV.String()) { return requiredGV, nil } // If we are using an explicit config version the server does not support, fail. return nil, fmt.Errorf("server does not support API version %q", requiredGV) } for _, clientGV := range clientRegisteredGVs { if serverVersions.Has(clientGV.String()) { // Version was not explicitly requested in command config (--api-version). // Ok to fall back to a supported version with a warning. // TODO: caesarxuchao: enable the warning message when we have // proper fix. Please refer to issue #14895. // if len(version) != 0 { // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion) // } t := clientGV return &t, nil } } // if we have no server versions and we have no required version, choose the first clientRegisteredVersion if len(serverVersions) == 0 && len(clientRegisteredGVs) > 0 { return &clientRegisteredGVs[0], nil } return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverVersions, clientVersions) }
func ExampleNewInformer() { // source simulates an apiserver object endpoint. source := fcache.NewFakeControllerSource() // Let's do threadsafe output to get predictable test results. deletionCounter := make(chan string, 1000) // Make a controller that immediately deletes anything added to it, and // logs anything deleted. _, controller := NewInformer( source, &v1.Pod{}, time.Millisecond*100, ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { source.Delete(obj.(runtime.Object)) }, DeleteFunc: func(obj interface{}) { key, err := DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { key = "oops something went wrong with the key" } // Report this deletion. deletionCounter <- key }, }, ) // Run the controller and run it until we close stop. stop := make(chan struct{}) defer close(stop) go controller.Run(stop) // Let's add a few objects to the source. testIDs := []string{"a-hello", "b-controller", "c-framework"} for _, name := range testIDs { // Note that these pods are not valid-- the fake source doesn't // call validation or anything. source.Add(&v1.Pod{ObjectMeta: v1.ObjectMeta{Name: name}}) } // Let's wait for the controller to process the things we just added. outputSet := sets.String{} for i := 0; i < len(testIDs); i++ { outputSet.Insert(<-deletionCounter) } for _, key := range outputSet.List() { fmt.Println(key) } // Output: // a-hello // b-controller // c-framework }
// RESTMapper returns a union RESTMapper of all known types with priorities chosen in the following order: // 1. if KUBE_API_VERSIONS is specified, then KUBE_API_VERSIONS in order, OR // 1. legacy kube group preferred version, extensions preferred version, metrics perferred version, legacy // kube any version, extensions any version, metrics any version, all other groups alphabetical preferred version, // all other groups alphabetical. func (m *APIRegistrationManager) RESTMapper(versionPatterns ...unversioned.GroupVersion) meta.RESTMapper { unionMapper := meta.MultiRESTMapper{} unionedGroups := sets.NewString() for enabledVersion := range m.enabledVersions { if !unionedGroups.Has(enabledVersion.Group) { unionedGroups.Insert(enabledVersion.Group) groupMeta := m.groupMetaMap[enabledVersion.Group] unionMapper = append(unionMapper, groupMeta.RESTMapper) } } if len(versionPatterns) != 0 { resourcePriority := []unversioned.GroupVersionResource{} kindPriority := []unversioned.GroupVersionKind{} for _, versionPriority := range versionPatterns { resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource)) kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind)) } return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} } if len(m.envRequestedVersions) != 0 { resourcePriority := []unversioned.GroupVersionResource{} kindPriority := []unversioned.GroupVersionKind{} for _, versionPriority := range m.envRequestedVersions { resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource)) kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind)) } return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} } prioritizedGroups := []string{"", "extensions", "metrics"} resourcePriority, kindPriority := m.prioritiesForGroups(prioritizedGroups...) prioritizedGroupsSet := sets.NewString(prioritizedGroups...) remainingGroups := sets.String{} for enabledVersion := range m.enabledVersions { if !prioritizedGroupsSet.Has(enabledVersion.Group) { remainingGroups.Insert(enabledVersion.Group) } } remainingResourcePriority, remainingKindPriority := m.prioritiesForGroups(remainingGroups.List()...) resourcePriority = append(resourcePriority, remainingResourcePriority...) kindPriority = append(kindPriority, remainingKindPriority...) return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} }
func Example() { // source simulates an apiserver object endpoint. source := fcache.NewFakeControllerSource() // This will hold the downstream state, as we know it. downstream := NewStore(DeletionHandlingMetaNamespaceKeyFunc) // This will hold incoming changes. Note how we pass downstream in as a // KeyLister, that way resync operations will result in the correct set // of update/delete deltas. fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, downstream) // Let's do threadsafe output to get predictable test results. deletionCounter := make(chan string, 1000) cfg := &Config{ Queue: fifo, ListerWatcher: source, ObjectType: &v1.Pod{}, FullResyncPeriod: time.Millisecond * 100, RetryOnError: false, // Let's implement a simple controller that just deletes // everything that comes in. Process: func(obj interface{}) error { // Obj is from the Pop method of the Queue we make above. newest := obj.(Deltas).Newest() if newest.Type != Deleted { // Update our downstream store. err := downstream.Add(newest.Object) if err != nil { return err } // Delete this object. source.Delete(newest.Object.(runtime.Object)) } else { // Update our downstream store. err := downstream.Delete(newest.Object) if err != nil { return err } // fifo's KeyOf is easiest, because it handles // DeletedFinalStateUnknown markers. key, err := fifo.KeyOf(newest.Object) if err != nil { return err } // Report this deletion. deletionCounter <- key } return nil }, } // Create the controller and run it until we close stop. stop := make(chan struct{}) defer close(stop) go New(cfg).Run(stop) // Let's add a few objects to the source. testIDs := []string{"a-hello", "b-controller", "c-framework"} for _, name := range testIDs { // Note that these pods are not valid-- the fake source doesn't // call validation or anything. source.Add(&v1.Pod{ObjectMeta: v1.ObjectMeta{Name: name}}) } // Let's wait for the controller to process the things we just added. outputSet := sets.String{} for i := 0; i < len(testIDs); i++ { outputSet.Insert(<-deletionCounter) } for _, key := range outputSet.List() { fmt.Println(key) } // Output: // a-hello // b-controller // c-framework }
func TestHammerController(t *testing.T) { // This test executes a bunch of requests through the fake source and // controller framework to make sure there's no locking/threading // errors. If an error happens, it should hang forever or trigger the // race detector. // source simulates an apiserver object endpoint. source := fcache.NewFakeControllerSource() // Let's do threadsafe output to get predictable test results. outputSetLock := sync.Mutex{} // map of key to operations done on the key outputSet := map[string][]string{} recordFunc := func(eventType string, obj interface{}) { key, err := DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { t.Errorf("something wrong with key: %v", err) key = "oops something went wrong with the key" } // Record some output when items are deleted. outputSetLock.Lock() defer outputSetLock.Unlock() outputSet[key] = append(outputSet[key], eventType) } // Make a controller which just logs all the changes it gets. _, controller := NewInformer( source, &v1.Pod{}, time.Millisecond*100, ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { recordFunc("add", obj) }, UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) }, DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) }, }, ) if controller.HasSynced() { t.Errorf("Expected HasSynced() to return false before we started the controller") } // Run the controller and run it until we close stop. stop := make(chan struct{}) go controller.Run(stop) // Let's wait for the controller to do its initial sync wait.Poll(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { return controller.HasSynced(), nil }) if !controller.HasSynced() { t.Errorf("Expected HasSynced() to return true after the initial sync") } wg := sync.WaitGroup{} const threads = 3 wg.Add(threads) for i := 0; i < threads; i++ { go func() { defer wg.Done() // Let's add a few objects to the source. currentNames := sets.String{} rs := rand.NewSource(rand.Int63()) f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs) r := rand.New(rs) // Mustn't use r and f concurrently! for i := 0; i < 100; i++ { var name string var isNew bool if currentNames.Len() == 0 || r.Intn(3) == 1 { f.Fuzz(&name) isNew = true } else { l := currentNames.List() name = l[r.Intn(len(l))] } pod := &v1.Pod{} f.Fuzz(pod) pod.ObjectMeta.Name = name pod.ObjectMeta.Namespace = "default" // Add, update, or delete randomly. // Note that these pods are not valid-- the fake source doesn't // call validation or perform any other checking. if isNew { currentNames.Insert(name) source.Add(pod) continue } switch r.Intn(2) { case 0: currentNames.Insert(name) source.Modify(pod) case 1: currentNames.Delete(name) source.Delete(pod) } } }() } wg.Wait() // Let's wait for the controller to finish processing the things we just added. // TODO: look in the queue to see how many items need to be processed. time.Sleep(100 * time.Millisecond) close(stop) // TODO: Verify that no goroutines were leaked here and that everything shut // down cleanly. outputSetLock.Lock() t.Logf("got: %#v", outputSet) }
// Test public interface func doTestStore(t *testing.T, store Store) { mkObj := func(id string, val string) testStoreObject { return testStoreObject{id: id, val: val} } store.Add(mkObj("foo", "bar")) if item, ok, _ := store.Get(mkObj("foo", "")); !ok { t.Errorf("didn't find inserted item") } else { if e, a := "bar", item.(testStoreObject).val; e != a { t.Errorf("expected %v, got %v", e, a) } } store.Update(mkObj("foo", "baz")) if item, ok, _ := store.Get(mkObj("foo", "")); !ok { t.Errorf("didn't find inserted item") } else { if e, a := "baz", item.(testStoreObject).val; e != a { t.Errorf("expected %v, got %v", e, a) } } store.Delete(mkObj("foo", "")) if _, ok, _ := store.Get(mkObj("foo", "")); ok { t.Errorf("found deleted item??") } // Test List. store.Add(mkObj("a", "b")) store.Add(mkObj("c", "d")) store.Add(mkObj("e", "e")) { found := sets.String{} for _, item := range store.List() { found.Insert(item.(testStoreObject).val) } if !found.HasAll("b", "d", "e") { t.Errorf("missing items, found: %v", found) } if len(found) != 3 { t.Errorf("extra items") } } // Test Replace. store.Replace([]interface{}{ mkObj("foo", "foo"), mkObj("bar", "bar"), }, "0") { found := sets.String{} for _, item := range store.List() { found.Insert(item.(testStoreObject).val) } if !found.HasAll("foo", "bar") { t.Errorf("missing items") } if len(found) != 2 { t.Errorf("extra items") } } }
// NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of // preference. // - If version is provided, but not default config (explicitly requested via // commandline flag), and is unsupported by the server, print a warning to // stderr and try client's registered versions in order of preference. // - If version is config default, and the server does not support it, // return an error. func NegotiateVersion(client DiscoveryInterface, c *rest.Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) { var err error if client == nil { client, err = NewDiscoveryClientForConfig(c) if err != nil { return nil, err } } clientVersions := sets.String{} for _, gv := range clientRegisteredGVs { clientVersions.Insert(gv.String()) } groups, err := client.ServerGroups() if err != nil { // This is almost always a connection error, and higher level code should treat this as a generic error, // not a negotiation specific error. return nil, err } versions := unversioned.ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) } // If no version requested, use config version (may also be empty). // make a copy of the original so we don't risk mutating input here or in the returned value var preferredGV *unversioned.GroupVersion switch { case requestedGV != nil: t := *requestedGV preferredGV = &t case c.GroupVersion != nil: t := *c.GroupVersion preferredGV = &t } // If version explicitly requested verify that both client and server support it. // If server does not support warn, but try to negotiate a lower version. if preferredGV != nil { if !clientVersions.Has(preferredGV.String()) { return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", preferredGV, clientVersions) } // If the server supports no versions, then we should just use the preferredGV // This can happen because discovery fails due to 403 Forbidden errors if len(serverVersions) == 0 { return preferredGV, nil } if serverVersions.Has(preferredGV.String()) { return preferredGV, nil } // If we are using an explicit config version the server does not support, fail. if (c.GroupVersion != nil) && (*preferredGV == *c.GroupVersion) { return nil, fmt.Errorf("server does not support API version %q", preferredGV) } } for _, clientGV := range clientRegisteredGVs { if serverVersions.Has(clientGV.String()) { // Version was not explicitly requested in command config (--api-version). // Ok to fall back to a supported version with a warning. // TODO: caesarxuchao: enable the warning message when we have // proper fix. Please refer to issue #14895. // if len(version) != 0 { // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion) // } t := clientGV return &t, nil } } return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverVersions, clientVersions) }