mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
reflector: move LIST to its own method
This commit is contained in:
parent
e9e26068b7
commit
6fc09008de
@ -254,111 +254,10 @@ func (r *Reflector) resyncChan() (<-chan time.Time, func() bool) {
|
|||||||
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||||
klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)
|
klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)
|
||||||
var resourceVersion string
|
var resourceVersion string
|
||||||
|
var err error
|
||||||
|
|
||||||
options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}
|
resourceVersion, err = r.list(stopCh)
|
||||||
|
if err != nil {
|
||||||
if err := func() error {
|
|
||||||
initTrace := trace.New("Reflector ListAndWatch", trace.Field{Key: "name", Value: r.name})
|
|
||||||
defer initTrace.LogIfLong(10 * time.Second)
|
|
||||||
var list runtime.Object
|
|
||||||
var paginatedResult bool
|
|
||||||
var err error
|
|
||||||
listCh := make(chan struct{}, 1)
|
|
||||||
panicCh := make(chan interface{}, 1)
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicCh <- r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// Attempt to gather list in chunks, if supported by listerWatcher, if not, the first
|
|
||||||
// list request will return the full response.
|
|
||||||
pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
|
|
||||||
return r.listerWatcher.List(opts)
|
|
||||||
}))
|
|
||||||
switch {
|
|
||||||
case r.WatchListPageSize != 0:
|
|
||||||
pager.PageSize = r.WatchListPageSize
|
|
||||||
case r.paginatedResult:
|
|
||||||
// We got a paginated result initially. Assume this resource and server honor
|
|
||||||
// paging requests (i.e. watch cache is probably disabled) and leave the default
|
|
||||||
// pager size set.
|
|
||||||
case options.ResourceVersion != "" && options.ResourceVersion != "0":
|
|
||||||
// User didn't explicitly request pagination.
|
|
||||||
//
|
|
||||||
// With ResourceVersion != "", we have a possibility to list from watch cache,
|
|
||||||
// but we do that (for ResourceVersion != "0") only if Limit is unset.
|
|
||||||
// To avoid thundering herd on etcd (e.g. on master upgrades), we explicitly
|
|
||||||
// switch off pagination to force listing from watch cache (if enabled).
|
|
||||||
// With the existing semantic of RV (result is at least as fresh as provided RV),
|
|
||||||
// this is correct and doesn't lead to going back in time.
|
|
||||||
//
|
|
||||||
// We also don't turn off pagination for ResourceVersion="0", since watch cache
|
|
||||||
// is ignoring Limit in that case anyway, and if watch cache is not enabled
|
|
||||||
// we don't introduce regression.
|
|
||||||
pager.PageSize = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
list, paginatedResult, err = pager.List(context.Background(), options)
|
|
||||||
if isExpiredError(err) || isTooLargeResourceVersionError(err) {
|
|
||||||
r.setIsLastSyncResourceVersionUnavailable(true)
|
|
||||||
// Retry immediately if the resource version used to list is unavailable.
|
|
||||||
// The pager already falls back to full list if paginated list calls fail due to an "Expired" error on
|
|
||||||
// continuation pages, but the pager might not be enabled, the full list might fail because the
|
|
||||||
// resource version it is listing at is expired or the cache may not yet be synced to the provided
|
|
||||||
// resource version. So we need to fallback to resourceVersion="" in all to recover and ensure
|
|
||||||
// the reflector makes forward progress.
|
|
||||||
list, paginatedResult, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
|
|
||||||
}
|
|
||||||
close(listCh)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
return nil
|
|
||||||
case r := <-panicCh:
|
|
||||||
panic(r)
|
|
||||||
case <-listCh:
|
|
||||||
}
|
|
||||||
initTrace.Step("Objects listed", trace.Field{Key: "error", Value: err})
|
|
||||||
if err != nil {
|
|
||||||
klog.Warningf("%s: failed to list %v: %v", r.name, r.expectedTypeName, err)
|
|
||||||
return fmt.Errorf("failed to list %v: %w", r.expectedTypeName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We check if the list was paginated and if so set the paginatedResult based on that.
|
|
||||||
// However, we want to do that only for the initial list (which is the only case
|
|
||||||
// when we set ResourceVersion="0"). The reasoning behind it is that later, in some
|
|
||||||
// situations we may force listing directly from etcd (by setting ResourceVersion="")
|
|
||||||
// which will return paginated result, even if watch cache is enabled. However, in
|
|
||||||
// that case, we still want to prefer sending requests to watch cache if possible.
|
|
||||||
//
|
|
||||||
// Paginated result returned for request with ResourceVersion="0" mean that watch
|
|
||||||
// cache is disabled and there are a lot of objects of a given type. In such case,
|
|
||||||
// there is no need to prefer listing from watch cache.
|
|
||||||
if options.ResourceVersion == "0" && paginatedResult {
|
|
||||||
r.paginatedResult = true
|
|
||||||
}
|
|
||||||
|
|
||||||
r.setIsLastSyncResourceVersionUnavailable(false) // list was successful
|
|
||||||
listMetaInterface, err := meta.ListAccessor(list)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to understand list result %#v: %v", list, err)
|
|
||||||
}
|
|
||||||
resourceVersion = listMetaInterface.GetResourceVersion()
|
|
||||||
initTrace.Step("Resource version extracted")
|
|
||||||
items, err := meta.ExtractList(list)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to understand list result %#v (%v)", list, err)
|
|
||||||
}
|
|
||||||
initTrace.Step("Objects extracted")
|
|
||||||
if err := r.syncWith(items, resourceVersion); err != nil {
|
|
||||||
return fmt.Errorf("unable to sync list result: %v", err)
|
|
||||||
}
|
|
||||||
initTrace.Step("SyncWith done")
|
|
||||||
r.setLastSyncResourceVersion(resourceVersion)
|
|
||||||
initTrace.Step("Resource version updated")
|
|
||||||
return nil
|
|
||||||
}(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +298,7 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))
|
timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))
|
||||||
options = metav1.ListOptions{
|
options := metav1.ListOptions{
|
||||||
ResourceVersion: resourceVersion,
|
ResourceVersion: resourceVersion,
|
||||||
// We want to avoid situations of hanging watchers. Stop any watchers that do not
|
// We want to avoid situations of hanging watchers. Stop any watchers that do not
|
||||||
// receive any events within the timeout window.
|
// receive any events within the timeout window.
|
||||||
@ -447,6 +346,114 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list simply lists all items and gets a resource version obtained from the server at the moment of the call.
|
||||||
|
// the resource version can be used for further progress notification (aka. watch).
|
||||||
|
func (r *Reflector) list(stopCh <-chan struct{}) (string, error) {
|
||||||
|
var resourceVersion string
|
||||||
|
options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}
|
||||||
|
|
||||||
|
initTrace := trace.New("Reflector ListAndWatch", trace.Field{Key: "name", Value: r.name})
|
||||||
|
defer initTrace.LogIfLong(10 * time.Second)
|
||||||
|
var list runtime.Object
|
||||||
|
var paginatedResult bool
|
||||||
|
var err error
|
||||||
|
listCh := make(chan struct{}, 1)
|
||||||
|
panicCh := make(chan interface{}, 1)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
panicCh <- r
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Attempt to gather list in chunks, if supported by listerWatcher, if not, the first
|
||||||
|
// list request will return the full response.
|
||||||
|
pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return r.listerWatcher.List(opts)
|
||||||
|
}))
|
||||||
|
switch {
|
||||||
|
case r.WatchListPageSize != 0:
|
||||||
|
pager.PageSize = r.WatchListPageSize
|
||||||
|
case r.paginatedResult:
|
||||||
|
// We got a paginated result initially. Assume this resource and server honor
|
||||||
|
// paging requests (i.e. watch cache is probably disabled) and leave the default
|
||||||
|
// pager size set.
|
||||||
|
case options.ResourceVersion != "" && options.ResourceVersion != "0":
|
||||||
|
// User didn't explicitly request pagination.
|
||||||
|
//
|
||||||
|
// With ResourceVersion != "", we have a possibility to list from watch cache,
|
||||||
|
// but we do that (for ResourceVersion != "0") only if Limit is unset.
|
||||||
|
// To avoid thundering herd on etcd (e.g. on master upgrades), we explicitly
|
||||||
|
// switch off pagination to force listing from watch cache (if enabled).
|
||||||
|
// With the existing semantic of RV (result is at least as fresh as provided RV),
|
||||||
|
// this is correct and doesn't lead to going back in time.
|
||||||
|
//
|
||||||
|
// We also don't turn off pagination for ResourceVersion="0", since watch cache
|
||||||
|
// is ignoring Limit in that case anyway, and if watch cache is not enabled
|
||||||
|
// we don't introduce regression.
|
||||||
|
pager.PageSize = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
list, paginatedResult, err = pager.List(context.Background(), options)
|
||||||
|
if isExpiredError(err) || isTooLargeResourceVersionError(err) {
|
||||||
|
r.setIsLastSyncResourceVersionUnavailable(true)
|
||||||
|
// Retry immediately if the resource version used to list is unavailable.
|
||||||
|
// The pager already falls back to full list if paginated list calls fail due to an "Expired" error on
|
||||||
|
// continuation pages, but the pager might not be enabled, the full list might fail because the
|
||||||
|
// resource version it is listing at is expired or the cache may not yet be synced to the provided
|
||||||
|
// resource version. So we need to fallback to resourceVersion="" in all to recover and ensure
|
||||||
|
// the reflector makes forward progress.
|
||||||
|
list, paginatedResult, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
|
||||||
|
}
|
||||||
|
close(listCh)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
return "", nil
|
||||||
|
case r := <-panicCh:
|
||||||
|
panic(r)
|
||||||
|
case <-listCh:
|
||||||
|
}
|
||||||
|
initTrace.Step("Objects listed", trace.Field{Key: "error", Value: err})
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("%s: failed to list %v: %v", r.name, r.expectedTypeName, err)
|
||||||
|
return "", fmt.Errorf("failed to list %v: %w", r.expectedTypeName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We check if the list was paginated and if so set the paginatedResult based on that.
|
||||||
|
// However, we want to do that only for the initial list (which is the only case
|
||||||
|
// when we set ResourceVersion="0"). The reasoning behind it is that later, in some
|
||||||
|
// situations we may force listing directly from etcd (by setting ResourceVersion="")
|
||||||
|
// which will return paginated result, even if watch cache is enabled. However, in
|
||||||
|
// that case, we still want to prefer sending requests to watch cache if possible.
|
||||||
|
//
|
||||||
|
// Paginated result returned for request with ResourceVersion="0" mean that watch
|
||||||
|
// cache is disabled and there are a lot of objects of a given type. In such case,
|
||||||
|
// there is no need to prefer listing from watch cache.
|
||||||
|
if options.ResourceVersion == "0" && paginatedResult {
|
||||||
|
r.paginatedResult = true
|
||||||
|
}
|
||||||
|
|
||||||
|
r.setIsLastSyncResourceVersionUnavailable(false) // list was successful
|
||||||
|
listMetaInterface, err := meta.ListAccessor(list)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to understand list result %#v: %v", list, err)
|
||||||
|
}
|
||||||
|
resourceVersion = listMetaInterface.GetResourceVersion()
|
||||||
|
initTrace.Step("Resource version extracted")
|
||||||
|
items, err := meta.ExtractList(list)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to understand list result %#v (%v)", list, err)
|
||||||
|
}
|
||||||
|
initTrace.Step("Objects extracted")
|
||||||
|
if err := r.syncWith(items, resourceVersion); err != nil {
|
||||||
|
return "", fmt.Errorf("unable to sync list result: %v", err)
|
||||||
|
}
|
||||||
|
initTrace.Step("SyncWith done")
|
||||||
|
r.setLastSyncResourceVersion(resourceVersion)
|
||||||
|
initTrace.Step("Resource version updated")
|
||||||
|
return resourceVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
// syncWith replaces the store's items with the given list.
|
// syncWith replaces the store's items with the given list.
|
||||||
func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) error {
|
func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) error {
|
||||||
found := make([]interface{}, 0, len(items))
|
found := make([]interface{}, 0, len(items))
|
||||||
|
Loading…
Reference in New Issue
Block a user