Skip to content

Commit e214f24

Browse files
committed
Add ResourceVersionMatch parameter to make Resource Version semantics consistent for list
1 parent cb37c08 commit e214f24

File tree

24 files changed

+684
-51
lines changed

24 files changed

+684
-51
lines changed

pkg/master/reconcilers/lease.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ var _ Leases = &storageLeases{}
6464
func (s *storageLeases) ListLeases() ([]string, error) {
6565
ipInfoList := &corev1.EndpointsList{}
6666
storageOpts := storage.ListOptions{
67-
ResourceVersion: "0",
68-
Predicate: storage.Everything,
67+
ResourceVersion: "0",
68+
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
69+
Predicate: storage.Everything,
6970
}
7071
if err := s.storage.List(apirequest.NewDefaultContext(), s.baseKey, storageOpts, ipInfoList); err != nil {
7172
return nil, err
67 Bytes
Binary file not shown.
67 Bytes
Binary file not shown.

staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
215215
j.Finalizers = nil
216216
}
217217
},
218+
func(j *metav1.ResourceVersionMatch, c fuzz.Continue) {
219+
matches := []metav1.ResourceVersionMatch{"", metav1.ResourceVersionMatchExact, metav1.ResourceVersionMatchNotOlderThan}
220+
*j = matches[c.Rand.Intn(len(matches))]
221+
},
218222
func(j *metav1.ListMeta, c fuzz.Continue) {
219223
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
220224
j.SelfLink = c.RandString()

staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,17 @@ type ListOptions struct {
4444
// If the feature gate WatchBookmarks is not enabled in apiserver,
4545
// this field is ignored.
4646
AllowWatchBookmarks bool
47-
// When specified with a watch call, shows changes that occur after that particular version of a resource.
48-
// Defaults to changes from the beginning of history.
49-
// When specified for list:
50-
// - if unset, then the result is returned from remote storage based on quorum-read flag;
51-
// - if it's 0, then we simply return what we currently have in cache, no guarantee;
52-
// - if set to non zero, then the result is at least as fresh as given rv.
47+
// resourceVersion sets a constraint on what resource versions a request may be served from.
48+
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
49+
// details.
5350
ResourceVersion string
51+
// resourceVersionMatch determines how resourceVersion is applied to list calls.
52+
// It is highly recommended that resourceVersionMatch be set for list calls where
53+
// resourceVersion is set.
54+
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
55+
// details.
56+
ResourceVersionMatch metav1.ResourceVersionMatch
57+
5458
// Timeout for the list/watch call.
5559
TimeoutSeconds *int64
5660
// Limit specifies the maximum number of results to return from the server. The server may
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["validation.go"],
6+
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation",
7+
importpath = "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation",
8+
visibility = ["//visibility:public"],
9+
deps = [
10+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
11+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
12+
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
13+
],
14+
)
15+
16+
go_test(
17+
name = "go_default_test",
18+
srcs = ["validation_test.go"],
19+
embed = [":go_default_library"],
20+
deps = [
21+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
22+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
23+
],
24+
)
25+
26+
filegroup(
27+
name = "package-srcs",
28+
srcs = glob(["**"]),
29+
tags = ["automanaged"],
30+
visibility = ["//visibility:private"],
31+
)
32+
33+
filegroup(
34+
name = "all-srcs",
35+
srcs = [":package-srcs"],
36+
tags = ["automanaged"],
37+
visibility = ["//visibility:public"],
38+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validation
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/util/validation/field"
23+
)
24+
25+
// ValidateListOptions returns all validation errors found while validating the ListOptions.
26+
func ValidateListOptions(options *internalversion.ListOptions) field.ErrorList {
27+
allErrs := field.ErrorList{}
28+
if match := options.ResourceVersionMatch; len(match) > 0 {
29+
if options.Watch {
30+
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden for watch"))
31+
}
32+
if len(options.ResourceVersion) == 0 {
33+
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden unless resourceVersion is provided"))
34+
}
35+
if len(options.Continue) > 0 {
36+
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden when continue is provided"))
37+
}
38+
if match != metav1.ResourceVersionMatchExact && match != metav1.ResourceVersionMatchNotOlderThan {
39+
allErrs = append(allErrs, field.NotSupported(field.NewPath("resourceVersionMatch"), match, []string{string(metav1.ResourceVersionMatchExact), string(metav1.ResourceVersionMatchNotOlderThan), ""}))
40+
}
41+
if match == metav1.ResourceVersionMatchExact && options.ResourceVersion == "0" {
42+
allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\""))
43+
}
44+
}
45+
return allErrs
46+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validation
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"testing"
23+
)
24+
25+
func TestValidateListOptions(t *testing.T) {
26+
cases := []struct {
27+
name string
28+
opts internalversion.ListOptions
29+
expectError string
30+
}{
31+
{
32+
name: "valid-default",
33+
opts: internalversion.ListOptions{},
34+
},
35+
{
36+
name: "valid-resourceversionmatch-exact",
37+
opts: internalversion.ListOptions{
38+
ResourceVersion: "1",
39+
ResourceVersionMatch: metav1.ResourceVersionMatchExact,
40+
},
41+
},
42+
{
43+
name: "invalid-resourceversionmatch-exact",
44+
opts: internalversion.ListOptions{
45+
ResourceVersion: "0",
46+
ResourceVersionMatch: metav1.ResourceVersionMatchExact,
47+
},
48+
expectError: "resourceVersionMatch: Forbidden: resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\"",
49+
},
50+
{
51+
name: "valid-resourceversionmatch-notolderthan",
52+
opts: internalversion.ListOptions{
53+
ResourceVersion: "0",
54+
ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
55+
},
56+
},
57+
{
58+
name: "invalid-resourceversionmatch",
59+
opts: internalversion.ListOptions{
60+
ResourceVersion: "0",
61+
ResourceVersionMatch: "foo",
62+
},
63+
expectError: "resourceVersionMatch: Unsupported value: \"foo\": supported values: \"Exact\", \"NotOlderThan\", \"\"",
64+
},
65+
}
66+
67+
for _, tc := range cases {
68+
t.Run(tc.name, func(t *testing.T) {
69+
errs := ValidateListOptions(&tc.opts)
70+
if tc.expectError != "" {
71+
if len(errs) != 1 {
72+
t.Errorf("expected an error but got %d errors", len(errs))
73+
} else if errs[0].Error() != tc.expectError {
74+
t.Errorf("expected error '%s' but got '%s'", tc.expectError, errs[0].Error())
75+
}
76+
return
77+
}
78+
if len(errs) != 0 {
79+
t.Errorf("expected no errors, but got: %v", errs)
80+
}
81+
})
82+
}
83+
}

staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,11 @@ func Convert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptions,
345345
}
346346
return nil
347347
}
348+
349+
// Convert_Slice_string_To_v1_ResourceVersionMatch allows converting a URL query parameter to ResourceVersionMatch
350+
func Convert_Slice_string_To_v1_ResourceVersionMatch(in *[]string, out *ResourceVersionMatch, s conversion.Scope) error {
351+
if len(*in) > 0 {
352+
*out = ResourceVersionMatch((*in)[0])
353+
}
354+
return nil
355+
}

staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,23 @@ type ListOptions struct {
355355
// +optional
356356
AllowWatchBookmarks bool `json:"allowWatchBookmarks,omitempty" protobuf:"varint,9,opt,name=allowWatchBookmarks"`
357357

358-
// When specified with a watch call, shows changes that occur after that particular version of a resource.
359-
// Defaults to changes from the beginning of history.
360-
// When specified for list:
361-
// - if unset, then the result is returned from remote storage based on quorum-read flag;
362-
// - if it's 0, then we simply return what we currently have in cache, no guarantee;
363-
// - if set to non zero, then the result is at least as fresh as given rv.
358+
// resourceVersion sets a constraint on what resource versions a request may be served from.
359+
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
360+
// details.
361+
//
362+
// Defaults to unset
364363
// +optional
365364
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,4,opt,name=resourceVersion"`
365+
366+
// resourceVersionMatch determines how resourceVersion is applied to list calls.
367+
// It is highly recommended that resourceVersionMatch be set for list calls where
368+
// resourceVersion is set
369+
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
370+
// details.
371+
//
372+
// Defaults to unset
373+
// +optional
374+
ResourceVersionMatch ResourceVersionMatch `json:"resourceVersionMatch,omitempty" protobuf:"bytes,10,opt,name=resourceVersionMatch,casttype=ResourceVersionMatch"`
366375
// Timeout for the list/watch call.
367376
// This limits the duration of the call, regardless of any activity or inactivity.
368377
// +optional
@@ -402,6 +411,25 @@ type ListOptions struct {
402411
Continue string `json:"continue,omitempty" protobuf:"bytes,8,opt,name=continue"`
403412
}
404413

414+
// resourceVersionMatch specifies how the resourceVersion parameter is applied. resourceVersionMatch
415+
// may only be set if resourceVersion is also set.
416+
//
417+
// "NotOlderThan" matches data at least as new as the provided resourceVersion.
418+
// "Exact" matches data at the exact resourceVersion provided.
419+
//
420+
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
421+
// details.
422+
type ResourceVersionMatch string
423+
424+
const (
425+
// ResourceVersionMatchNotOlderThan matches data at least as new as the provided
426+
// resourceVersion.
427+
ResourceVersionMatchNotOlderThan ResourceVersionMatch = "NotOlderThan"
428+
// ResourceVersionMatchExact matches data at the exact resourceVersion
429+
// provided.
430+
ResourceVersionMatchExact ResourceVersionMatch = "Exact"
431+
)
432+
405433
// +k8s:conversion-gen:explicit-from=net/url.Values
406434
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
407435

@@ -423,10 +451,12 @@ type ExportOptions struct {
423451
// GetOptions is the standard query options to the standard REST get call.
424452
type GetOptions struct {
425453
TypeMeta `json:",inline"`
426-
// When specified:
427-
// - if unset, then the result is returned from remote storage based on quorum-read flag;
428-
// - if it's 0, then we simply return what we currently have in cache, no guarantee;
429-
// - if set to non zero, then the result is at least as fresh as given rv.
454+
// resourceVersion sets a constraint on what resource versions a request may be served from.
455+
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for
456+
// details.
457+
//
458+
// Defaults to unset
459+
// +optional
430460
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"`
431461
// +k8s:deprecated=includeUninitialized,protobuf=2
432462
}

0 commit comments

Comments
 (0)