Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame^] | 1 | /* |
| 2 | Copyright 2017 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package pager |
| 18 | |
| 19 | import ( |
| 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 | |
| 30 | const defaultPageSize = 500 |
| 31 | |
| 32 | // ListPageFunc returns a list object for the given list options. |
| 33 | type 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. |
| 36 | func 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. |
| 46 | type 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. |
| 56 | func 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. |
| 70 | func (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 | } |