diff --git a/pkg/listwatch/listwatch.go b/pkg/listwatch/listwatch.go index b09f86ec1c..361f2486b5 100644 --- a/pkg/listwatch/listwatch.go +++ b/pkg/listwatch/listwatch.go @@ -22,6 +22,8 @@ import ( "strings" "sync" + "k8s.io/klog/v2" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -135,24 +137,27 @@ type multiListerWatcher []cache.ListerWatcher // a single result. func (mlw multiListerWatcher) List(options metav1.ListOptions) (runtime.Object, error) { l := metav1.List{} - var resourceVersions []string - for _, lw := range mlw { + resourceVersions := make([]string, len(mlw)) + for i, lw := range mlw { list, err := lw.List(options) if err != nil { - return nil, err + klog.Errorf("Failed to list resources: %v", err) + continue } items, err := meta.ExtractList(list) if err != nil { - return nil, err + klog.Errorf("Failed to extract list: %v", err) + continue } metaObj, err := meta.ListAccessor(list) if err != nil { - return nil, err + klog.Errorf("Failed to list accessor: %v", err) + continue } for _, item := range items { l.Items = append(l.Items, runtime.RawExtension{Object: item.DeepCopyObject()}) } - resourceVersions = append(resourceVersions, metaObj.GetResourceVersion()) + resourceVersions[i] = metaObj.GetResourceVersion() } // Combine the resource versions so that the composite Watch method can // distribute appropriate versions to each underlying Watch func. diff --git a/pkg/listwatch/listwatch_test.go b/pkg/listwatch/listwatch_test.go new file mode 100644 index 0000000000..de7e74550f --- /dev/null +++ b/pkg/listwatch/listwatch_test.go @@ -0,0 +1,93 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package listwatch_test + +import ( + "fmt" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + "k8s.io/kube-state-metrics/v2/pkg/listwatch" +) + +var errList = fmt.Errorf("error listing resources") + +type listWatcherStub struct { + err error +} + +func (l listWatcherStub) List(_ metav1.ListOptions) (runtime.Object, error) { + if l.err != nil { + return nil, l.err + } + + return makePodList(), nil +} + +func (l listWatcherStub) Watch(_ metav1.ListOptions) (watch.Interface, error) { + return nil, nil +} + +func TestMultiNamespaceListerWatcher(t *testing.T) { + namespaces := []string{"ns-succeed", "ns-fail", "ns-succeed"} + mlw := listwatch.MultiNamespaceListerWatcher(namespaces, []string{}, func(s string) cache.ListerWatcher { + if s == "ns-fail" { + return listWatcherStub{err: errList} + } else { + return listWatcherStub{} + } + }) + + result, err := mlw.List(metav1.ListOptions{}) + if err != nil { + t.Fatal(err) + } + + list, ok := result.(*metav1.List) + if !ok { + t.Fatal("invalid result type: want meta.List") + } + if len(list.Items) != 2 { + t.Fatalf("invalid item count: got %d, want %d", len(list.Items), 1) + } + if list.ResourceVersion != "1//1" { + t.Fatalf("invalid resource version: got %s, want %s", list.ResourceVersion, "/1") + } +} + +func makePodList() *v1.PodList { + return &v1.PodList{ + TypeMeta: metav1.TypeMeta{ + Kind: "pods", + APIVersion: "", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "1", + }, + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + }, + }, + }, + } +}