Merge pull request #91505 from jpbetz/rv-semantics

Consistent Resource Version Semantics for List
This commit is contained in:
Kubernetes Prow Robot 2020-06-27 02:26:17 -07:00 committed by GitHub
commit 27aca3f7c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 4057 additions and 637 deletions

File diff suppressed because it is too large Load Diff

View File

@ -65,6 +65,7 @@ func (s *storageLeases) ListLeases() ([]string, error) {
ipInfoList := &corev1.EndpointsList{} ipInfoList := &corev1.EndpointsList{}
storageOpts := storage.ListOptions{ storageOpts := storage.ListOptions{
ResourceVersion: "0", ResourceVersion: "0",
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
Predicate: storage.Everything, Predicate: storage.Everything,
} }
if err := s.storage.List(apirequest.NewDefaultContext(), s.baseKey, storageOpts, ipInfoList); err != nil { if err := s.storage.List(apirequest.NewDefaultContext(), s.baseKey, storageOpts, ipInfoList); err != nil {

View File

@ -5,7 +5,8 @@
"fieldSelector": "3", "fieldSelector": "3",
"watch": true, "watch": true,
"resourceVersion": "4", "resourceVersion": "4",
"timeoutSeconds": 1002466899136229878, "resourceVersionMatch": "Exact",
"limit": 5339971464584210463, "timeoutSeconds": 6780787122834727873,
"limit": 3549865785210165515,
"continue": "5" "continue": "5"
} }

View File

@ -3,7 +3,8 @@ continue: "5"
fieldSelector: "3" fieldSelector: "3"
kind: ListOptions kind: ListOptions
labelSelector: "2" labelSelector: "2"
limit: 5339971464584210463 limit: 3549865785210165515
resourceVersion: "4" resourceVersion: "4"
timeoutSeconds: 1002466899136229878 resourceVersionMatch: Exact
timeoutSeconds: 6780787122834727873
watch: true watch: true

View File

@ -1414,7 +1414,7 @@ func schema_pkg_apis_meta_v1_GetOptions(ref common.ReferenceCallback) common.Ope
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },
@ -1855,7 +1855,14 @@ func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.Op
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"},
Format: "",
},
},
"resourceVersionMatch": {
SchemaProps: spec.SchemaProps{
Description: "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },

View File

@ -215,6 +215,10 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
j.Finalizers = nil j.Finalizers = nil
} }
}, },
func(j *metav1.ResourceVersionMatch, c fuzz.Continue) {
matches := []metav1.ResourceVersionMatch{"", metav1.ResourceVersionMatchExact, metav1.ResourceVersionMatchNotOlderThan}
*j = matches[c.Rand.Intn(len(matches))]
},
func(j *metav1.ListMeta, c fuzz.Continue) { func(j *metav1.ListMeta, c fuzz.Continue) {
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.SelfLink = c.RandString() j.SelfLink = c.RandString()

View File

@ -36,6 +36,7 @@ filegroup(
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],
) )

View File

@ -44,13 +44,17 @@ type ListOptions struct {
// If the feature gate WatchBookmarks is not enabled in apiserver, // If the feature gate WatchBookmarks is not enabled in apiserver,
// this field is ignored. // this field is ignored.
AllowWatchBookmarks bool AllowWatchBookmarks bool
// When specified with a watch call, shows changes that occur after that particular version of a resource. // resourceVersion sets a constraint on what resource versions a request may be served from.
// Defaults to changes from the beginning of history. // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// When specified for list: // details.
// - if unset, then the result is returned from remote storage based on quorum-read flag;
// - if it's 0, then we simply return what we currently have in cache, no guarantee;
// - if set to non zero, then the result is at least as fresh as given rv.
ResourceVersion string ResourceVersion string
// resourceVersionMatch determines how resourceVersion is applied to list calls.
// It is highly recommended that resourceVersionMatch be set for list calls where
// resourceVersion is set.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// details.
ResourceVersionMatch metav1.ResourceVersionMatch
// Timeout for the list/watch call. // Timeout for the list/watch call.
TimeoutSeconds *int64 TimeoutSeconds *int64
// Limit specifies the maximum number of results to return from the server. The server may // Limit specifies the maximum number of results to return from the server. The server may

View File

@ -0,0 +1,38 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["validation.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation",
importpath = "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,46 @@
/*
Copyright 2020 The Kubernetes Authors.
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 validation
import (
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ValidateListOptions returns all validation errors found while validating the ListOptions.
func ValidateListOptions(options *internalversion.ListOptions) field.ErrorList {
allErrs := field.ErrorList{}
if match := options.ResourceVersionMatch; len(match) > 0 {
if options.Watch {
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden for watch"))
}
if len(options.ResourceVersion) == 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden unless resourceVersion is provided"))
}
if len(options.Continue) > 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden when continue is provided"))
}
if match != metav1.ResourceVersionMatchExact && match != metav1.ResourceVersionMatchNotOlderThan {
allErrs = append(allErrs, field.NotSupported(field.NewPath("resourceVersionMatch"), match, []string{string(metav1.ResourceVersionMatchExact), string(metav1.ResourceVersionMatchNotOlderThan), ""}))
}
if match == metav1.ResourceVersionMatchExact && options.ResourceVersion == "0" {
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\""))
}
}
return allErrs
}

View File

@ -0,0 +1,83 @@
/*
Copyright 2020 The Kubernetes Authors.
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 validation
import (
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"testing"
)
func TestValidateListOptions(t *testing.T) {
cases := []struct {
name string
opts internalversion.ListOptions
expectError string
}{
{
name: "valid-default",
opts: internalversion.ListOptions{},
},
{
name: "valid-resourceversionmatch-exact",
opts: internalversion.ListOptions{
ResourceVersion: "1",
ResourceVersionMatch: metav1.ResourceVersionMatchExact,
},
},
{
name: "invalid-resourceversionmatch-exact",
opts: internalversion.ListOptions{
ResourceVersion: "0",
ResourceVersionMatch: metav1.ResourceVersionMatchExact,
},
expectError: "resourceVersionMatch: Forbidden: resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\"",
},
{
name: "valid-resourceversionmatch-notolderthan",
opts: internalversion.ListOptions{
ResourceVersion: "0",
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
},
},
{
name: "invalid-resourceversionmatch",
opts: internalversion.ListOptions{
ResourceVersion: "0",
ResourceVersionMatch: "foo",
},
expectError: "resourceVersionMatch: Unsupported value: \"foo\": supported values: \"Exact\", \"NotOlderThan\", \"\"",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
errs := ValidateListOptions(&tc.opts)
if tc.expectError != "" {
if len(errs) != 1 {
t.Errorf("expected an error but got %d errors", len(errs))
} else if errs[0].Error() != tc.expectError {
t.Errorf("expected error '%s' but got '%s'", tc.expectError, errs[0].Error())
}
return
}
if len(errs) != 0 {
t.Errorf("expected no errors, but got: %v", errs)
}
})
}
}

View File

@ -110,6 +110,7 @@ func autoConvert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions,
out.Watch = in.Watch out.Watch = in.Watch
out.AllowWatchBookmarks = in.AllowWatchBookmarks out.AllowWatchBookmarks = in.AllowWatchBookmarks
out.ResourceVersion = in.ResourceVersion out.ResourceVersion = in.ResourceVersion
out.ResourceVersionMatch = v1.ResourceVersionMatch(in.ResourceVersionMatch)
out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds))
out.Limit = in.Limit out.Limit = in.Limit
out.Continue = in.Continue out.Continue = in.Continue
@ -131,6 +132,7 @@ func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOption
out.Watch = in.Watch out.Watch = in.Watch
out.AllowWatchBookmarks = in.AllowWatchBookmarks out.AllowWatchBookmarks = in.AllowWatchBookmarks
out.ResourceVersion = in.ResourceVersion out.ResourceVersion = in.ResourceVersion
out.ResourceVersionMatch = v1.ResourceVersionMatch(in.ResourceVersionMatch)
out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds))
out.Limit = in.Limit out.Limit = in.Limit
out.Continue = in.Continue out.Continue = in.Continue

View File

@ -345,3 +345,11 @@ func Convert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptions,
} }
return nil return nil
} }
// Convert_Slice_string_To_v1_ResourceVersionMatch allows converting a URL query parameter to ResourceVersionMatch
func Convert_Slice_string_To_v1_ResourceVersionMatch(in *[]string, out *ResourceVersionMatch, s conversion.Scope) error {
if len(*in) > 0 {
*out = ResourceVersionMatch((*in)[0])
}
return nil
}

View File

@ -1297,177 +1297,179 @@ func init() {
} }
var fileDescriptor_cf52fa777ced5367 = []byte{ var fileDescriptor_cf52fa777ced5367 = []byte{
// 2714 bytes of a gzipped FileDescriptorProto // 2737 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x19, 0xcd, 0x6f, 0x1b, 0x59, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x39, 0xcb, 0x6f, 0x24, 0x47,
0x3d, 0x63, 0xc7, 0x8e, 0xfd, 0x73, 0x9c, 0x8f, 0x97, 0x16, 0xdc, 0x20, 0xe2, 0xec, 0x2c, 0xaa, 0xf9, 0xee, 0x79, 0x79, 0xe6, 0x1b, 0x8f, 0x1f, 0x65, 0xef, 0xef, 0x37, 0x31, 0xc2, 0xe3, 0x74,
0x52, 0xe8, 0x3a, 0x9b, 0x02, 0xab, 0xd2, 0x65, 0x0b, 0x71, 0x9c, 0x74, 0xc3, 0x36, 0x4d, 0xf4, 0x50, 0xe4, 0x40, 0x32, 0x8e, 0x97, 0x10, 0x2d, 0x1b, 0x12, 0xf0, 0x78, 0xec, 0x8d, 0xc9, 0x3a,
0xd2, 0x16, 0x28, 0x15, 0xea, 0x64, 0xe6, 0xc5, 0x19, 0x32, 0x9e, 0xf1, 0xbe, 0x19, 0x27, 0x35, 0xb6, 0xca, 0xd9, 0x05, 0x42, 0x84, 0xd2, 0xee, 0x2e, 0x8f, 0x1b, 0xf7, 0x74, 0x4f, 0xaa, 0x7b,
0x1c, 0xd8, 0x03, 0x08, 0x90, 0x60, 0xd5, 0x23, 0xe2, 0x80, 0xb6, 0x82, 0xbf, 0x80, 0x13, 0x7f, 0xec, 0x1d, 0x38, 0x90, 0x03, 0x08, 0x90, 0x20, 0xda, 0x23, 0xe2, 0x80, 0xb2, 0x82, 0xbf, 0x80,
0x00, 0x12, 0xbd, 0x20, 0xad, 0xc4, 0x65, 0x25, 0x90, 0xb5, 0x0d, 0x07, 0x8e, 0x88, 0x6b, 0x4e, 0x0b, 0xfc, 0x01, 0x48, 0xec, 0x31, 0x12, 0x97, 0x48, 0xa0, 0x51, 0xd6, 0x1c, 0x38, 0x22, 0xae,
0xe8, 0x7d, 0xcd, 0x87, 0x1d, 0x37, 0x63, 0xba, 0xac, 0xb8, 0x79, 0x7e, 0xdf, 0xbf, 0xf7, 0x7e, 0x3e, 0xa1, 0x7a, 0xf5, 0x63, 0x1e, 0xeb, 0x1e, 0x36, 0x44, 0xdc, 0xba, 0xbe, 0x77, 0x55, 0x7d,
0xef, 0xf7, 0x65, 0xd8, 0x3a, 0xbc, 0xee, 0xd7, 0x6c, 0x6f, 0xf9, 0xb0, 0xb3, 0x47, 0xa8, 0x4b, 0xf5, 0xbd, 0x1a, 0xf6, 0x4e, 0x6f, 0xf8, 0x75, 0xdb, 0x5b, 0x3f, 0xed, 0x1e, 0x11, 0xea, 0x92,
0x02, 0xe2, 0x2f, 0x1f, 0x11, 0xd7, 0xf2, 0xe8, 0xb2, 0x44, 0x18, 0x6d, 0xbb, 0x65, 0x98, 0x07, 0x80, 0xf8, 0xeb, 0x67, 0xc4, 0xb5, 0x3c, 0xba, 0x2e, 0x11, 0x46, 0xc7, 0x6e, 0x1b, 0xe6, 0x89,
0xb6, 0x4b, 0x68, 0x77, 0xb9, 0x7d, 0xd8, 0x64, 0x00, 0x7f, 0xb9, 0x45, 0x02, 0x63, 0xf9, 0x68, 0xed, 0x12, 0xda, 0x5b, 0xef, 0x9c, 0xb6, 0x18, 0xc0, 0x5f, 0x6f, 0x93, 0xc0, 0x58, 0x3f, 0xdb,
0x65, 0xb9, 0x49, 0x5c, 0x42, 0x8d, 0x80, 0x58, 0xb5, 0x36, 0xf5, 0x02, 0x0f, 0x7d, 0x41, 0x70, 0x58, 0x6f, 0x11, 0x97, 0x50, 0x23, 0x20, 0x56, 0xbd, 0x43, 0xbd, 0xc0, 0x43, 0x5f, 0x10, 0x5c,
0xd5, 0xe2, 0x5c, 0xb5, 0xf6, 0x61, 0x93, 0x01, 0xfc, 0x1a, 0xe3, 0xaa, 0x1d, 0xad, 0xcc, 0xbf, 0xf5, 0x38, 0x57, 0xbd, 0x73, 0xda, 0x62, 0x00, 0xbf, 0xce, 0xb8, 0xea, 0x67, 0x1b, 0xcb, 0x2f,
0xd6, 0xb4, 0x83, 0x83, 0xce, 0x5e, 0xcd, 0xf4, 0x5a, 0xcb, 0x4d, 0xaf, 0xe9, 0x2d, 0x73, 0xe6, 0xb4, 0xec, 0xe0, 0xa4, 0x7b, 0x54, 0x37, 0xbd, 0xf6, 0x7a, 0xcb, 0x6b, 0x79, 0xeb, 0x9c, 0xf9,
0xbd, 0xce, 0x3e, 0xff, 0xe2, 0x1f, 0xfc, 0x97, 0x10, 0x3a, 0x3f, 0xd4, 0x14, 0xda, 0x71, 0x03, 0xa8, 0x7b, 0xcc, 0x57, 0x7c, 0xc1, 0xbf, 0x84, 0xd0, 0xe5, 0xb1, 0xa6, 0xd0, 0xae, 0x1b, 0xd8,
0xbb, 0x45, 0xfa, 0xad, 0x98, 0x7f, 0xe3, 0x3c, 0x06, 0xdf, 0x3c, 0x20, 0x2d, 0xa3, 0x9f, 0x4f, 0x6d, 0x32, 0x68, 0xc5, 0xf2, 0xcb, 0x57, 0x31, 0xf8, 0xe6, 0x09, 0x69, 0x1b, 0x83, 0x7c, 0xfa,
0xff, 0x73, 0x16, 0x0a, 0xab, 0x3b, 0x9b, 0xb7, 0xa8, 0xd7, 0x69, 0xa3, 0x45, 0x18, 0x77, 0x8d, 0x9f, 0xb3, 0x50, 0xdc, 0x3c, 0xd8, 0xbd, 0x45, 0xbd, 0x6e, 0x07, 0xad, 0x42, 0xce, 0x35, 0xda,
0x16, 0xa9, 0x68, 0x8b, 0xda, 0x52, 0xb1, 0x3e, 0xf9, 0xac, 0x57, 0x1d, 0x3b, 0xe9, 0x55, 0xc7, 0xa4, 0xaa, 0xad, 0x6a, 0x6b, 0xa5, 0xc6, 0xcc, 0xc3, 0x7e, 0x6d, 0xea, 0xa2, 0x5f, 0xcb, 0xbd,
0xef, 0x18, 0x2d, 0x82, 0x39, 0x06, 0x39, 0x50, 0x38, 0x22, 0xd4, 0xb7, 0x3d, 0xd7, 0xaf, 0x64, 0x69, 0xb4, 0x09, 0xe6, 0x18, 0xe4, 0x40, 0xf1, 0x8c, 0x50, 0xdf, 0xf6, 0x5c, 0xbf, 0x9a, 0x59,
0x16, 0xb3, 0x4b, 0xa5, 0x6b, 0x37, 0x6b, 0x69, 0xfc, 0xaf, 0x71, 0x05, 0xf7, 0x05, 0xeb, 0x86, 0xcd, 0xae, 0x95, 0xaf, 0xbf, 0x56, 0x4f, 0xb3, 0xff, 0x3a, 0x57, 0x70, 0x57, 0xb0, 0xee, 0x78,
0x47, 0x1b, 0xb6, 0x6f, 0x7a, 0x47, 0x84, 0x76, 0xeb, 0x33, 0x52, 0x4b, 0x41, 0x22, 0x7d, 0x1c, 0xb4, 0x69, 0xfb, 0xa6, 0x77, 0x46, 0x68, 0xaf, 0x31, 0x2f, 0xb5, 0x14, 0x25, 0xd2, 0xc7, 0xa1,
0x6a, 0x40, 0x3f, 0xd1, 0x60, 0xa6, 0x4d, 0xc9, 0x3e, 0xa1, 0x94, 0x58, 0x12, 0x5f, 0xc9, 0x2e, 0x06, 0xf4, 0x63, 0x0d, 0xe6, 0x3b, 0x94, 0x1c, 0x13, 0x4a, 0x89, 0x25, 0xf1, 0xd5, 0xec, 0xaa,
0x6a, 0x9f, 0x80, 0xda, 0x8a, 0x54, 0x3b, 0xb3, 0xd3, 0x27, 0x1f, 0x0f, 0x68, 0x44, 0xbf, 0xd3, 0xf6, 0x29, 0xa8, 0xad, 0x4a, 0xb5, 0xf3, 0x07, 0x03, 0xf2, 0xf1, 0x90, 0x46, 0xf4, 0x5b, 0x0d,
0x60, 0xde, 0x27, 0xf4, 0x88, 0xd0, 0x55, 0xcb, 0xa2, 0xc4, 0xf7, 0xeb, 0xdd, 0x35, 0xc7, 0x26, 0x96, 0x7d, 0x42, 0xcf, 0x08, 0xdd, 0xb4, 0x2c, 0x4a, 0x7c, 0xbf, 0xd1, 0xdb, 0x72, 0x6c, 0xe2,
0x6e, 0xb0, 0xb6, 0xd9, 0xc0, 0x7e, 0x65, 0x9c, 0x9f, 0xc3, 0x37, 0xd2, 0x19, 0xb4, 0x3b, 0x4c, 0x06, 0x5b, 0xbb, 0x4d, 0xec, 0x57, 0x73, 0xfc, 0x1c, 0xbe, 0x9e, 0xce, 0xa0, 0xc3, 0x71, 0x72,
0x4e, 0x5d, 0x97, 0x16, 0xcd, 0x0f, 0x25, 0xf1, 0xf1, 0x0b, 0xcc, 0xd0, 0xf7, 0x61, 0x52, 0x5d, 0x1a, 0xba, 0xb4, 0x68, 0x79, 0x2c, 0x89, 0x8f, 0x1f, 0x63, 0x86, 0x7e, 0x0c, 0x33, 0xea, 0x22,
0xe4, 0x6d, 0xdb, 0x0f, 0xd0, 0x7d, 0xc8, 0x37, 0xd9, 0x87, 0x5f, 0xd1, 0xb8, 0x81, 0xb5, 0x74, 0x6f, 0xdb, 0x7e, 0x80, 0xee, 0x42, 0xa1, 0xc5, 0x16, 0x7e, 0x55, 0xe3, 0x06, 0xd6, 0xd3, 0x19,
0x06, 0x2a, 0x19, 0xf5, 0x29, 0x69, 0x4f, 0x9e, 0x7f, 0xfa, 0x58, 0x4a, 0xd3, 0x7f, 0x31, 0x0e, 0xa8, 0x64, 0x34, 0x66, 0xa5, 0x3d, 0x05, 0xbe, 0xf4, 0xb1, 0x94, 0xa6, 0xff, 0x3c, 0x07, 0xe5,
0xa5, 0xd5, 0x9d, 0x4d, 0x4c, 0x7c, 0xaf, 0x43, 0x4d, 0x92, 0x22, 0x68, 0xae, 0xc3, 0xa4, 0x6f, 0xcd, 0x83, 0x5d, 0x4c, 0x7c, 0xaf, 0x4b, 0x4d, 0x92, 0xc2, 0x69, 0x6e, 0xc0, 0x8c, 0x6f, 0xbb,
0xbb, 0xcd, 0x8e, 0x63, 0x50, 0x06, 0xad, 0xe4, 0x39, 0xe5, 0x05, 0x49, 0x39, 0xb9, 0x1b, 0xc3, 0xad, 0xae, 0x63, 0x50, 0x06, 0xad, 0x16, 0x38, 0xe5, 0x92, 0xa4, 0x9c, 0x39, 0x8c, 0xe1, 0x70,
0xe1, 0x04, 0x25, 0xba, 0x06, 0xc0, 0x24, 0xf8, 0x6d, 0xc3, 0x24, 0x56, 0x25, 0xb3, 0xa8, 0x2d, 0x82, 0x12, 0x5d, 0x07, 0x60, 0x12, 0xfc, 0x8e, 0x61, 0x12, 0xab, 0x9a, 0x59, 0xd5, 0xd6, 0x8a,
0x15, 0xea, 0x48, 0xf2, 0xc1, 0x9d, 0x10, 0x83, 0x63, 0x54, 0xe8, 0x55, 0xc8, 0x71, 0x4b, 0x2b, 0x0d, 0x24, 0xf9, 0xe0, 0xcd, 0x10, 0x83, 0x63, 0x54, 0xe8, 0x19, 0xc8, 0x73, 0x4b, 0xab, 0x45,
0x05, 0xae, 0xa6, 0x2c, 0xc9, 0x73, 0xdc, 0x0d, 0x2c, 0x70, 0xe8, 0x0a, 0x4c, 0xc8, 0x28, 0xab, 0xae, 0xa6, 0x22, 0xc9, 0xf3, 0x7c, 0x1b, 0x58, 0xe0, 0xd0, 0x73, 0x30, 0x2d, 0xbd, 0xac, 0x5a,
0x14, 0x39, 0xd9, 0xb4, 0x24, 0x9b, 0x50, 0x61, 0xa0, 0xf0, 0xcc, 0xbf, 0x43, 0xdb, 0xb5, 0x78, 0xe2, 0x64, 0x73, 0x92, 0x6c, 0x5a, 0xb9, 0x81, 0xc2, 0xb3, 0xfd, 0x9d, 0xda, 0xae, 0xc5, 0xfd,
0xdc, 0xc5, 0xfc, 0x7b, 0xc7, 0x76, 0x2d, 0xcc, 0x31, 0xe8, 0x36, 0xe4, 0x8e, 0x08, 0xdd, 0x63, 0x2e, 0xb6, 0xbf, 0x37, 0x6c, 0xd7, 0xc2, 0x1c, 0x83, 0x6e, 0x43, 0xfe, 0x8c, 0xd0, 0x23, 0xe6,
0x91, 0xc0, 0x42, 0xf3, 0x4b, 0xe9, 0x0e, 0xfa, 0x3e, 0x63, 0xa9, 0x17, 0x99, 0x69, 0xfc, 0x27, 0x09, 0xcc, 0x35, 0xbf, 0x94, 0xee, 0xa0, 0xef, 0x32, 0x96, 0x46, 0x89, 0x99, 0xc6, 0x3f, 0xb1,
0x16, 0x42, 0x50, 0x0d, 0xc0, 0x3f, 0xf0, 0x68, 0xc0, 0xdd, 0xab, 0xe4, 0x16, 0xb3, 0x4b, 0xc5, 0x10, 0x82, 0xea, 0x00, 0xfe, 0x89, 0x47, 0x03, 0xbe, 0xbd, 0x6a, 0x7e, 0x35, 0xbb, 0x56, 0x6a,
0xfa, 0x14, 0xf3, 0x77, 0x37, 0x84, 0xe2, 0x18, 0x05, 0xa3, 0x37, 0x8d, 0x80, 0x34, 0x3d, 0x6a, 0xcc, 0xb2, 0xfd, 0x1e, 0x86, 0x50, 0x1c, 0xa3, 0x60, 0xf4, 0xa6, 0x11, 0x90, 0x96, 0x47, 0x6d,
0x13, 0xbf, 0x32, 0x11, 0xd1, 0xaf, 0x85, 0x50, 0x1c, 0xa3, 0x40, 0xdf, 0x02, 0xe4, 0x07, 0x1e, 0xe2, 0x57, 0xa7, 0x23, 0xfa, 0xad, 0x10, 0x8a, 0x63, 0x14, 0xe8, 0x9b, 0x80, 0xfc, 0xc0, 0xa3,
0x35, 0x9a, 0x44, 0xba, 0xfa, 0xb6, 0xe1, 0x1f, 0x54, 0x80, 0x7b, 0x37, 0x2f, 0xbd, 0x43, 0xbb, 0x46, 0x8b, 0xc8, 0xad, 0xbe, 0x6e, 0xf8, 0x27, 0x55, 0xe0, 0xbb, 0x5b, 0x96, 0xbb, 0x43, 0x87,
0x03, 0x14, 0xf8, 0x0c, 0x2e, 0xfd, 0x0f, 0x1a, 0x4c, 0xc7, 0x62, 0x81, 0xc7, 0xdd, 0x75, 0x98, 0x43, 0x14, 0x78, 0x04, 0x97, 0xfe, 0x7b, 0x0d, 0xe6, 0x62, 0xbe, 0xc0, 0xfd, 0xee, 0x06, 0xcc,
0x6c, 0xc6, 0x5e, 0x9d, 0x8c, 0x8b, 0xf0, 0xb6, 0xe3, 0x2f, 0x12, 0x27, 0x28, 0x11, 0x81, 0x22, 0xb4, 0x62, 0xaf, 0x4e, 0xfa, 0x45, 0x78, 0xdb, 0xf1, 0x17, 0x89, 0x13, 0x94, 0x88, 0x40, 0x89,
0x95, 0x92, 0x54, 0x76, 0x59, 0x49, 0x1d, 0xb4, 0xca, 0x86, 0x48, 0x53, 0x0c, 0xe8, 0xe3, 0x48, 0x4a, 0x49, 0x2a, 0xba, 0x6c, 0xa4, 0x76, 0x5a, 0x65, 0x43, 0xa4, 0x29, 0x06, 0xf4, 0x71, 0x24,
0xb2, 0xfe, 0x4f, 0x8d, 0x07, 0xb0, 0xca, 0x37, 0x68, 0x29, 0x96, 0xd3, 0x34, 0x7e, 0x7c, 0x93, 0x59, 0xff, 0x87, 0xc6, 0x1d, 0x58, 0xc5, 0x1b, 0xb4, 0x16, 0x8b, 0x69, 0x1a, 0x3f, 0xbe, 0x99,
0x43, 0xf2, 0xd1, 0x39, 0x89, 0x20, 0xf3, 0x7f, 0x91, 0x08, 0x6e, 0x14, 0x7e, 0xfd, 0x41, 0x75, 0x31, 0xf1, 0xe8, 0x8a, 0x40, 0x90, 0xf9, 0x9f, 0x08, 0x04, 0x37, 0x8b, 0xbf, 0xfa, 0xb0, 0x36,
0xec, 0xbd, 0xbf, 0x2f, 0x8e, 0xe9, 0x2d, 0x28, 0xaf, 0x51, 0x62, 0x04, 0x64, 0xbb, 0x1d, 0x70, 0xf5, 0xfe, 0xdf, 0x56, 0xa7, 0xf4, 0x36, 0x54, 0xb6, 0x28, 0x31, 0x02, 0xb2, 0xdf, 0x09, 0xf8,
0x07, 0x74, 0xc8, 0x5b, 0xb4, 0x8b, 0x3b, 0xae, 0x74, 0x14, 0xd8, 0xfb, 0x6e, 0x70, 0x08, 0x96, 0x06, 0x74, 0x28, 0x58, 0xb4, 0x87, 0xbb, 0xae, 0xdc, 0x28, 0xb0, 0xf7, 0xdd, 0xe4, 0x10, 0x2c,
0x18, 0x76, 0x7f, 0xfb, 0x36, 0x71, 0xac, 0x2d, 0xc3, 0x35, 0x9a, 0x84, 0xca, 0xb8, 0x0f, 0x4f, 0x31, 0xec, 0xfe, 0x8e, 0x6d, 0xe2, 0x58, 0x7b, 0x86, 0x6b, 0xb4, 0x08, 0x95, 0x7e, 0x1f, 0x9e,
0x75, 0x23, 0x86, 0xc3, 0x09, 0x4a, 0xfd, 0x67, 0x59, 0x28, 0x37, 0x88, 0x43, 0x22, 0x7d, 0x1b, 0xea, 0x4e, 0x0c, 0x87, 0x13, 0x94, 0xfa, 0x4f, 0xb3, 0x50, 0x69, 0x12, 0x87, 0x44, 0xfa, 0x76,
0x80, 0x9a, 0xd4, 0x30, 0xc9, 0x0e, 0xa1, 0xb6, 0x67, 0xed, 0x12, 0xd3, 0x73, 0x2d, 0x9f, 0x47, 0x00, 0xb5, 0xa8, 0x61, 0x92, 0x03, 0x42, 0x6d, 0xcf, 0x3a, 0x24, 0xa6, 0xe7, 0x5a, 0x3e, 0xf7,
0x44, 0xb6, 0xfe, 0x19, 0x16, 0x67, 0xb7, 0x06, 0xb0, 0xf8, 0x0c, 0x0e, 0xe4, 0x40, 0xb9, 0x4d, 0x88, 0x6c, 0xe3, 0xff, 0x98, 0x9f, 0xdd, 0x1a, 0xc2, 0xe2, 0x11, 0x1c, 0xc8, 0x81, 0x4a, 0x87,
0xf9, 0x6f, 0x3b, 0x90, 0xb5, 0x87, 0xbd, 0xb4, 0x2f, 0xa7, 0x3b, 0xea, 0x9d, 0x38, 0x6b, 0x7d, 0xf2, 0x6f, 0x3b, 0x90, 0xb9, 0x87, 0xbd, 0xb4, 0x2f, 0xa7, 0x3b, 0xea, 0x83, 0x38, 0x6b, 0x63,
0xf6, 0xa4, 0x57, 0x2d, 0x27, 0x40, 0x38, 0x29, 0x1c, 0x7d, 0x13, 0x66, 0x3c, 0xda, 0x3e, 0x30, 0xe1, 0xa2, 0x5f, 0xab, 0x24, 0x40, 0x38, 0x29, 0x1c, 0x7d, 0x03, 0xe6, 0x3d, 0xda, 0x39, 0x31,
0xdc, 0x06, 0x69, 0x13, 0xd7, 0x22, 0x6e, 0xe0, 0xf3, 0x53, 0x28, 0xd4, 0x2f, 0xb0, 0x8a, 0xb1, 0xdc, 0x26, 0xe9, 0x10, 0xd7, 0x22, 0x6e, 0xe0, 0xf3, 0x53, 0x28, 0x36, 0x96, 0x58, 0xc6, 0xd8,
0xdd, 0x87, 0xc3, 0x03, 0xd4, 0xe8, 0x01, 0xcc, 0xb6, 0xa9, 0xd7, 0x36, 0x9a, 0x06, 0x93, 0xb8, 0x1f, 0xc0, 0xe1, 0x21, 0x6a, 0xf4, 0x36, 0x2c, 0x74, 0xa8, 0xd7, 0x31, 0x5a, 0x06, 0x93, 0x78,
0xe3, 0x39, 0xb6, 0xd9, 0xe5, 0xd9, 0xa1, 0x58, 0xbf, 0x7a, 0xd2, 0xab, 0xce, 0xee, 0xf4, 0x23, 0xe0, 0x39, 0xb6, 0xd9, 0xe3, 0xd1, 0xa1, 0xd4, 0x78, 0xfe, 0xa2, 0x5f, 0x5b, 0x38, 0x18, 0x44,
0x4f, 0x7b, 0xd5, 0x39, 0x7e, 0x74, 0x0c, 0x12, 0x21, 0xf1, 0xa0, 0x98, 0xd8, 0x1d, 0xe6, 0x86, 0x5e, 0xf6, 0x6b, 0x8b, 0xfc, 0xe8, 0x18, 0x24, 0x42, 0xe2, 0x61, 0x31, 0xb1, 0x3b, 0xcc, 0x8f,
0xdd, 0xa1, 0xbe, 0x09, 0x85, 0x46, 0x87, 0x72, 0x2e, 0xf4, 0x16, 0x14, 0x2c, 0xf9, 0x5b, 0x9e, 0xbb, 0x43, 0x7d, 0x17, 0x8a, 0xcd, 0x2e, 0xe5, 0x5c, 0xe8, 0x55, 0x28, 0x5a, 0xf2, 0x5b, 0x9e,
0xfc, 0x2b, 0xaa, 0xe4, 0x2a, 0x9a, 0xd3, 0x5e, 0xb5, 0xcc, 0x9a, 0x84, 0x9a, 0x02, 0xe0, 0x90, 0xfc, 0xd3, 0x2a, 0xe5, 0x2a, 0x9a, 0xcb, 0x7e, 0xad, 0xc2, 0x8a, 0x84, 0xba, 0x02, 0xe0, 0x90,
0x45, 0x7f, 0x08, 0xe5, 0xf5, 0xc7, 0x6d, 0x8f, 0x06, 0xea, 0x4e, 0x2f, 0x43, 0x9e, 0x70, 0x00, 0x45, 0x7f, 0x07, 0x2a, 0xdb, 0xf7, 0x3a, 0x1e, 0x0d, 0xd4, 0x9d, 0x3e, 0x0b, 0x05, 0xc2, 0x01,
0x97, 0x56, 0x88, 0xea, 0x84, 0x20, 0xc3, 0x12, 0xcb, 0xf2, 0x30, 0x79, 0x6c, 0x98, 0x81, 0x4c, 0x5c, 0x5a, 0x31, 0xca, 0x13, 0x82, 0x0c, 0x4b, 0x2c, 0x8b, 0xc3, 0xe4, 0x9e, 0x61, 0x06, 0x32,
0xdb, 0x61, 0x1e, 0x5e, 0x67, 0x40, 0x2c, 0x70, 0xfa, 0x65, 0x28, 0xf0, 0x80, 0xf2, 0xef, 0xaf, 0x6c, 0x87, 0x71, 0x78, 0x9b, 0x01, 0xb1, 0xc0, 0xe9, 0xcf, 0x42, 0x91, 0x3b, 0x94, 0x7f, 0x77,
0xa0, 0x19, 0xc8, 0x62, 0xe3, 0x98, 0x4b, 0x9d, 0xc4, 0x59, 0x6a, 0x1c, 0xc7, 0x22, 0x79, 0x1b, 0x03, 0xcd, 0x43, 0x16, 0x1b, 0xe7, 0x5c, 0xea, 0x0c, 0xce, 0x52, 0xe3, 0x3c, 0xe6, 0xc9, 0xfb,
0xe0, 0x16, 0x09, 0x4d, 0x58, 0x85, 0x69, 0xf5, 0x9c, 0x93, 0x59, 0xe6, 0xb3, 0x52, 0xc9, 0x34, 0x00, 0xb7, 0x48, 0x68, 0xc2, 0x26, 0xcc, 0xa9, 0xe7, 0x9c, 0x8c, 0x32, 0xff, 0x2f, 0x95, 0xcc,
0x4e, 0xa2, 0x71, 0x3f, 0xbd, 0xfe, 0x10, 0x8a, 0x3c, 0x13, 0xb1, 0x34, 0x1e, 0x95, 0x0c, 0xed, 0xe1, 0x24, 0x1a, 0x0f, 0xd2, 0xeb, 0xef, 0x40, 0x89, 0x47, 0x22, 0x16, 0xc6, 0xa3, 0x94, 0xa1,
0x05, 0x25, 0x43, 0xd5, 0x81, 0xcc, 0xb0, 0x3a, 0x10, 0x33, 0xd7, 0x81, 0xb2, 0xe0, 0x55, 0x45, 0x3d, 0x26, 0x65, 0xa8, 0x3c, 0x90, 0x19, 0x97, 0x07, 0x62, 0xe6, 0x3a, 0x50, 0x11, 0xbc, 0x2a,
0x32, 0x95, 0x86, 0xab, 0x50, 0x50, 0x66, 0x4a, 0x2d, 0x61, 0x73, 0xa4, 0x04, 0xe1, 0x90, 0x22, 0x49, 0xa6, 0xd2, 0xf0, 0x3c, 0x14, 0x95, 0x99, 0x52, 0x4b, 0x58, 0x1c, 0x29, 0x41, 0x38, 0xa4,
0xa6, 0xed, 0x00, 0x12, 0x59, 0x35, 0x9d, 0xb2, 0x58, 0x05, 0xcc, 0xbc, 0xb8, 0x02, 0xc6, 0x34, 0x88, 0x69, 0x3b, 0x81, 0x44, 0x54, 0x4d, 0xa7, 0x2c, 0x96, 0x01, 0x33, 0x8f, 0xcf, 0x80, 0x31,
0xfd, 0x18, 0x2a, 0xc3, 0x3a, 0xaa, 0x97, 0xc8, 0xfb, 0xe9, 0x4d, 0xd1, 0xdf, 0xd7, 0x60, 0x26, 0x4d, 0x3f, 0x82, 0xea, 0xb8, 0x8a, 0xea, 0x09, 0xe2, 0x7e, 0x7a, 0x53, 0xf4, 0x0f, 0x34, 0x98,
0x2e, 0x29, 0xfd, 0xf5, 0xa5, 0x57, 0x72, 0x7e, 0xc5, 0x8f, 0x9d, 0xc8, 0x6f, 0x35, 0xb8, 0x90, 0x8f, 0x4b, 0x4a, 0x7f, 0x7d, 0xe9, 0x95, 0x5c, 0x9d, 0xf1, 0x63, 0x27, 0xf2, 0x1b, 0x0d, 0x96,
0x70, 0x6d, 0xa4, 0x1b, 0x1f, 0xc1, 0xa8, 0x78, 0x70, 0x64, 0x47, 0x08, 0x8e, 0xbf, 0x66, 0xa0, 0x12, 0x5b, 0x9b, 0xe8, 0xc6, 0x27, 0x30, 0x2a, 0xee, 0x1c, 0xd9, 0x09, 0x9c, 0xe3, 0x2f, 0x19,
0x7c, 0xdb, 0xd8, 0x23, 0xce, 0x2e, 0x71, 0x88, 0x19, 0x78, 0x14, 0xfd, 0x08, 0x4a, 0x2d, 0x23, 0xa8, 0xdc, 0x36, 0x8e, 0x88, 0x73, 0x48, 0x1c, 0x62, 0x06, 0x1e, 0x45, 0x3f, 0x84, 0x72, 0xdb,
0x30, 0x0f, 0x38, 0x54, 0x75, 0x87, 0x8d, 0x74, 0xa9, 0x34, 0x21, 0xa9, 0xb6, 0x15, 0x89, 0x59, 0x08, 0xcc, 0x13, 0x0e, 0x55, 0xd5, 0x61, 0x33, 0x5d, 0x28, 0x4d, 0x48, 0xaa, 0xef, 0x45, 0x62,
0x77, 0x03, 0xda, 0xad, 0xcf, 0x49, 0x93, 0x4a, 0x31, 0x0c, 0x8e, 0x6b, 0xe3, 0x2d, 0x3d, 0xff, 0xb6, 0xdd, 0x80, 0xf6, 0x1a, 0x8b, 0xd2, 0xa4, 0x72, 0x0c, 0x83, 0xe3, 0xda, 0x78, 0x49, 0xcf,
0x5e, 0x7f, 0xdc, 0x66, 0xa5, 0x6b, 0xf4, 0x49, 0x22, 0x61, 0x02, 0x26, 0xef, 0x76, 0x6c, 0x4a, 0xd7, 0xdb, 0xf7, 0x3a, 0x2c, 0x75, 0x4d, 0xde, 0x49, 0x24, 0x4c, 0xc0, 0xe4, 0xbd, 0xae, 0x4d,
0x5a, 0xc4, 0x0d, 0xa2, 0x96, 0x7e, 0xab, 0x4f, 0x3e, 0x1e, 0xd0, 0x38, 0x7f, 0x13, 0x66, 0xfa, 0x49, 0x9b, 0xb8, 0x41, 0x54, 0xd2, 0xef, 0x0d, 0xc8, 0xc7, 0x43, 0x1a, 0x97, 0x5f, 0x83, 0xf9,
0x8d, 0x67, 0xf9, 0xe7, 0x90, 0x74, 0xc5, 0x7d, 0x61, 0xf6, 0x13, 0x5d, 0x80, 0xdc, 0x91, 0xe1, 0x41, 0xe3, 0x59, 0xfc, 0x39, 0x25, 0x3d, 0x71, 0x5f, 0x98, 0x7d, 0xa2, 0x25, 0xc8, 0x9f, 0x19,
0x74, 0xe4, 0x6b, 0xc4, 0xe2, 0xe3, 0x46, 0xe6, 0xba, 0xa6, 0xff, 0x5e, 0x83, 0xca, 0x30, 0x43, 0x4e, 0x57, 0xbe, 0x46, 0x2c, 0x16, 0x37, 0x33, 0x37, 0x34, 0xfd, 0x77, 0x1a, 0x54, 0xc7, 0x19,
0xd0, 0xe7, 0x63, 0x82, 0xea, 0x25, 0x69, 0x55, 0xf6, 0x1d, 0xd2, 0x15, 0x52, 0xd7, 0xa1, 0xe0, 0x82, 0x3e, 0x1f, 0x13, 0xd4, 0x28, 0x4b, 0xab, 0xb2, 0x6f, 0x90, 0x9e, 0x90, 0xba, 0x0d, 0x45,
0xb5, 0xd9, 0x10, 0xe6, 0x51, 0x79, 0xeb, 0x57, 0xd4, 0x4d, 0x6e, 0x4b, 0xf8, 0x69, 0xaf, 0x7a, 0xaf, 0xc3, 0x9a, 0x30, 0x8f, 0xca, 0x5b, 0x7f, 0x4e, 0xdd, 0xe4, 0xbe, 0x84, 0x5f, 0xf6, 0x6b,
0x31, 0x21, 0x5e, 0x21, 0x70, 0xc8, 0xca, 0xea, 0x00, 0xb7, 0x87, 0xd5, 0xa6, 0xb0, 0x0e, 0xdc, 0xd7, 0x12, 0xe2, 0x15, 0x02, 0x87, 0xac, 0x2c, 0x0f, 0x70, 0x7b, 0x58, 0x6e, 0x0a, 0xf3, 0xc0,
0xe7, 0x10, 0x2c, 0x31, 0xfa, 0x1f, 0x35, 0x18, 0xe7, 0x4d, 0xd9, 0x43, 0x28, 0xb0, 0xf3, 0xb3, 0x5d, 0x0e, 0xc1, 0x12, 0xa3, 0xff, 0x51, 0x83, 0x1c, 0x2f, 0xca, 0xde, 0x81, 0x22, 0x3b, 0x3f,
0x8c, 0xc0, 0xe0, 0x76, 0xa5, 0x1e, 0x07, 0x18, 0xf7, 0x16, 0x09, 0x8c, 0x28, 0xda, 0x14, 0x04, 0xcb, 0x08, 0x0c, 0x6e, 0x57, 0xea, 0x76, 0x80, 0x71, 0xef, 0x91, 0xc0, 0x88, 0xbc, 0x4d, 0x41,
0x87, 0x12, 0x11, 0x86, 0x9c, 0x1d, 0x90, 0x96, 0xba, 0xc8, 0xd7, 0x86, 0x8a, 0x96, 0xc3, 0x68, 0x70, 0x28, 0x11, 0x61, 0xc8, 0xdb, 0x01, 0x69, 0xab, 0x8b, 0x7c, 0x61, 0xac, 0x68, 0xd9, 0x8c,
0x0d, 0x1b, 0xc7, 0xeb, 0x8f, 0x03, 0xe2, 0xb2, 0xcb, 0x88, 0x9e, 0xc6, 0x26, 0x93, 0x81, 0x85, 0xd6, 0xb1, 0x71, 0xbe, 0x7d, 0x2f, 0x20, 0x2e, 0xbb, 0x8c, 0xe8, 0x69, 0xec, 0x32, 0x19, 0x58,
0x28, 0xfd, 0xdf, 0x1a, 0x84, 0xaa, 0x58, 0xf0, 0xfb, 0xc4, 0xd9, 0xbf, 0x6d, 0xbb, 0x87, 0xf2, 0x88, 0xd2, 0xff, 0xa5, 0x41, 0xa8, 0x8a, 0x39, 0xbf, 0x4f, 0x9c, 0xe3, 0xdb, 0xb6, 0x7b, 0x2a,
0x58, 0x43, 0x73, 0x76, 0x25, 0x1c, 0x87, 0x14, 0x67, 0x95, 0x87, 0xcc, 0x68, 0xe5, 0x81, 0x29, 0x8f, 0x35, 0x34, 0xe7, 0x50, 0xc2, 0x71, 0x48, 0x31, 0x2a, 0x3d, 0x64, 0x26, 0x4b, 0x0f, 0x4c,
0x34, 0x3d, 0x37, 0xb0, 0xdd, 0xce, 0xc0, 0x6b, 0x5b, 0x93, 0x70, 0x1c, 0x52, 0xb0, 0x36, 0x87, 0xa1, 0xe9, 0xb9, 0x81, 0xed, 0x76, 0x87, 0x5e, 0xdb, 0x96, 0x84, 0xe3, 0x90, 0x82, 0x95, 0x39,
0x92, 0x96, 0x61, 0xbb, 0xb6, 0xdb, 0x64, 0x4e, 0xac, 0x79, 0x1d, 0x37, 0xe0, 0xf5, 0x5e, 0xb6, 0x94, 0xb4, 0x0d, 0xdb, 0xb5, 0xdd, 0x16, 0xdb, 0xc4, 0x96, 0xd7, 0x75, 0x03, 0x9e, 0xef, 0x65,
0x39, 0x78, 0x00, 0x8b, 0xcf, 0xe0, 0xd0, 0xff, 0x92, 0x85, 0x12, 0xf3, 0x59, 0xd5, 0xb9, 0x37, 0x99, 0x83, 0x87, 0xb0, 0x78, 0x04, 0x87, 0xfe, 0x87, 0x1c, 0x94, 0xd9, 0x9e, 0x55, 0x9e, 0x7b,
0xa1, 0xec, 0xc4, 0xa3, 0x40, 0xfa, 0x7e, 0x51, 0x9a, 0x92, 0x7c, 0xd7, 0x38, 0x49, 0xcb, 0x98, 0x05, 0x2a, 0x4e, 0xdc, 0x0b, 0xe4, 0xde, 0xaf, 0x49, 0x53, 0x92, 0xef, 0x1a, 0x27, 0x69, 0x19,
0x79, 0x77, 0x16, 0x32, 0x67, 0x92, 0xcc, 0x1b, 0x71, 0x24, 0x4e, 0xd2, 0xb2, 0xec, 0x75, 0xcc, 0x33, 0xaf, 0xce, 0x42, 0xe6, 0x4c, 0x92, 0x79, 0x27, 0x8e, 0xc4, 0x49, 0x5a, 0x16, 0xbd, 0xce,
0xde, 0x87, 0xec, 0x7b, 0xc2, 0x2b, 0xfa, 0x36, 0x03, 0x62, 0x81, 0x43, 0x5b, 0x30, 0x67, 0x38, 0xd9, 0xfb, 0x90, 0x75, 0x4f, 0x78, 0x45, 0xdf, 0x62, 0x40, 0x2c, 0x70, 0x68, 0x0f, 0x16, 0x0d,
0x8e, 0x77, 0xcc, 0x81, 0x75, 0xcf, 0x3b, 0x6c, 0x19, 0xf4, 0xd0, 0xe7, 0x03, 0x55, 0xa1, 0xfe, 0xc7, 0xf1, 0xce, 0x39, 0xb0, 0xe1, 0x79, 0xa7, 0x6d, 0x83, 0x9e, 0xfa, 0xbc, 0xa1, 0x2a, 0x36,
0x39, 0xc9, 0x32, 0xb7, 0x3a, 0x48, 0x82, 0xcf, 0xe2, 0x3b, 0xeb, 0xda, 0xc6, 0x47, 0xbc, 0xb6, 0x3e, 0x27, 0x59, 0x16, 0x37, 0x87, 0x49, 0xf0, 0x28, 0xbe, 0x51, 0xd7, 0x96, 0x9b, 0xf0, 0xda,
0x1b, 0x30, 0xc5, 0xe2, 0xcb, 0xeb, 0x04, 0xaa, 0xd7, 0xcc, 0xf1, 0x4b, 0x40, 0x27, 0xbd, 0xea, 0x4e, 0x60, 0x69, 0x00, 0xc4, 0x5f, 0xb9, 0xec, 0x6e, 0x5e, 0x92, 0x72, 0x96, 0xf0, 0x08, 0x9a,
0xd4, 0xdd, 0x04, 0x06, 0xf7, 0x51, 0x32, 0x97, 0x1d, 0xbb, 0x65, 0x07, 0x95, 0x09, 0xce, 0x12, 0xcb, 0x31, 0x70, 0x3c, 0x52, 0x22, 0xba, 0x09, 0xb3, 0xcc, 0x93, 0xbd, 0x6e, 0xa0, 0xaa, 0xda,
0xba, 0x7c, 0x9b, 0x01, 0xb1, 0xc0, 0x25, 0xe2, 0xa2, 0x70, 0x5e, 0x5c, 0xe8, 0xbf, 0xc9, 0x02, 0x3c, 0xbf, 0x6e, 0x74, 0xd1, 0xaf, 0xcd, 0xbe, 0x95, 0xc0, 0xe0, 0x01, 0x4a, 0x76, 0xb8, 0x8e,
0x12, 0xcd, 0xb1, 0x25, 0xba, 0x1c, 0x91, 0x68, 0xae, 0xc0, 0x44, 0x4b, 0x36, 0xd7, 0x5a, 0x32, 0xdd, 0xb6, 0x83, 0xea, 0x34, 0x67, 0x09, 0x0f, 0xf7, 0x36, 0x03, 0x62, 0x81, 0x4b, 0x78, 0x60,
0xeb, 0xab, 0xbe, 0x5a, 0xe1, 0xd1, 0x16, 0x14, 0xc5, 0x83, 0x8f, 0x82, 0x78, 0x59, 0x12, 0x17, 0xf1, 0x2a, 0x0f, 0xd4, 0x7f, 0x9d, 0x05, 0x24, 0xca, 0x70, 0x4b, 0xd4, 0x53, 0x22, 0xa4, 0x3d,
0xb7, 0x15, 0xe2, 0xb4, 0x57, 0x9d, 0x4f, 0xa8, 0x09, 0x31, 0x77, 0xbb, 0x6d, 0x82, 0x23, 0x09, 0x07, 0xd3, 0x6d, 0x59, 0xc6, 0x6b, 0xc9, 0xfc, 0xa2, 0x2a, 0x78, 0x85, 0x47, 0x7b, 0x50, 0x12,
0x6c, 0x9e, 0x36, 0xda, 0x76, 0x7c, 0x93, 0x52, 0x8c, 0xe6, 0xe9, 0x68, 0x26, 0xc2, 0x31, 0x2a, 0xa1, 0x25, 0x7a, 0x2e, 0xeb, 0x92, 0xb8, 0xb4, 0xaf, 0x10, 0x97, 0xfd, 0xda, 0x72, 0x42, 0x4d,
0xf4, 0x36, 0x8c, 0xb3, 0x93, 0x92, 0xc3, 0xed, 0x17, 0xd3, 0xa5, 0x0d, 0x76, 0xd6, 0xf5, 0x02, 0x88, 0x79, 0xab, 0xd7, 0x21, 0x38, 0x92, 0xc0, 0x3a, 0x77, 0xa3, 0x63, 0xc7, 0x67, 0x36, 0xa5,
0xab, 0x9a, 0xec, 0x17, 0xe6, 0x12, 0x98, 0x76, 0x1e, 0x65, 0x3e, 0x33, 0x4b, 0x6e, 0x01, 0x42, 0xa8, 0x73, 0x8f, 0xba, 0x2f, 0x1c, 0xa3, 0x42, 0xaf, 0x43, 0x8e, 0x9d, 0x94, 0x6c, 0xa3, 0xbf,
0xed, 0x1b, 0x21, 0x06, 0xc7, 0xa8, 0xd0, 0x77, 0xa0, 0xb0, 0x2f, 0x1b, 0x44, 0x7e, 0x31, 0xa9, 0x98, 0x2e, 0x40, 0xb1, 0xb3, 0x6e, 0x14, 0x59, 0x7e, 0x66, 0x5f, 0x98, 0x4b, 0x60, 0xda, 0xb9,
0x13, 0x97, 0x6a, 0x2b, 0xc5, 0x30, 0xa7, 0xbe, 0x70, 0x28, 0x4d, 0x7f, 0x17, 0x8a, 0x5b, 0xb6, 0x3f, 0xfb, 0xcc, 0x2c, 0x39, 0x6f, 0x08, 0xb5, 0xef, 0x84, 0x18, 0x1c, 0xa3, 0x42, 0xdf, 0x86,
0x49, 0x3d, 0x66, 0x20, 0xbb, 0x12, 0x3f, 0x31, 0x9d, 0x84, 0x57, 0xa2, 0xc2, 0x45, 0xe1, 0x59, 0xe2, 0xb1, 0x2c, 0x45, 0xf9, 0xc5, 0xa4, 0x0e, 0x91, 0xaa, 0x80, 0x15, 0x6d, 0xa3, 0x5a, 0xe1,
0x9c, 0xb8, 0x86, 0xeb, 0x89, 0x19, 0x24, 0x17, 0xc5, 0xc9, 0x1d, 0x06, 0xc4, 0x02, 0x77, 0xe3, 0x50, 0x9a, 0xfe, 0x1e, 0x94, 0xf6, 0x6c, 0x93, 0x7a, 0xcc, 0x40, 0x76, 0x25, 0x7e, 0xa2, 0x0f,
0x02, 0xab, 0xbf, 0x3f, 0x7f, 0x5a, 0x1d, 0x7b, 0xf2, 0xb4, 0x3a, 0xf6, 0xc1, 0x53, 0x59, 0x8b, 0x0a, 0xaf, 0x44, 0xb9, 0x8b, 0xc2, 0x33, 0x3f, 0x71, 0x0d, 0xd7, 0x13, 0xdd, 0x4e, 0x3e, 0xf2,
0x4f, 0x01, 0x60, 0x7b, 0xef, 0x07, 0xc4, 0x14, 0x59, 0x2d, 0xd5, 0xe6, 0x44, 0x2d, 0xec, 0xf8, 0x93, 0x37, 0x19, 0x10, 0x0b, 0xdc, 0xcd, 0x25, 0x96, 0xe9, 0x7f, 0xf6, 0xa0, 0x36, 0x75, 0xff,
0xe6, 0x24, 0xd3, 0xd7, 0x53, 0xc5, 0x70, 0x38, 0x41, 0x89, 0x96, 0xa1, 0x18, 0xee, 0x44, 0xe4, 0x41, 0x6d, 0xea, 0xc3, 0x07, 0x32, 0xeb, 0x5f, 0x02, 0xc0, 0xfe, 0xd1, 0xf7, 0x89, 0x29, 0xe2,
0x45, 0xcf, 0xaa, 0xc0, 0x09, 0x17, 0x27, 0x38, 0xa2, 0x49, 0xa4, 0xd8, 0xf1, 0x73, 0x53, 0x6c, 0x67, 0xaa, 0x19, 0x8d, 0x1a, 0x0d, 0xf2, 0x19, 0x4d, 0x66, 0xa0, 0x7a, 0x8b, 0xe1, 0x70, 0x82,
0x1d, 0xb2, 0x1d, 0xdb, 0xe2, 0xaf, 0xab, 0x58, 0x7f, 0x5d, 0x95, 0xb8, 0x7b, 0x9b, 0x8d, 0xd3, 0x12, 0xad, 0x43, 0x29, 0x9c, 0xbe, 0xc8, 0x8b, 0x5e, 0x50, 0x8e, 0x13, 0x8e, 0x68, 0x70, 0x44,
0x5e, 0xf5, 0x95, 0x61, 0xab, 0xc8, 0xa0, 0xdb, 0x26, 0x7e, 0xed, 0xde, 0x66, 0x03, 0x33, 0xe6, 0x93, 0x08, 0xe6, 0xb9, 0x2b, 0x83, 0x79, 0x03, 0xb2, 0x5d, 0xdb, 0xe2, 0xaf, 0xab, 0xd4, 0x78,
0xb3, 0xde, 0x7b, 0x7e, 0xc4, 0xf7, 0x7e, 0x0d, 0x40, 0x7a, 0xcd, 0xb8, 0xc5, 0xc3, 0x0d, 0x23, 0x51, 0x25, 0xd3, 0x3b, 0xbb, 0xcd, 0xcb, 0x7e, 0xed, 0xe9, 0x71, 0x43, 0xcf, 0xa0, 0xd7, 0x21,
0xea, 0x56, 0x88, 0xc1, 0x31, 0x2a, 0xe4, 0xc3, 0xac, 0xc9, 0x86, 0x62, 0xf6, 0x3c, 0xec, 0x16, 0x7e, 0xfd, 0xce, 0x6e, 0x13, 0x33, 0xe6, 0x51, 0x91, 0xa5, 0x30, 0x61, 0x64, 0xb9, 0x0e, 0x20,
0xf1, 0x03, 0xa3, 0x25, 0x76, 0x45, 0xa3, 0x05, 0xf7, 0x25, 0xa9, 0x66, 0x76, 0xad, 0x5f, 0x18, 0x77, 0xcd, 0xb8, 0xc5, 0xc3, 0x0d, 0x3d, 0xea, 0x56, 0x88, 0xc1, 0x31, 0x2a, 0xe4, 0xc3, 0x82,
0x1e, 0x94, 0x8f, 0x3c, 0x98, 0xb5, 0xe4, 0x78, 0x17, 0x29, 0x2d, 0x8e, 0xac, 0xf4, 0x22, 0x53, 0xc9, 0xda, 0x6f, 0xf6, 0x3c, 0xec, 0x36, 0xf1, 0x03, 0xa3, 0x2d, 0xa6, 0x52, 0x93, 0x39, 0xf7,
0xd8, 0xe8, 0x17, 0x84, 0x07, 0x65, 0xa3, 0xef, 0xc3, 0xbc, 0x02, 0x0e, 0xce, 0xd8, 0x7c, 0xdb, 0x53, 0x52, 0xcd, 0xc2, 0xd6, 0xa0, 0x30, 0x3c, 0x2c, 0x1f, 0x79, 0xb0, 0x60, 0xc9, 0x46, 0x32,
0x93, 0xad, 0x2f, 0x9c, 0xf4, 0xaa, 0xf3, 0x8d, 0xa1, 0x54, 0xf8, 0x05, 0x12, 0x90, 0x05, 0x79, 0x52, 0x5a, 0x9a, 0x58, 0xe9, 0x35, 0xa6, 0xb0, 0x39, 0x28, 0x08, 0x0f, 0xcb, 0x46, 0xdf, 0x83,
0x47, 0xf4, 0x8f, 0x25, 0x5e, 0xf3, 0xbf, 0x9e, 0xce, 0x8b, 0x28, 0xfa, 0x6b, 0xf1, 0xbe, 0x31, 0x65, 0x05, 0x1c, 0xee, 0xe6, 0x79, 0xe4, 0xcd, 0x36, 0x56, 0x2e, 0xfa, 0xb5, 0xe5, 0xe6, 0x58,
0x9c, 0x21, 0x65, 0xcb, 0x28, 0x65, 0xa3, 0xc7, 0x50, 0x32, 0x5c, 0xd7, 0x0b, 0x0c, 0x31, 0xf5, 0x2a, 0xfc, 0x18, 0x09, 0xc8, 0x82, 0x82, 0x23, 0x2a, 0xd5, 0x32, 0xaf, 0x2e, 0xbe, 0x96, 0x6e,
0x4f, 0x72, 0x55, 0xab, 0x23, 0xab, 0x5a, 0x8d, 0x64, 0xf4, 0xf5, 0xa9, 0x31, 0x0c, 0x8e, 0xab, 0x17, 0x91, 0xf7, 0xd7, 0xe3, 0x15, 0x6a, 0xd8, 0xad, 0xca, 0xe2, 0x54, 0xca, 0x46, 0xf7, 0xa0,
0x42, 0xc7, 0x30, 0xed, 0x1d, 0xbb, 0x84, 0x62, 0xb2, 0x4f, 0x28, 0x71, 0x4d, 0xe2, 0x57, 0xca, 0x6c, 0xb8, 0xae, 0x17, 0x18, 0x62, 0xbe, 0x30, 0xc3, 0x55, 0x6d, 0x4e, 0xac, 0x6a, 0x33, 0x92,
0x5c, 0xfb, 0x57, 0x52, 0x6a, 0x4f, 0x30, 0x47, 0x21, 0x9d, 0x84, 0xfb, 0xb8, 0x5f, 0x0b, 0xaa, 0x31, 0x50, 0x11, 0xc7, 0x30, 0x38, 0xae, 0x0a, 0x9d, 0xc3, 0x9c, 0x77, 0xee, 0x12, 0x8a, 0xc9,
0xb1, 0x24, 0xe9, 0x1a, 0x8e, 0xfd, 0x43, 0x42, 0xfd, 0xca, 0x54, 0xb4, 0xce, 0xdb, 0x08, 0xa1, 0x31, 0xa1, 0xc4, 0x35, 0x89, 0x5f, 0xad, 0x70, 0xed, 0x2f, 0xa5, 0xd4, 0x9e, 0x60, 0x8e, 0x5c,
0x38, 0x46, 0x81, 0xbe, 0x0a, 0x25, 0xd3, 0xe9, 0xf8, 0x01, 0x11, 0xbb, 0xd5, 0x69, 0xfe, 0x82, 0x3a, 0x09, 0xf7, 0xf1, 0xa0, 0x16, 0x54, 0x67, 0x41, 0xd2, 0x35, 0x1c, 0xfb, 0x07, 0x84, 0xfa,
0x42, 0xff, 0xd6, 0x22, 0x14, 0x8e, 0xd3, 0xa1, 0x0e, 0x94, 0x5b, 0xf1, 0x92, 0x51, 0x99, 0xe5, 0xd5, 0xd9, 0x68, 0x70, 0xb8, 0x13, 0x42, 0x71, 0x8c, 0x02, 0x7d, 0x05, 0xca, 0xa6, 0xd3, 0xf5,
0xde, 0x5d, 0x4f, 0xe7, 0xdd, 0x60, 0x51, 0x8b, 0xfa, 0x8a, 0x04, 0x0e, 0x27, 0xb5, 0xcc, 0x7f, 0x03, 0x22, 0xa6, 0xb8, 0x73, 0xfc, 0x05, 0x85, 0xfb, 0xdb, 0x8a, 0x50, 0x38, 0x4e, 0x87, 0xba,
0x0d, 0x4a, 0xff, 0x65, 0xcb, 0xcd, 0x5a, 0xf6, 0xfe, 0x7b, 0x1c, 0xa9, 0x65, 0xff, 0x53, 0x06, 0x50, 0x69, 0xc7, 0x53, 0x46, 0x75, 0x81, 0xef, 0xee, 0x46, 0xba, 0xdd, 0x0d, 0x27, 0xb5, 0xa8,
0xa6, 0x92, 0xa7, 0xdf, 0x57, 0x0e, 0x73, 0xa9, 0xca, 0xa1, 0x1a, 0x0e, 0xb5, 0xa1, 0xeb, 0x60, 0x82, 0x49, 0xe0, 0x70, 0x52, 0xcb, 0xf2, 0x57, 0xa1, 0xfc, 0x1f, 0x16, 0xf7, 0xac, 0x39, 0x18,
0x95, 0xd6, 0xb3, 0x43, 0xd3, 0xba, 0xcc, 0x9e, 0xe3, 0x2f, 0x93, 0x3d, 0x6b, 0x00, 0xac, 0xcf, 0xbc, 0xc7, 0x89, 0x9a, 0x83, 0x3f, 0x65, 0x60, 0x36, 0x79, 0xfa, 0x03, 0xe9, 0x30, 0x9f, 0x2a,
0xa0, 0x9e, 0xe3, 0x10, 0xca, 0x13, 0x67, 0x41, 0xae, 0x7d, 0x43, 0x28, 0x8e, 0x51, 0xb0, 0x1e, 0x1d, 0xaa, 0x36, 0x54, 0x1b, 0x3b, 0x78, 0x56, 0x61, 0x3d, 0x3b, 0x36, 0xac, 0xcb, 0xe8, 0x99,
0x75, 0xcf, 0xf1, 0xcc, 0x43, 0x7e, 0x04, 0xea, 0xd1, 0xf3, 0x94, 0x59, 0x10, 0x3d, 0x6a, 0x7d, 0x7b, 0x92, 0xe8, 0x59, 0x07, 0x60, 0x75, 0x06, 0xf5, 0x1c, 0x87, 0x50, 0x1e, 0x38, 0x8b, 0x72,
0x00, 0x8b, 0xcf, 0xe0, 0xd0, 0xbb, 0x70, 0x71, 0xc7, 0xa0, 0x81, 0x6d, 0x38, 0xd1, 0x03, 0xe3, 0xc0, 0x1c, 0x42, 0x71, 0x8c, 0x82, 0x55, 0xc3, 0x47, 0x8e, 0x67, 0x9e, 0xf2, 0x23, 0x50, 0x8f,
0x43, 0xc0, 0xa3, 0x81, 0x11, 0xe3, 0xf5, 0x51, 0x1f, 0x6a, 0x74, 0xf8, 0x11, 0x2c, 0x1a, 0x33, 0x9e, 0x87, 0xcc, 0xa2, 0xa8, 0x86, 0x1b, 0x43, 0x58, 0x3c, 0x82, 0x43, 0xef, 0xc1, 0xb5, 0x03,
0xf4, 0xbf, 0x69, 0x70, 0xe9, 0x4c, 0xdd, 0x9f, 0xc2, 0x88, 0xf3, 0x28, 0x39, 0xe2, 0xbc, 0x99, 0x83, 0x06, 0xb6, 0xe1, 0x44, 0x0f, 0x8c, 0xb7, 0x1b, 0xef, 0x0e, 0x35, 0x33, 0x2f, 0x4e, 0xfa,
0x72, 0xf3, 0x78, 0x96, 0xb5, 0x43, 0x06, 0x9e, 0x09, 0xc8, 0xed, 0xb0, 0x86, 0x58, 0xff, 0x95, 0x50, 0xa3, 0xc3, 0x8f, 0x60, 0x51, 0x43, 0xa3, 0xff, 0x55, 0x83, 0xa7, 0x46, 0xea, 0xfe, 0x0c,
0x06, 0x93, 0xfc, 0xd7, 0x28, 0x5b, 0xdb, 0x2a, 0xe4, 0xf6, 0x3d, 0xb5, 0x38, 0x2a, 0x88, 0xbf, 0x9a, 0xa9, 0x77, 0x93, 0xcd, 0xd4, 0x2b, 0x29, 0x67, 0x9c, 0xa3, 0xac, 0x1d, 0xd3, 0x5a, 0x4d,
0x15, 0x36, 0x18, 0x00, 0x0b, 0xf8, 0x4b, 0xac, 0x75, 0xdf, 0xd7, 0x20, 0xb9, 0x2f, 0x45, 0x37, 0x43, 0xfe, 0x80, 0x15, 0xb1, 0xfa, 0x2f, 0x35, 0x98, 0xe1, 0x5f, 0x93, 0xcc, 0x87, 0x6b, 0x90,
0x45, 0xfc, 0x6a, 0xe1, 0x42, 0x73, 0xc4, 0xd8, 0x7d, 0x6b, 0xd8, 0x80, 0x36, 0x97, 0x6a, 0x77, 0x3f, 0xf6, 0xd4, 0x88, 0xaa, 0x28, 0x7e, 0x60, 0xec, 0x30, 0x00, 0x16, 0xf0, 0x27, 0x18, 0x20,
0x77, 0x15, 0x8a, 0xd8, 0xf3, 0x82, 0x1d, 0x23, 0x38, 0xf0, 0x99, 0xe3, 0x6d, 0xf6, 0x43, 0x9e, 0x7f, 0xa0, 0x41, 0x72, 0x32, 0x8b, 0x5e, 0x13, 0xfe, 0xab, 0x85, 0xa3, 0xd3, 0x09, 0x7d, 0xf7,
0x0d, 0x77, 0x9c, 0x63, 0xb0, 0x80, 0xeb, 0xbf, 0xd4, 0xe0, 0xd2, 0xd0, 0x4d, 0x3a, 0x4b, 0x01, 0xd5, 0x71, 0xad, 0xe0, 0x62, 0xaa, 0x29, 0xe1, 0xf3, 0x50, 0xc2, 0x9e, 0x17, 0x1c, 0x18, 0xc1,
0x66, 0xf8, 0x25, 0x3d, 0x0a, 0xa3, 0x30, 0xa2, 0xc3, 0x31, 0x2a, 0x36, 0x59, 0x25, 0xd6, 0xef, 0x89, 0xcf, 0x36, 0xde, 0x61, 0x1f, 0xf2, 0x6c, 0xf8, 0xc6, 0x39, 0x06, 0x0b, 0xb8, 0xfe, 0x0b,
0xfd, 0x93, 0x55, 0x42, 0x1b, 0x4e, 0xd2, 0xea, 0xff, 0xca, 0x40, 0x7e, 0x37, 0x30, 0x82, 0x8e, 0x0d, 0x9e, 0x1a, 0x3b, 0xb3, 0x67, 0x21, 0xc0, 0x0c, 0x57, 0x72, 0x47, 0xa1, 0x17, 0x46, 0x74,
0xff, 0x3f, 0x8e, 0xd8, 0xcb, 0x90, 0xf7, 0xb9, 0x1e, 0x69, 0x5e, 0x58, 0x63, 0x85, 0x76, 0x2c, 0x38, 0x46, 0xc5, 0x7a, 0xb8, 0xc4, 0xa0, 0x7f, 0xb0, 0x87, 0x4b, 0x68, 0xc3, 0x49, 0x5a, 0xfd,
0xb1, 0x7c, 0x1a, 0x21, 0xbe, 0x6f, 0x34, 0x55, 0xc6, 0x8a, 0xa6, 0x11, 0x01, 0xc6, 0x0a, 0x8f, 0x9f, 0x19, 0x28, 0x1c, 0x06, 0x46, 0xd0, 0xf5, 0xff, 0xcb, 0x1e, 0xfb, 0x2c, 0x14, 0x7c, 0xae,
0xde, 0x80, 0x3c, 0x25, 0x86, 0x1f, 0x0e, 0x66, 0x0b, 0x4a, 0x24, 0xe6, 0xd0, 0xd3, 0x5e, 0x75, 0x47, 0x9a, 0x17, 0xe6, 0x58, 0xa1, 0x1d, 0x4b, 0x2c, 0xef, 0x46, 0x88, 0xef, 0x1b, 0x2d, 0x15,
0x52, 0x0a, 0xe7, 0xdf, 0x58, 0x52, 0xa3, 0x07, 0x30, 0x61, 0x91, 0xc0, 0xb0, 0x1d, 0x31, 0x8f, 0xb1, 0xa2, 0x6e, 0x44, 0x80, 0xb1, 0xc2, 0xa3, 0x97, 0xa1, 0x40, 0x89, 0xe1, 0x87, 0x2d, 0xe0,
0xa5, 0x5e, 0xdc, 0x0b, 0x61, 0x0d, 0xc1, 0x5a, 0x2f, 0x31, 0x9b, 0xe4, 0x07, 0x56, 0x02, 0x59, 0x8a, 0x12, 0x89, 0x39, 0xf4, 0xb2, 0x5f, 0x9b, 0x91, 0xc2, 0xf9, 0x1a, 0x4b, 0x6a, 0xf4, 0x36,
0xb6, 0x35, 0x3d, 0x4b, 0x8c, 0x13, 0xb9, 0x28, 0xdb, 0xae, 0x79, 0x16, 0xc1, 0x1c, 0xa3, 0x3f, 0x4c, 0x5b, 0x24, 0x30, 0x6c, 0x47, 0xf4, 0x63, 0xa9, 0x7f, 0x11, 0x08, 0x61, 0x4d, 0xc1, 0xda,
0xd1, 0xa0, 0x24, 0x24, 0xad, 0x19, 0x1d, 0x9f, 0xa0, 0x95, 0xd0, 0x0b, 0x71, 0xdd, 0xaa, 0x93, 0x28, 0x33, 0x9b, 0xe4, 0x02, 0x2b, 0x81, 0x2c, 0xda, 0x9a, 0x9e, 0x25, 0xda, 0x89, 0x7c, 0x14,
0x1b, 0x67, 0x03, 0xc7, 0x69, 0xaf, 0x5a, 0xe4, 0x64, 0x7c, 0x12, 0x51, 0x0e, 0xc4, 0xce, 0x28, 0x6d, 0xb7, 0x3c, 0x8b, 0x60, 0x8e, 0xd1, 0xef, 0x6b, 0x50, 0x16, 0x92, 0xb6, 0x8c, 0xae, 0x4f,
0x73, 0xce, 0x19, 0xbd, 0x0a, 0x39, 0xfe, 0x7a, 0xe4, 0x61, 0x86, 0x6f, 0x9d, 0x3f, 0x30, 0x2c, 0xd0, 0x46, 0xb8, 0x0b, 0x71, 0xdd, 0xaa, 0x92, 0xcb, 0xb1, 0x86, 0xe3, 0xb2, 0x5f, 0x2b, 0x71,
0x70, 0xfa, 0xc7, 0x19, 0x28, 0x27, 0x9c, 0x4b, 0x31, 0x0b, 0x84, 0x0b, 0xc5, 0x4c, 0x8a, 0x25, 0x32, 0xde, 0x89, 0xa8, 0x0d, 0xc4, 0xce, 0x28, 0x73, 0xc5, 0x19, 0x3d, 0x03, 0x79, 0xfe, 0x7a,
0xf5, 0xf0, 0x3f, 0x2b, 0x65, 0xed, 0xc9, 0xbf, 0x4c, 0xed, 0xf9, 0x2e, 0xe4, 0x4d, 0x76, 0x46, 0xe4, 0x61, 0x86, 0x6f, 0x9d, 0x3f, 0x30, 0x2c, 0x70, 0xfa, 0x27, 0x19, 0xa8, 0x24, 0x36, 0x97,
0xea, 0xbf, 0xef, 0x95, 0x51, 0xae, 0x93, 0x9f, 0x6e, 0x14, 0x8d, 0xfc, 0xd3, 0xc7, 0x52, 0x20, 0xa2, 0x17, 0x08, 0x47, 0x97, 0x99, 0x14, 0xe3, 0xf0, 0xf1, 0xbf, 0x45, 0x65, 0xee, 0x29, 0x3c,
0xba, 0x05, 0xb3, 0x94, 0x04, 0xb4, 0xbb, 0xba, 0x1f, 0x10, 0x1a, 0x1f, 0xe2, 0x73, 0x51, 0xc7, 0x49, 0xee, 0xf9, 0x0e, 0x14, 0x4c, 0x76, 0x46, 0xea, 0x2f, 0xfb, 0xc6, 0x24, 0xd7, 0xc9, 0x4f,
0x8d, 0xfb, 0x09, 0xf0, 0x20, 0x8f, 0xbe, 0x07, 0x93, 0x77, 0x8d, 0x3d, 0x27, 0xfc, 0x2b, 0x0a, 0x37, 0xf2, 0x46, 0xbe, 0xf4, 0xb1, 0x14, 0x88, 0x6e, 0xc1, 0x02, 0x25, 0x01, 0xed, 0x6d, 0x1e,
0x43, 0xd9, 0x76, 0x4d, 0xa7, 0x63, 0x11, 0x91, 0x8d, 0x55, 0xf6, 0x52, 0x8f, 0x76, 0x33, 0x8e, 0x07, 0x84, 0xc6, 0x9b, 0xf8, 0x7c, 0x54, 0x71, 0xe3, 0x41, 0x02, 0x3c, 0xcc, 0xa3, 0x1f, 0xc1,
0x3c, 0xed, 0x55, 0xe7, 0x12, 0x00, 0xf1, 0xdf, 0x0b, 0x4e, 0x8a, 0xd0, 0x1d, 0x18, 0xff, 0x14, 0xcc, 0x5b, 0xc6, 0x91, 0x13, 0xfe, 0xf4, 0xc2, 0x50, 0xb1, 0x5d, 0xd3, 0xe9, 0x5a, 0x44, 0x44,
0xa7, 0xc7, 0xef, 0x41, 0x31, 0xea, 0xef, 0x3f, 0x61, 0x95, 0xfa, 0x23, 0x28, 0xb0, 0x88, 0x57, 0x63, 0x15, 0xbd, 0xd4, 0xa3, 0xdd, 0x8d, 0x23, 0x2f, 0xfb, 0xb5, 0xc5, 0x04, 0x40, 0xfc, 0xe5,
0x73, 0xe9, 0x39, 0x2d, 0x4e, 0xb2, 0x71, 0xca, 0xa4, 0x69, 0x9c, 0xf4, 0x16, 0x94, 0xef, 0xb5, 0xc1, 0x49, 0x11, 0xba, 0x03, 0xb9, 0xcf, 0xb0, 0x7b, 0xfc, 0x2e, 0x94, 0xa2, 0xfa, 0xfe, 0x53,
0xad, 0x97, 0xfc, 0x33, 0x32, 0x93, 0xba, 0x6a, 0x5d, 0x03, 0xf1, 0xb7, 0x3a, 0x2b, 0x10, 0xa2, 0x56, 0xa9, 0xbf, 0x0b, 0x45, 0xe6, 0xf1, 0xaa, 0x2f, 0xbd, 0xa2, 0xc4, 0x49, 0x16, 0x4e, 0x99,
0x72, 0xc7, 0x0a, 0x44, 0xbc, 0xf0, 0xc6, 0x76, 0xe5, 0x3f, 0xd5, 0x00, 0xf8, 0x52, 0x6a, 0xfd, 0x34, 0x85, 0x93, 0xde, 0x86, 0xca, 0x9d, 0x8e, 0xf5, 0x84, 0xbf, 0x3d, 0x33, 0xa9, 0xb3, 0xd6,
0x88, 0xb8, 0x01, 0x3b, 0x07, 0x16, 0xf8, 0xfd, 0xe7, 0xc0, 0x33, 0x03, 0xc7, 0xa0, 0x7b, 0x90, 0x75, 0x10, 0x3f, 0xf0, 0x59, 0x82, 0x10, 0x99, 0x3b, 0x96, 0x20, 0xe2, 0x89, 0x37, 0x36, 0x95,
0xf7, 0x44, 0x34, 0x89, 0x3f, 0x24, 0x47, 0xdc, 0x7c, 0x86, 0x8f, 0x40, 0xc4, 0x13, 0x96, 0xc2, 0xff, 0x89, 0x06, 0xc0, 0xc7, 0x5f, 0xdb, 0x67, 0xc4, 0x0d, 0xd8, 0x39, 0x30, 0xc7, 0x1f, 0x3c,
0xea, 0x4b, 0xcf, 0x9e, 0x2f, 0x8c, 0x7d, 0xf8, 0x7c, 0x61, 0xec, 0xa3, 0xe7, 0x0b, 0x63, 0xef, 0x07, 0x1e, 0x19, 0x38, 0x06, 0xdd, 0x81, 0x82, 0x27, 0xbc, 0x49, 0xfc, 0xfa, 0x9c, 0x70, 0xc6,
0x9d, 0x2c, 0x68, 0xcf, 0x4e, 0x16, 0xb4, 0x0f, 0x4f, 0x16, 0xb4, 0x8f, 0x4e, 0x16, 0xb4, 0x8f, 0x1a, 0x3e, 0x02, 0xe1, 0x4f, 0x58, 0x0a, 0x6b, 0xac, 0x3d, 0x7c, 0xb4, 0x32, 0xf5, 0xd1, 0xa3,
0x4f, 0x16, 0xb4, 0x27, 0xff, 0x58, 0x18, 0x7b, 0x90, 0x39, 0x5a, 0xf9, 0x4f, 0x00, 0x00, 0x00, 0x95, 0xa9, 0x8f, 0x1f, 0xad, 0x4c, 0xbd, 0x7f, 0xb1, 0xa2, 0x3d, 0xbc, 0x58, 0xd1, 0x3e, 0xba,
0xff, 0xff, 0x89, 0x39, 0x24, 0x64, 0xcc, 0x24, 0x00, 0x00, 0x58, 0xd1, 0x3e, 0xbe, 0x58, 0xd1, 0x3e, 0xb9, 0x58, 0xd1, 0xee, 0xff, 0x7d, 0x65, 0xea, 0xed,
0xcc, 0xd9, 0xc6, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xa1, 0x16, 0x06, 0x49, 0x36, 0x25, 0x00,
0x00,
} }
func (m *APIGroup) Marshal() (dAtA []byte, err error) { func (m *APIGroup) Marshal() (dAtA []byte, err error) {
@ -2399,6 +2401,11 @@ func (m *ListOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
i -= len(m.ResourceVersionMatch)
copy(dAtA[i:], m.ResourceVersionMatch)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersionMatch)))
i--
dAtA[i] = 0x52
i-- i--
if m.AllowWatchBookmarks { if m.AllowWatchBookmarks {
dAtA[i] = 1 dAtA[i] = 1
@ -3758,6 +3765,8 @@ func (m *ListOptions) Size() (n int) {
l = len(m.Continue) l = len(m.Continue)
n += 1 + l + sovGenerated(uint64(l)) n += 1 + l + sovGenerated(uint64(l))
n += 2 n += 2
l = len(m.ResourceVersionMatch)
n += 1 + l + sovGenerated(uint64(l))
return n return n
} }
@ -4345,6 +4354,7 @@ func (this *ListOptions) String() string {
`Limit:` + fmt.Sprintf("%v", this.Limit) + `,`, `Limit:` + fmt.Sprintf("%v", this.Limit) + `,`,
`Continue:` + fmt.Sprintf("%v", this.Continue) + `,`, `Continue:` + fmt.Sprintf("%v", this.Continue) + `,`,
`AllowWatchBookmarks:` + fmt.Sprintf("%v", this.AllowWatchBookmarks) + `,`, `AllowWatchBookmarks:` + fmt.Sprintf("%v", this.AllowWatchBookmarks) + `,`,
`ResourceVersionMatch:` + fmt.Sprintf("%v", this.ResourceVersionMatch) + `,`,
`}`, `}`,
}, "") }, "")
return s return s
@ -7801,6 +7811,38 @@ func (m *ListOptions) Unmarshal(dAtA []byte) error {
} }
} }
m.AllowWatchBookmarks = bool(v != 0) m.AllowWatchBookmarks = bool(v != 0)
case 10:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersionMatch", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ResourceVersionMatch = ResourceVersionMatch(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:]) skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -232,10 +232,12 @@ message FieldsV1 {
// GetOptions is the standard query options to the standard REST get call. // GetOptions is the standard query options to the standard REST get call.
message GetOptions { message GetOptions {
// When specified: // resourceVersion sets a constraint on what resource versions a request may be served from.
// - if unset, then the result is returned from remote storage based on quorum-read flag; // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// - if it's 0, then we simply return what we currently have in cache, no guarantee; // details.
// - if set to non zero, then the result is at least as fresh as given rv. //
// Defaults to unset
// +optional
optional string resourceVersion = 1; optional string resourceVersion = 1;
} }
@ -421,15 +423,24 @@ message ListOptions {
// +optional // +optional
optional bool allowWatchBookmarks = 9; optional bool allowWatchBookmarks = 9;
// When specified with a watch call, shows changes that occur after that particular version of a resource. // resourceVersion sets a constraint on what resource versions a request may be served from.
// Defaults to changes from the beginning of history. // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// When specified for list: // details.
// - if unset, then the result is returned from remote storage based on quorum-read flag; //
// - if it's 0, then we simply return what we currently have in cache, no guarantee; // Defaults to unset
// - if set to non zero, then the result is at least as fresh as given rv.
// +optional // +optional
optional string resourceVersion = 4; optional string resourceVersion = 4;
// resourceVersionMatch determines how resourceVersion is applied to list calls.
// It is highly recommended that resourceVersionMatch be set for list calls where
// resourceVersion is set
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// details.
//
// Defaults to unset
// +optional
optional string resourceVersionMatch = 10;
// Timeout for the list/watch call. // Timeout for the list/watch call.
// This limits the duration of the call, regardless of any activity or inactivity. // This limits the duration of the call, regardless of any activity or inactivity.
// +optional // +optional

View File

@ -355,14 +355,23 @@ type ListOptions struct {
// +optional // +optional
AllowWatchBookmarks bool `json:"allowWatchBookmarks,omitempty" protobuf:"varint,9,opt,name=allowWatchBookmarks"` AllowWatchBookmarks bool `json:"allowWatchBookmarks,omitempty" protobuf:"varint,9,opt,name=allowWatchBookmarks"`
// When specified with a watch call, shows changes that occur after that particular version of a resource. // resourceVersion sets a constraint on what resource versions a request may be served from.
// Defaults to changes from the beginning of history. // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// When specified for list: // details.
// - if unset, then the result is returned from remote storage based on quorum-read flag; //
// - if it's 0, then we simply return what we currently have in cache, no guarantee; // Defaults to unset
// - if set to non zero, then the result is at least as fresh as given rv.
// +optional // +optional
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,4,opt,name=resourceVersion"` ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,4,opt,name=resourceVersion"`
// resourceVersionMatch determines how resourceVersion is applied to list calls.
// It is highly recommended that resourceVersionMatch be set for list calls where
// resourceVersion is set
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// details.
//
// Defaults to unset
// +optional
ResourceVersionMatch ResourceVersionMatch `json:"resourceVersionMatch,omitempty" protobuf:"bytes,10,opt,name=resourceVersionMatch,casttype=ResourceVersionMatch"`
// Timeout for the list/watch call. // Timeout for the list/watch call.
// This limits the duration of the call, regardless of any activity or inactivity. // This limits the duration of the call, regardless of any activity or inactivity.
// +optional // +optional
@ -402,6 +411,25 @@ type ListOptions struct {
Continue string `json:"continue,omitempty" protobuf:"bytes,8,opt,name=continue"` Continue string `json:"continue,omitempty" protobuf:"bytes,8,opt,name=continue"`
} }
// resourceVersionMatch specifies how the resourceVersion parameter is applied. resourceVersionMatch
// may only be set if resourceVersion is also set.
//
// "NotOlderThan" matches data at least as new as the provided resourceVersion.
// "Exact" matches data at the exact resourceVersion provided.
//
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// details.
type ResourceVersionMatch string
const (
// ResourceVersionMatchNotOlderThan matches data at least as new as the provided
// resourceVersion.
ResourceVersionMatchNotOlderThan ResourceVersionMatch = "NotOlderThan"
// ResourceVersionMatchExact matches data at the exact resourceVersion
// provided.
ResourceVersionMatchExact ResourceVersionMatch = "Exact"
)
// +k8s:conversion-gen:explicit-from=net/url.Values // +k8s:conversion-gen:explicit-from=net/url.Values
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -423,10 +451,12 @@ type ExportOptions struct {
// GetOptions is the standard query options to the standard REST get call. // GetOptions is the standard query options to the standard REST get call.
type GetOptions struct { type GetOptions struct {
TypeMeta `json:",inline"` TypeMeta `json:",inline"`
// When specified: // resourceVersion sets a constraint on what resource versions a request may be served from.
// - if unset, then the result is returned from remote storage based on quorum-read flag; // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
// - if it's 0, then we simply return what we currently have in cache, no guarantee; // details.
// - if set to non zero, then the result is at least as fresh as given rv. //
// Defaults to unset
// +optional
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"` ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"`
// +k8s:deprecated=includeUninitialized,protobuf=2 // +k8s:deprecated=includeUninitialized,protobuf=2
} }

View File

@ -129,7 +129,7 @@ func (FieldsV1) SwaggerDoc() map[string]string {
var map_GetOptions = map[string]string{ var map_GetOptions = map[string]string{
"": "GetOptions is the standard query options to the standard REST get call.", "": "GetOptions is the standard query options to the standard REST get call.",
"resourceVersion": "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", "resourceVersion": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
} }
func (GetOptions) SwaggerDoc() map[string]string { func (GetOptions) SwaggerDoc() map[string]string {
@ -195,7 +195,8 @@ var map_ListOptions = map[string]string{
"fieldSelector": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", "fieldSelector": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
"watch": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", "watch": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
"allowWatchBookmarks": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.", "allowWatchBookmarks": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.",
"resourceVersion": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", "resourceVersion": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
"resourceVersionMatch": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
"timeoutSeconds": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "timeoutSeconds": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.",
"limit": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", "limit": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.",
"continue": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", "continue": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",

View File

@ -145,6 +145,11 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddConversionFunc((*[]string)(nil), (*ResourceVersionMatch)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_Slice_string_To_v1_ResourceVersionMatch(a.(*[]string), b.(*ResourceVersionMatch), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*[]string)(nil), (*Time)(nil), func(a, b interface{}, scope conversion.Scope) error { if err := s.AddConversionFunc((*[]string)(nil), (*Time)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_Slice_string_To_v1_Time(a.(*[]string), b.(*Time), scope) return Convert_Slice_string_To_v1_Time(a.(*[]string), b.(*Time), scope)
}); err != nil { }); err != nil {
@ -415,6 +420,13 @@ func autoConvert_url_Values_To_v1_ListOptions(in *url.Values, out *ListOptions,
} else { } else {
out.ResourceVersion = "" out.ResourceVersion = ""
} }
if values, ok := map[string][]string(*in)["resourceVersionMatch"]; ok && len(values) > 0 {
if err := Convert_Slice_string_To_v1_ResourceVersionMatch(&values, &out.ResourceVersionMatch, s); err != nil {
return err
}
} else {
out.ResourceVersionMatch = ""
}
if values, ok := map[string][]string(*in)["timeoutSeconds"]; ok && len(values) > 0 { if values, ok := map[string][]string(*in)["timeoutSeconds"]; ok && len(values) > 0 {
if err := runtime.Convert_Slice_string_To_Pointer_int64(&values, &out.TimeoutSeconds, s); err != nil { if err := runtime.Convert_Slice_string_To_Pointer_int64(&values, &out.TimeoutSeconds, s); err != nil {
return err return err

View File

@ -69,6 +69,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -198,6 +199,12 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
return return
} }
if errs := metainternalversionvalidation.ValidateListOptions(&listOptions); len(errs) > 0 {
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
scope.err(err, w, req)
return
}
// transform fields // transform fields
// TODO: DecodeParametersInto should do this. // TODO: DecodeParametersInto should do this.
if listOptions.FieldSelector != nil { if listOptions.FieldSelector != nil {

View File

@ -19,6 +19,8 @@ package handlers
import ( import (
"context" "context"
"fmt" "fmt"
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
"k8s.io/apimachinery/pkg/runtime/schema"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
@ -198,6 +200,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatc
return return
} }
if errs := metainternalversionvalidation.ValidateListOptions(&opts); len(errs) > 0 {
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
scope.err(err, w, req)
return
}
// transform fields // transform fields
// TODO: DecodeParametersInto should do this. // TODO: DecodeParametersInto should do this.
if opts.FieldSelector != nil { if opts.FieldSelector != nil {

View File

@ -1078,6 +1078,10 @@ func typeToJSON(typeName string) string {
return "string" return "string"
case "v1.DeletionPropagation", "*v1.DeletionPropagation": case "v1.DeletionPropagation", "*v1.DeletionPropagation":
return "string" return "string"
case "v1.ResourceVersionMatch", "*v1.ResourceVersionMatch":
return "string"
case "v1.IncludeObjectPolicy", "*v1.IncludeObjectPolicy":
return "string"
// TODO: Fix these when go-restful supports a way to specify an array query param: // TODO: Fix these when go-restful supports a way to specify an array query param:
// https://github.com/emicklei/go-restful/issues/225 // https://github.com/emicklei/go-restful/issues/225

View File

@ -322,7 +322,7 @@ func (e *Store) ListPredicate(ctx context.Context, p storage.SelectionPredicate,
p.Continue = options.Continue p.Continue = options.Continue
list := e.NewListFunc() list := e.NewListFunc()
qualifiedResource := e.qualifiedResourceFromContext(ctx) qualifiedResource := e.qualifiedResourceFromContext(ctx)
storageOpts := storage.ListOptions{ResourceVersion: options.ResourceVersion, Predicate: p} storageOpts := storage.ListOptions{ResourceVersion: options.ResourceVersion, ResourceVersionMatch: options.ResourceVersionMatch, Predicate: p}
if name, ok := p.MatchesSingle(); ok { if name, ok := p.MatchesSingle(); ok {
if key, err := e.KeyFunc(ctx, name); err == nil { if key, err := e.KeyFunc(ctx, name); err == nil {
err := e.Storage.GetToList(ctx, key, storageOpts, list) err := e.Storage.GetToList(ctx, key, storageOpts, list)

View File

@ -580,7 +580,7 @@ func (c *Cacher) GetToList(ctx context.Context, key string, opts storage.ListOpt
pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking) pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
hasContinuation := pagingEnabled && len(pred.Continue) > 0 hasContinuation := pagingEnabled && len(pred.Continue) > 0
hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0" hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0"
if resourceVersion == "" || hasContinuation || hasLimit { if resourceVersion == "" || hasContinuation || hasLimit || opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact {
// If resourceVersion is not specified, serve it from underlying // If resourceVersion is not specified, serve it from underlying
// storage (for backward compatibility). If a continuation is // storage (for backward compatibility). If a continuation is
// requested, serve it from the underlying storage as well. // requested, serve it from the underlying storage as well.
@ -654,7 +654,7 @@ func (c *Cacher) List(ctx context.Context, key string, opts storage.ListOptions,
pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking) pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
hasContinuation := pagingEnabled && len(pred.Continue) > 0 hasContinuation := pagingEnabled && len(pred.Continue) > 0
hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0" hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0"
if resourceVersion == "" || hasContinuation || hasLimit { if resourceVersion == "" || hasContinuation || hasLimit || opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact {
// If resourceVersion is not specified, serve it from underlying // If resourceVersion is not specified, serve it from underlying
// storage (for backward compatibility). If a continuation is // storage (for backward compatibility). If a continuation is
// requested, serve it from the underlying storage as well. // requested, serve it from the underlying storage as well.
@ -1090,7 +1090,7 @@ func (lw *cacherListerWatcher) List(options metav1.ListOptions) (runtime.Object,
Continue: options.Continue, Continue: options.Continue,
} }
if err := lw.storage.List(context.TODO(), lw.resourcePrefix, storage.ListOptions{Predicate: pred}, list); err != nil { if err := lw.storage.List(context.TODO(), lw.resourcePrefix, storage.ListOptions{ResourceVersionMatch: options.ResourceVersionMatch, Predicate: pred}, list); err != nil {
return nil, err return nil, err
} }
return list, nil return list, nil

View File

@ -68,6 +68,7 @@ go_library(
deps = [ deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -32,6 +32,8 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -63,9 +65,6 @@ var _ value.Context = authenticatedDataString("")
type store struct { type store struct {
client *clientv3.Client client *clientv3.Client
// getOps contains additional options that should be passed
// to all Get() calls.
getOps []clientv3.OpOption
codec runtime.Codec codec runtime.Codec
versioner storage.Versioner versioner storage.Versioner
transformer value.Transformer transformer value.Transformer
@ -115,13 +114,12 @@ func (s *store) Versioner() storage.Versioner {
func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error { func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error {
key = path.Join(s.pathPrefix, key) key = path.Join(s.pathPrefix, key)
startTime := time.Now() startTime := time.Now()
callOpts := s.getOps getResp, err := s.client.KV.Get(ctx, key)
getResp, err := s.client.KV.Get(ctx, key, callOpts...)
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime) metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
if err != nil { if err != nil {
return err return err
} }
if err = s.ensureMinimumResourceVersion(opts.ResourceVersion, uint64(getResp.Header.Revision)); err != nil { if err = s.validateMinimumResourceVersion(opts.ResourceVersion, uint64(getResp.Header.Revision)); err != nil {
return err return err
} }
@ -252,7 +250,7 @@ func (s *store) GuaranteedUpdate(
getCurrentState := func() (*objState, error) { getCurrentState := func() (*objState, error) {
startTime := time.Now() startTime := time.Now()
getResp, err := s.client.KV.Get(ctx, key, s.getOps...) getResp, err := s.client.KV.Get(ctx, key)
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime) metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
if err != nil { if err != nil {
return nil, err return nil, err
@ -382,10 +380,12 @@ func (s *store) GuaranteedUpdate(
// GetToList implements storage.Interface.GetToList. // GetToList implements storage.Interface.GetToList.
func (s *store) GetToList(ctx context.Context, key string, listOpts storage.ListOptions, listObj runtime.Object) error { func (s *store) GetToList(ctx context.Context, key string, listOpts storage.ListOptions, listObj runtime.Object) error {
resourceVersion := listOpts.ResourceVersion resourceVersion := listOpts.ResourceVersion
match := listOpts.ResourceVersionMatch
pred := listOpts.Predicate pred := listOpts.Predicate
trace := utiltrace.New("GetToList etcd3", trace := utiltrace.New("GetToList etcd3",
utiltrace.Field{"key", key}, utiltrace.Field{"key", key},
utiltrace.Field{"resourceVersion", resourceVersion}, utiltrace.Field{"resourceVersion", resourceVersion},
utiltrace.Field{"resourceVersionMatch", match},
utiltrace.Field{"limit", pred.Limit}, utiltrace.Field{"limit", pred.Limit},
utiltrace.Field{"continue", pred.Continue}) utiltrace.Field{"continue", pred.Continue})
defer trace.LogIfLong(500 * time.Millisecond) defer trace.LogIfLong(500 * time.Millisecond)
@ -402,12 +402,21 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List
key = path.Join(s.pathPrefix, key) key = path.Join(s.pathPrefix, key)
startTime := time.Now() startTime := time.Now()
getResp, err := s.client.KV.Get(ctx, key, s.getOps...) var opts []clientv3.OpOption
if len(resourceVersion) > 0 && match == metav1.ResourceVersionMatchExact {
rv, err := s.versioner.ParseResourceVersion(resourceVersion)
if err != nil {
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err))
}
opts = append(opts, clientv3.WithRev(int64(rv)))
}
getResp, err := s.client.KV.Get(ctx, key, opts...)
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime) metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
if err != nil { if err != nil {
return err return err
} }
if err = s.ensureMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil { if err = s.validateMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
return err return err
} }
@ -515,10 +524,12 @@ func encodeContinue(key, keyPrefix string, resourceVersion int64) (string, error
// List implements storage.Interface.List. // List implements storage.Interface.List.
func (s *store) List(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { func (s *store) List(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
resourceVersion := opts.ResourceVersion resourceVersion := opts.ResourceVersion
match := opts.ResourceVersionMatch
pred := opts.Predicate pred := opts.Predicate
trace := utiltrace.New("List etcd3", trace := utiltrace.New("List etcd3",
utiltrace.Field{"key", key}, utiltrace.Field{"key", key},
utiltrace.Field{"resourceVersion", resourceVersion}, utiltrace.Field{"resourceVersion", resourceVersion},
utiltrace.Field{"resourceVersionMatch", match},
utiltrace.Field{"limit", pred.Limit}, utiltrace.Field{"limit", pred.Limit},
utiltrace.Field{"continue", pred.Continue}) utiltrace.Field{"continue", pred.Continue})
defer trace.LogIfLong(500 * time.Millisecond) defer trace.LogIfLong(500 * time.Millisecond)
@ -552,6 +563,15 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
newItemFunc := getNewItemFunc(listObj, v) newItemFunc := getNewItemFunc(listObj, v)
var fromRV *uint64
if len(resourceVersion) > 0 {
parsedRV, err := s.versioner.ParseResourceVersion(resourceVersion)
if err != nil {
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err))
}
fromRV = &parsedRV
}
var returnedRV, continueRV int64 var returnedRV, continueRV int64
var continueKey string var continueKey string
switch { switch {
@ -577,20 +597,41 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
returnedRV = continueRV returnedRV = continueRV
} }
case s.pagingEnabled && pred.Limit > 0: case s.pagingEnabled && pred.Limit > 0:
if len(resourceVersion) > 0 { if fromRV != nil {
fromRV, err := s.versioner.ParseResourceVersion(resourceVersion) switch match {
if err != nil { case metav1.ResourceVersionMatchNotOlderThan:
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err)) // The not older than constraint is checked after we get a response from etcd,
// and returnedRV is then set to the revision we get from the etcd response.
case metav1.ResourceVersionMatchExact:
returnedRV = int64(*fromRV)
options = append(options, clientv3.WithRev(returnedRV))
case "": // legacy case
if *fromRV > 0 {
returnedRV = int64(*fromRV)
options = append(options, clientv3.WithRev(returnedRV))
} }
if fromRV > 0 { default:
options = append(options, clientv3.WithRev(int64(fromRV))) return fmt.Errorf("unknown ResourceVersionMatch value: %v", match)
} }
returnedRV = int64(fromRV)
} }
rangeEnd := clientv3.GetPrefixRangeEnd(keyPrefix) rangeEnd := clientv3.GetPrefixRangeEnd(keyPrefix)
options = append(options, clientv3.WithRange(rangeEnd)) options = append(options, clientv3.WithRange(rangeEnd))
default: default:
if fromRV != nil {
switch match {
case metav1.ResourceVersionMatchNotOlderThan:
// The not older than constraint is checked after we get a response from etcd,
// and returnedRV is then set to the revision we get from the etcd response.
case metav1.ResourceVersionMatchExact:
returnedRV = int64(*fromRV)
options = append(options, clientv3.WithRev(returnedRV))
case "": // legacy case
default:
return fmt.Errorf("unknown ResourceVersionMatch value: %v", match)
}
}
options = append(options, clientv3.WithPrefix()) options = append(options, clientv3.WithPrefix())
} }
@ -605,7 +646,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
if err != nil { if err != nil {
return interpretListError(err, len(pred.Continue) > 0, continueKey, keyPrefix) return interpretListError(err, len(pred.Continue) > 0, continueKey, keyPrefix)
} }
if err = s.ensureMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil { if err = s.validateMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
return err return err
} }
hasMore = getResp.More hasMore = getResp.More
@ -822,9 +863,9 @@ func (s *store) ttlOpts(ctx context.Context, ttl int64) ([]clientv3.OpOption, er
return []clientv3.OpOption{clientv3.WithLease(id)}, nil return []clientv3.OpOption{clientv3.WithLease(id)}, nil
} }
// ensureMinimumResourceVersion returns a 'too large resource' version error when the provided minimumResourceVersion is // validateMinimumResourceVersion returns a 'too large resource' version error when the provided minimumResourceVersion is
// greater than the most recent actualRevision available from storage. // greater than the most recent actualRevision available from storage.
func (s *store) ensureMinimumResourceVersion(minimumResourceVersion string, actualRevision uint64) error { func (s *store) validateMinimumResourceVersion(minimumResourceVersion string, actualRevision uint64) error {
if minimumResourceVersion == "" { if minimumResourceVersion == "" {
return nil return nil
} }

View File

@ -373,6 +373,10 @@ func TestConditionalDelete(t *testing.T) {
func TestGetToList(t *testing.T) { func TestGetToList(t *testing.T) {
ctx, store, cluster := testSetup(t) ctx, store, cluster := testSetup(t)
defer cluster.Terminate(t) defer cluster.Terminate(t)
prevKey, prevStoredObj := testPropogateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "prev"}})
prevRV, _ := strconv.Atoi(prevStoredObj.ResourceVersion)
key, storedObj := testPropogateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) key, storedObj := testPropogateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
currentRV, _ := strconv.Atoi(storedObj.ResourceVersion) currentRV, _ := strconv.Atoi(storedObj.ResourceVersion)
@ -382,6 +386,7 @@ func TestGetToList(t *testing.T) {
pred storage.SelectionPredicate pred storage.SelectionPredicate
expectedOut []*example.Pod expectedOut []*example.Pod
rv string rv string
rvMatch metav1.ResourceVersionMatch
expectRVTooLarge bool expectRVTooLarge bool
}{{ // test GetToList on existing key }{{ // test GetToList on existing key
key: key, key: key,
@ -392,11 +397,41 @@ func TestGetToList(t *testing.T) {
pred: storage.Everything, pred: storage.Everything,
expectedOut: []*example.Pod{storedObj}, expectedOut: []*example.Pod{storedObj},
rv: "0", rv: "0",
}, { // test GetToList on existing key with minimum resource version set to 0, match=minimum
key: key,
pred: storage.Everything,
expectedOut: []*example.Pod{storedObj},
rv: "0",
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
}, { // test GetToList on existing key with minimum resource version set to current resource version }, { // test GetToList on existing key with minimum resource version set to current resource version
key: key, key: key,
pred: storage.Everything, pred: storage.Everything,
expectedOut: []*example.Pod{storedObj}, expectedOut: []*example.Pod{storedObj},
rv: fmt.Sprintf("%d", currentRV), rv: fmt.Sprintf("%d", currentRV),
}, { // test GetToList on existing key with minimum resource version set to current resource version, match=minimum
key: key,
pred: storage.Everything,
expectedOut: []*example.Pod{storedObj},
rv: fmt.Sprintf("%d", currentRV),
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
}, { // test GetToList on existing key with minimum resource version set to previous resource version, match=minimum
key: key,
pred: storage.Everything,
expectedOut: []*example.Pod{storedObj},
rv: fmt.Sprintf("%d", prevRV),
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
}, { // test GetToList on existing key with resource version set to current resource version, match=exact
key: key,
pred: storage.Everything,
expectedOut: []*example.Pod{storedObj},
rv: fmt.Sprintf("%d", currentRV),
rvMatch: metav1.ResourceVersionMatchExact,
}, { // test GetToList on existing key with resource version set to previous resource version, match=exact
key: prevKey,
pred: storage.Everything,
expectedOut: []*example.Pod{prevStoredObj},
rv: fmt.Sprintf("%d", prevRV),
rvMatch: metav1.ResourceVersionMatchExact,
}, { // test GetToList on existing key with minimum resource version set too high }, { // test GetToList on existing key with minimum resource version set too high
key: key, key: key,
pred: storage.Everything, pred: storage.Everything,
@ -422,7 +457,7 @@ func TestGetToList(t *testing.T) {
for i, tt := range tests { for i, tt := range tests {
out := &example.PodList{} out := &example.PodList{}
err := store.GetToList(ctx, tt.key, storage.ListOptions{ResourceVersion: tt.rv, Predicate: tt.pred}, out) err := store.GetToList(ctx, tt.key, storage.ListOptions{ResourceVersion: tt.rv, ResourceVersionMatch: tt.rvMatch, Predicate: tt.pred}, out)
if tt.expectRVTooLarge { if tt.expectRVTooLarge {
if err == nil || !storage.IsTooLargeResourceVersion(err) { if err == nil || !storage.IsTooLargeResourceVersion(err) {
@ -934,6 +969,7 @@ func TestList(t *testing.T) {
name string name string
disablePaging bool disablePaging bool
rv string rv string
rvMatch metav1.ResourceVersionMatch
prefix string prefix string
pred storage.SelectionPredicate pred storage.SelectionPredicate
expectedOut []*example.Pod expectedOut []*example.Pod
@ -981,6 +1017,31 @@ func TestList(t *testing.T) {
expectedOut: []*example.Pod{preset[0].storedObj}, expectedOut: []*example.Pod{preset[0].storedObj},
rv: "0", rv: "0",
}, },
{
name: "test List on existing key with resource version set to 1, match=Exact",
prefix: "/one-level/",
pred: storage.Everything,
expectedOut: []*example.Pod{},
rv: "1",
rvMatch: metav1.ResourceVersionMatchExact,
expectRV: "1",
},
{
name: "test List on existing key with resource version set to 1, match=NotOlderThan",
prefix: "/one-level/",
pred: storage.Everything,
expectedOut: []*example.Pod{preset[0].storedObj},
rv: "0",
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
},
{
name: "test List on existing key with resource version set to 1, match=Invalid",
prefix: "/one-level/",
pred: storage.Everything,
rv: "0",
rvMatch: "Invalid",
expectError: true,
},
{ {
name: "test List on existing key with resource version set to current resource version", name: "test List on existing key with resource version set to current resource version",
prefix: "/one-level/", prefix: "/one-level/",
@ -988,6 +1049,23 @@ func TestList(t *testing.T) {
expectedOut: []*example.Pod{preset[0].storedObj}, expectedOut: []*example.Pod{preset[0].storedObj},
rv: list.ResourceVersion, rv: list.ResourceVersion,
}, },
{
name: "test List on existing key with resource version set to current resource version, match=Exact",
prefix: "/one-level/",
pred: storage.Everything,
expectedOut: []*example.Pod{preset[0].storedObj},
rv: list.ResourceVersion,
rvMatch: metav1.ResourceVersionMatchExact,
expectRV: list.ResourceVersion,
},
{
name: "test List on existing key with resource version set to current resource version, match=NotOlderThan",
prefix: "/one-level/",
pred: storage.Everything,
expectedOut: []*example.Pod{preset[0].storedObj},
rv: list.ResourceVersion,
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
},
{ {
name: "test List on non-existing key", name: "test List on non-existing key",
prefix: "/non-existing/", prefix: "/non-existing/",
@ -1029,6 +1107,21 @@ func TestList(t *testing.T) {
rv: list.ResourceVersion, rv: list.ResourceVersion,
expectRV: list.ResourceVersion, expectRV: list.ResourceVersion,
}, },
{
name: "test List with limit at current resource version and match=Exact",
prefix: "/two-level/",
pred: storage.SelectionPredicate{
Label: labels.Everything(),
Field: fields.Everything(),
Limit: 1,
},
expectedOut: []*example.Pod{preset[1].storedObj},
expectContinue: true,
expectedRemainingItemCount: utilpointer.Int64Ptr(1),
rv: list.ResourceVersion,
rvMatch: metav1.ResourceVersionMatchExact,
expectRV: list.ResourceVersion,
},
{ {
name: "test List with limit at resource version 0", name: "test List with limit at resource version 0",
prefix: "/two-level/", prefix: "/two-level/",
@ -1043,6 +1136,49 @@ func TestList(t *testing.T) {
rv: "0", rv: "0",
expectRV: list.ResourceVersion, expectRV: list.ResourceVersion,
}, },
{
name: "test List with limit at resource version 0 match=NotOlderThan",
prefix: "/two-level/",
pred: storage.SelectionPredicate{
Label: labels.Everything(),
Field: fields.Everything(),
Limit: 1,
},
expectedOut: []*example.Pod{preset[1].storedObj},
expectContinue: true,
expectedRemainingItemCount: utilpointer.Int64Ptr(1),
rv: "0",
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
expectRV: list.ResourceVersion,
},
{
name: "test List with limit at resource version 1 and match=Exact",
prefix: "/two-level/",
pred: storage.SelectionPredicate{
Label: labels.Everything(),
Field: fields.Everything(),
Limit: 1,
},
expectedOut: []*example.Pod{},
expectContinue: false,
rv: "1",
rvMatch: metav1.ResourceVersionMatchExact,
expectRV: "1",
},
{
name: "test List with limit at old resource version and match=Exact",
prefix: "/two-level/",
pred: storage.SelectionPredicate{
Label: labels.Everything(),
Field: fields.Everything(),
Limit: 1,
},
expectedOut: []*example.Pod{},
expectContinue: false,
rv: "1",
rvMatch: metav1.ResourceVersionMatchExact,
expectRV: "1",
},
{ {
name: "test List with limit when paging disabled", name: "test List with limit when paging disabled",
disablePaging: true, disablePaging: true,
@ -1201,7 +1337,7 @@ func TestList(t *testing.T) {
} }
out := &example.PodList{} out := &example.PodList{}
storageOpts := storage.ListOptions{ResourceVersion: tt.rv, Predicate: tt.pred} storageOpts := storage.ListOptions{ResourceVersion: tt.rv, ResourceVersionMatch: tt.rvMatch, Predicate: tt.pred}
var err error var err error
if tt.disablePaging { if tt.disablePaging {
err = disablePagingStore.List(ctx, tt.prefix, storageOpts, out) err = disablePagingStore.List(ctx, tt.prefix, storageOpts, out)

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -189,17 +190,20 @@ type Interface interface {
// Get unmarshals json found at key into objPtr. On a not found error, will either // Get unmarshals json found at key into objPtr. On a not found error, will either
// return a zero object of the requested type, or an error, depending on 'opts.ignoreNotFound'. // return a zero object of the requested type, or an error, depending on 'opts.ignoreNotFound'.
// Treats empty responses and nil response nodes exactly like a not found error. // Treats empty responses and nil response nodes exactly like a not found error.
// The returned contents may be delayed according to the semantics of GetOptions.ResourceVersion. // The returned contents may be delayed, but it is guaranteed that they will
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
Get(ctx context.Context, key string, opts GetOptions, objPtr runtime.Object) error Get(ctx context.Context, key string, opts GetOptions, objPtr runtime.Object) error
// GetToList unmarshals json found at key and opaque it into *List api object // GetToList unmarshals json found at key and opaque it into *List api object
// (an object that satisfies the runtime.IsList definition). // (an object that satisfies the runtime.IsList definition).
// The returned contents may be delayed according to the semantics of ListOptions.ResourceVersion. // The returned contents may be delayed, but it is guaranteed that they will
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
GetToList(ctx context.Context, key string, opts ListOptions, listObj runtime.Object) error GetToList(ctx context.Context, key string, opts ListOptions, listObj runtime.Object) error
// List unmarshalls jsons found at directory defined by key and opaque them // List unmarshalls jsons found at directory defined by key and opaque them
// into *List api object (an object that satisfies runtime.IsList definition). // into *List api object (an object that satisfies runtime.IsList definition).
// The returned contents may be delayed according to the semantics of ListOptions.ResourceVersion. // The returned contents may be delayed, but it is guaranteed that they will
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
List(ctx context.Context, key string, opts ListOptions, listObj runtime.Object) error List(ctx context.Context, key string, opts ListOptions, listObj runtime.Object) error
// GuaranteedUpdate keeps calling 'tryUpdate()' to update key 'key' (of type 'ptrToType') // GuaranteedUpdate keeps calling 'tryUpdate()' to update key 'key' (of type 'ptrToType')
@ -260,6 +264,9 @@ type ListOptions struct {
// ResourceVersion. The newest available data is preferred, but any data not older than this // ResourceVersion. The newest available data is preferred, but any data not older than this
// ResourceVersion may be served. // ResourceVersion may be served.
ResourceVersion string ResourceVersion string
// ResourceVersionMatch provides the rule for how the resource version constraint applies. If set
// to the default value "" the legacy resource version semantic apply.
ResourceVersionMatch metav1.ResourceVersionMatch
// Predicate provides the selection rules for the list operation. // Predicate provides the selection rules for the list operation.
Predicate SelectionPredicate Predicate SelectionPredicate
} }

View File

@ -623,7 +623,7 @@ func schema_pkg_apis_meta_v1_GetOptions(ref common.ReferenceCallback) common.Ope
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },
@ -1064,7 +1064,14 @@ func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.Op
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"},
Format: "",
},
},
"resourceVersionMatch": {
SchemaProps: spec.SchemaProps{
Description: "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },

View File

@ -626,7 +626,7 @@ func schema_pkg_apis_meta_v1_GetOptions(ref common.ReferenceCallback) common.Ope
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },
@ -1067,7 +1067,14 @@ func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.Op
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"},
Format: "",
},
},
"resourceVersionMatch": {
SchemaProps: spec.SchemaProps{
Description: "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },

View File

@ -624,7 +624,7 @@ func schema_pkg_apis_meta_v1_GetOptions(ref common.ReferenceCallback) common.Ope
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },
@ -1065,7 +1065,14 @@ func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.Op
}, },
"resourceVersion": { "resourceVersion": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"},
Format: "",
},
},
"resourceVersionMatch": {
SchemaProps: spec.SchemaProps{
Description: "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
Type: []string{"string"}, Type: []string{"string"},
Format: "", Format: "",
}, },

View File

@ -61,6 +61,7 @@ go_test(
"//staging/src/k8s.io/client-go/discovery/cached/disk:go_default_library", "//staging/src/k8s.io/client-go/discovery/cached/disk:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library", "//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
"//staging/src/k8s.io/client-go/metadata:go_default_library", "//staging/src/k8s.io/client-go/metadata:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
@ -68,6 +69,7 @@ go_test(
"//staging/src/k8s.io/client-go/tools/pager:go_default_library", "//staging/src/k8s.io/client-go/tools/pager:go_default_library",
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library", "//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library", "//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library", "//test/integration/framework:go_default_library",
"//vendor/github.com/google/uuid:go_default_library", "//vendor/github.com/google/uuid:go_default_library",
"//vendor/k8s.io/gengo/examples/set-gen/sets:go_default_library", "//vendor/k8s.io/gengo/examples/set-gen/sets:go_default_library",

View File

@ -54,12 +54,15 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
"k8s.io/client-go/metadata" "k8s.io/client-go/metadata"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/pager" "k8s.io/client-go/tools/pager"
featuregatetesting "k8s.io/component-base/featuregate/testing" featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/master" "k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
) )
@ -86,7 +89,7 @@ func setupWithResourcesWithOptions(t *testing.T, opts *framework.MasterConfigOpt
masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig() masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
_, s, closeFn := framework.RunAMaster(masterConfig) _, s, closeFn := framework.RunAMaster(masterConfig)
clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL}) clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL, QPS: -1})
if err != nil { if err != nil {
t.Fatalf("Error in create clientset: %v", err) t.Fatalf("Error in create clientset: %v", err)
} }
@ -309,6 +312,207 @@ func Test202StatusCode(t *testing.T) {
verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 202) verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 202)
} }
var (
invalidContinueToken = "invalidContinueToken"
invalidResourceVersion = "invalid"
invalidResourceVersionMatch = metav1.ResourceVersionMatch("InvalidMatch")
)
// TestListOptions ensures that list works as expected for valid and invalid combinations of limit, continue,
// resourceVersion and resourceVersionMatch.
func TestListOptions(t *testing.T) {
for _, watchCacheEnabled := range []bool{true, false} {
t.Run(fmt.Sprintf("watchCacheEnabled=%t", watchCacheEnabled), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIListChunking, true)()
etcdOptions := framework.DefaultEtcdOptions()
etcdOptions.EnableWatchCache = watchCacheEnabled
s, clientSet, closeFn := setupWithOptions(t, &framework.MasterConfigOptions{EtcdOptions: etcdOptions})
defer closeFn()
ns := framework.CreateTestingNamespace("list-options", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
var compactedRv, oldestUncompactedRv string
for i := 0; i < 15; i++ {
rs := newRS(ns.Name)
rs.Name = fmt.Sprintf("test-%d", i)
created, err := rsClient.Create(context.Background(), rs, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
if i == 0 {
compactedRv = created.ResourceVersion // We compact this first resource version below
}
// delete the first 5, and then compact them
if i < 5 {
var zero int64
if err := rsClient.Delete(context.Background(), rs.Name, metav1.DeleteOptions{GracePeriodSeconds: &zero}); err != nil {
t.Fatal(err)
}
oldestUncompactedRv = created.ResourceVersion
}
}
// compact some of the revision history in etcd so we can test "too old" resource versions
_, kvClient, err := integration.GetEtcdClients(etcdOptions.StorageConfig.Transport)
if err != nil {
t.Fatal(err)
}
revision, err := strconv.Atoi(oldestUncompactedRv)
if err != nil {
t.Fatal(err)
}
_, err = kvClient.Compact(context.Background(), int64(revision))
if err != nil {
t.Fatal(err)
}
listObj, err := rsClient.List(context.Background(), metav1.ListOptions{
Limit: 6,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
validContinueToken := listObj.Continue
// test all combinations of these, for both watch cache enabled and disabled:
limits := []int64{0, 6}
continueTokens := []string{"", validContinueToken, invalidContinueToken}
rvs := []string{"", "0", compactedRv, invalidResourceVersion}
rvMatches := []metav1.ResourceVersionMatch{
"",
metav1.ResourceVersionMatchNotOlderThan,
metav1.ResourceVersionMatchExact,
invalidResourceVersionMatch,
}
for _, limit := range limits {
for _, continueToken := range continueTokens {
for _, rv := range rvs {
for _, rvMatch := range rvMatches {
name := fmt.Sprintf("limit=%d continue=%s rv=%s rvMatch=%s", limit, continueToken, rv, rvMatch)
t.Run(name, func(t *testing.T) {
opts := metav1.ListOptions{
ResourceVersion: rv,
ResourceVersionMatch: rvMatch,
Continue: continueToken,
Limit: limit,
}
testListOptionsCase(t, rsClient, watchCacheEnabled, opts, compactedRv)
})
}
}
}
}
})
}
}
func testListOptionsCase(t *testing.T, rsClient appsv1.ReplicaSetInterface, watchCacheEnabled bool, opts metav1.ListOptions, compactedRv string) {
listObj, err := rsClient.List(context.Background(), opts)
// check for expected validation errors
if opts.ResourceVersion == "" && opts.ResourceVersionMatch != "" {
if err == nil || !strings.Contains(err.Error(), "resourceVersionMatch is forbidden unless resourceVersion is provided") {
t.Fatalf("expected forbidden error, but got: %v", err)
}
return
}
if opts.Continue != "" && opts.ResourceVersionMatch != "" {
if err == nil || !strings.Contains(err.Error(), "resourceVersionMatch is forbidden when continue is provided") {
t.Fatalf("expected forbidden error, but got: %v", err)
}
return
}
if opts.ResourceVersionMatch == invalidResourceVersionMatch {
if err == nil || !strings.Contains(err.Error(), "supported values") {
t.Fatalf("expected not supported error, but got: %v", err)
}
return
}
if opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact && opts.ResourceVersion == "0" {
if err == nil || !strings.Contains(err.Error(), "resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\"") {
t.Fatalf("expected forbidden error, but got: %v", err)
}
return
}
if opts.ResourceVersion == invalidResourceVersion {
if err == nil || !strings.Contains(err.Error(), "Invalid value") {
t.Fatalf("expecting invalid value error, but got: %v", err)
}
return
}
if opts.Continue == invalidContinueToken {
if err == nil || !strings.Contains(err.Error(), "continue key is not valid") {
t.Fatalf("expected continue key not valid error, but got: %v", err)
}
return
}
// Should not be allowed for any resource version, but tightening the validation would be a breaking change
if opts.Continue != "" && !(opts.ResourceVersion == "" || opts.ResourceVersion == "0") {
if err == nil || !strings.Contains(err.Error(), "specifying resource version is not allowed when using continue") {
t.Fatalf("expected not allowed error, but got: %v", err)
}
return
}
// Check for too old errors
isExact := opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact
// Legacy corner cases that can be avoided by using an explicit resourceVersionMatch value
// see https://kubernetes.io/docs/reference/using-api/api-concepts/#the-resourceversion-parameter
isLegacyExact := opts.Limit > 0 && opts.ResourceVersionMatch == ""
if opts.ResourceVersion == compactedRv && (isExact || isLegacyExact) {
if err == nil || !strings.Contains(err.Error(), "The resourceVersion for the provided list is too old") {
t.Fatalf("expected too old error, but got: %v", err)
}
return
}
// test successful responses
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
items, err := meta.ExtractList(listObj)
if err != nil {
t.Fatalf("Failed to extract list from %v", listObj)
}
count := int64(len(items))
// Cacher.GetToList defines this for logic to decide if the watch cache is skipped. We need to know it to know if
// the limit is respected when testing here.
pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
hasContinuation := pagingEnabled && len(opts.Continue) > 0
hasLimit := pagingEnabled && opts.Limit > 0 && opts.ResourceVersion != "0"
skipWatchCache := opts.ResourceVersion == "" || hasContinuation || hasLimit || isExact
usingWatchCache := watchCacheEnabled && !skipWatchCache
if usingWatchCache { // watch cache does not respect limit and is not used for continue
if count != 10 {
t.Errorf("Expected list size to be 10 but got %d", count) // limit is ignored if watch cache is hit
}
return
}
if opts.Continue != "" {
if count != 4 {
t.Errorf("Expected list size of 4 but got %d", count)
}
return
}
if opts.Limit > 0 {
if count != opts.Limit {
t.Errorf("Expected list size to be limited to %d but got %d", opts.Limit, count)
}
return
}
if count != 10 {
t.Errorf("Expected list size to be 10 but got %d", count)
}
}
func TestListResourceVersion0(t *testing.T) { func TestListResourceVersion0(t *testing.T) {
var testcases = []struct { var testcases = []struct {
name string name string

1
vendor/modules.txt vendored
View File

@ -1642,6 +1642,7 @@ k8s.io/apimachinery/pkg/api/validation/path
k8s.io/apimachinery/pkg/apis/meta/fuzzer k8s.io/apimachinery/pkg/apis/meta/fuzzer
k8s.io/apimachinery/pkg/apis/meta/internalversion k8s.io/apimachinery/pkg/apis/meta/internalversion
k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme
k8s.io/apimachinery/pkg/apis/meta/internalversion/validation
k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/apimachinery/pkg/apis/meta/v1
k8s.io/apimachinery/pkg/apis/meta/v1/unstructured k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme