1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package pager 18 19import ( 20 "context" 21 "fmt" 22 23 "k8s.io/apimachinery/pkg/api/errors" 24 "k8s.io/apimachinery/pkg/api/meta" 25 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28) 29 30const defaultPageSize = 500 31 32// ListPageFunc returns a list object for the given list options. 33type ListPageFunc func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) 34 35// SimplePageFunc adapts a context-less list function into one that accepts a context. 36func SimplePageFunc(fn func(opts metav1.ListOptions) (runtime.Object, error)) ListPageFunc { 37 return func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { 38 return fn(opts) 39 } 40} 41 42// ListPager assists client code in breaking large list queries into multiple 43// smaller chunks of PageSize or smaller. PageFn is expected to accept a 44// metav1.ListOptions that supports paging and return a list. The pager does 45// not alter the field or label selectors on the initial options list. 46type ListPager struct { 47 PageSize int64 48 PageFn ListPageFunc 49 50 FullListIfExpired bool 51} 52 53// New creates a new pager from the provided pager function using the default 54// options. It will fall back to a full list if an expiration error is encountered 55// as a last resort. 56func New(fn ListPageFunc) *ListPager { 57 return &ListPager{ 58 PageSize: defaultPageSize, 59 PageFn: fn, 60 FullListIfExpired: true, 61 } 62} 63 64// TODO: introduce other types of paging functions - such as those that retrieve from a list 65// of namespaces. 66 67// List returns a single list object, but attempts to retrieve smaller chunks from the 68// server to reduce the impact on the server. If the chunk attempt fails, it will load 69// the full list instead. The Limit field on options, if unset, will default to the page size. 70func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { 71 if options.Limit == 0 { 72 options.Limit = p.PageSize 73 } 74 var list *metainternalversion.List 75 for { 76 obj, err := p.PageFn(ctx, options) 77 if err != nil { 78 if !errors.IsResourceExpired(err) || !p.FullListIfExpired { 79 return nil, err 80 } 81 // the list expired while we were processing, fall back to a full list 82 options.Limit = 0 83 options.Continue = "" 84 return p.PageFn(ctx, options) 85 } 86 m, err := meta.ListAccessor(obj) 87 if err != nil { 88 return nil, fmt.Errorf("returned object must be a list: %v", err) 89 } 90 91 // exit early and return the object we got if we haven't processed any pages 92 if len(m.GetContinue()) == 0 && list == nil { 93 return obj, nil 94 } 95 96 // initialize the list and fill its contents 97 if list == nil { 98 list = &metainternalversion.List{Items: make([]runtime.Object, 0, options.Limit+1)} 99 list.ResourceVersion = m.GetResourceVersion() 100 list.SelfLink = m.GetSelfLink() 101 } 102 if err := meta.EachListItem(obj, func(obj runtime.Object) error { 103 list.Items = append(list.Items, obj) 104 return nil 105 }); err != nil { 106 return nil, err 107 } 108 109 // if we have no more items, return the list 110 if len(m.GetContinue()) == 0 { 111 return list, nil 112 } 113 114 // set the next loop up 115 options.Continue = m.GetContinue() 116 } 117} 118