Merge pull request #92064 from roycaihw/storage-version/serve-api

Serve storage-versions API in kube-apiserver
This commit is contained in:
Kubernetes Prow Robot 2020-09-10 10:45:48 -07:00 committed by GitHub
commit 6085d90897
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1927 additions and 3 deletions

File diff suppressed because it is too large Load Diff

View File

@ -275,6 +275,7 @@ var apiVersionPriorities = map[schema.GroupVersion]priority{
{Group: "discovery.k8s.io", Version: "v1beta1"}: {group: 16200, version: 12},
{Group: "discovery.k8s.io", Version: "v1alpha1"}: {group: 16200, version: 9},
{Group: "flowcontrol.apiserver.k8s.io", Version: "v1alpha1"}: {group: 16100, version: 9},
{Group: "internal.apiserver.k8s.io", Version: "v1alpha1"}: {group: 16000, version: 9},
// Append a new group to the end of the list if unsure.
// You can use min(existing group)-100 as the initial value for a group.
// Version can be set to 9 (to have space around) for a new group.

View File

@ -6,6 +6,7 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/apis/apiserverinternal/install",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/apis/apiserverinternal/v1alpha1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -21,10 +21,15 @@ package install
import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
"k8s.io/kubernetes/pkg/apis/apiserverinternal/v1alpha1"
)
func init() {
Install(legacyscheme.Scheme)
}
// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
utilruntime.Must(apiserverinternal.AddToScheme(scheme))

View File

@ -7,6 +7,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",

View File

@ -25,10 +25,42 @@ import (
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
)
// ValidateStorageVersion validate the storage version object.
func ValidateStorageVersion(sv *apiserverinternal.StorageVersion) field.ErrorList {
var allErrs field.ErrorList
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&sv.ObjectMeta, false, ValidateStorageVersionName, field.NewPath("metadata"))...)
allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...)
return allErrs
}
// ValidateStorageVersionName is a ValidateNameFunc for storage version names
func ValidateStorageVersionName(name string, prefix bool) []string {
var allErrs []string
idx := strings.LastIndex(name, ".")
if idx < 0 {
allErrs = append(allErrs, "name must be in the form of <group>.<resource>")
} else {
for _, msg := range utilvalidation.IsDNS1123Subdomain(name[:idx]) {
allErrs = append(allErrs, "the group segment "+msg)
}
for _, msg := range utilvalidation.IsDNS1035Label(name[idx+1:]) {
allErrs = append(allErrs, "the resource segment "+msg)
}
}
return allErrs
}
// ValidateStorageVersionUpdate tests if an update to a StorageVersion is valid.
func ValidateStorageVersionUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList {
// no error since StorageVersionSpec is an empty spec
return field.ErrorList{}
}
// ValidateStorageVersionStatusUpdate tests if an update to a StorageVersionStatus is valid.
func ValidateStorageVersionStatusUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList {
var allErrs field.ErrorList
allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...)
return allErrs

View File

@ -284,3 +284,66 @@ func TestValidateStorageVersionCondition(t *testing.T) {
}
}
}
func TestValidateStorageVersionName(t *testing.T) {
cases := []struct {
name string
expectedErr string
}{
{
name: "",
expectedErr: `name must be in the form of <group>.<resource>`,
},
{
name: "pods",
expectedErr: `name must be in the form of <group>.<resource>`,
},
{
name: "core.pods",
expectedErr: "",
},
{
name: "authentication.k8s.io.tokenreviews",
expectedErr: "",
},
{
name: strings.Repeat("x", 253) + ".tokenreviews",
expectedErr: "",
},
{
name: strings.Repeat("x", 254) + ".tokenreviews",
expectedErr: `the group segment must be no more than 253 characters`,
},
{
name: "authentication.k8s.io." + strings.Repeat("x", 63),
expectedErr: "",
},
{
name: "authentication.k8s.io." + strings.Repeat("x", 64),
expectedErr: `the resource segment must be no more than 63 characters`,
},
}
for _, tc := range cases {
errs := ValidateStorageVersionName(tc.name, false)
if errs == nil && len(tc.expectedErr) == 0 {
continue
}
if errs != nil && len(tc.expectedErr) == 0 {
t.Errorf("unexpected error %v", errs)
continue
}
if errs == nil && len(tc.expectedErr) != 0 {
t.Errorf("unexpected empty error")
continue
}
found := false
for _, msg := range errs {
if msg == tc.expectedErr {
found = true
}
}
if !found {
t.Errorf("expected error to contain %s, got %v", tc.expectedErr, errs)
}
}
}

View File

@ -44,6 +44,7 @@ go_library(
"//pkg/kubeapiserver/options:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//pkg/registry/admissionregistration/rest:go_default_library",
"//pkg/registry/apiserverinternal/rest:go_default_library",
"//pkg/registry/apps/rest:go_default_library",
"//pkg/registry/authentication/rest:go_default_library",
"//pkg/registry/authorization/rest:go_default_library",
@ -72,6 +73,7 @@ go_library(
"//pkg/util/node:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/api/apiserverinternal/v1alpha1:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/authentication/v1:go_default_library",
"//staging/src/k8s.io/api/authentication/v1beta1:go_default_library",

View File

@ -27,6 +27,7 @@ import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
apiserverinternalv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
@ -93,6 +94,7 @@ import (
// RESTStorage installers
admissionregistrationrest "k8s.io/kubernetes/pkg/registry/admissionregistration/rest"
apiserverinternalrest "k8s.io/kubernetes/pkg/registry/apiserverinternal/rest"
appsrest "k8s.io/kubernetes/pkg/registry/apps/rest"
authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest"
authorizationrest "k8s.io/kubernetes/pkg/registry/authorization/rest"
@ -415,6 +417,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
// TODO: describe the priority all the way down in the RESTStorageProviders and plumb it back through the various discovery
// handlers that we have.
restStorageProviders := []RESTStorageProvider{
apiserverinternalrest.StorageProvider{},
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator, APIAudiences: c.GenericConfig.Authentication.APIAudiences},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
autoscalingrest.RESTStorageProvider{},
@ -633,6 +636,7 @@ func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig {
)
// disable alpha versions explicitly so we have a full list of what's possible to serve
ret.DisableVersions(
apiserverinternalv1alpha1.SchemeGroupVersion,
batchapiv2alpha1.SchemeGroupVersion,
nodev1alpha1.SchemeGroupVersion,
rbacv1alpha1.SchemeGroupVersion,

View File

@ -14,6 +14,7 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
@ -47,6 +48,7 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/printers/internalversion",
deps = [
"//pkg/apis/admissionregistration:go_default_library",
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/apps/install:go_default_library",
"//pkg/apis/authentication/install:go_default_library",
@ -83,6 +85,7 @@ go_library(
"//pkg/features:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/node:go_default_library",
"//staging/src/k8s.io/api/apiserverinternal/v1alpha1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v2beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",

View File

@ -25,6 +25,7 @@ import (
"strings"
"time"
apiserverinternalv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
batchv1 "k8s.io/api/batch/v1"
@ -48,6 +49,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
@ -571,6 +573,15 @@ func AddHandlers(h printers.PrintHandler) {
}
h.TableHandler(priorityLevelColumnDefinitions, printPriorityLevelConfiguration)
h.TableHandler(priorityLevelColumnDefinitions, printPriorityLevelConfigurationList)
storageVersionColumnDefinitions := []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "CommonEncodingVersion", Type: "string", Description: apiserverinternalv1alpha1.StorageVersionStatus{}.SwaggerDoc()["commonEncodingVersion"]},
{Name: "StorageVersions", Type: "string", Description: apiserverinternalv1alpha1.StorageVersionStatus{}.SwaggerDoc()["storageVersions"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.TableHandler(storageVersionColumnDefinitions, printStorageVersion)
h.TableHandler(storageVersionColumnDefinitions, printStorageVersionList)
}
// Pass ports=nil for all ports.
@ -2477,6 +2488,46 @@ func printFlowSchemaList(list *flowcontrol.FlowSchemaList, options printers.Gene
return rows, nil
}
func printStorageVersion(obj *apiserverinternal.StorageVersion, options printers.GenerateOptions) ([]metav1.TableRow, error) {
row := metav1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
commonEncodingVersion := "<unset>"
if obj.Status.CommonEncodingVersion != nil {
commonEncodingVersion = *obj.Status.CommonEncodingVersion
}
row.Cells = append(row.Cells, obj.Name, commonEncodingVersion, formatStorageVersions(obj.Status.StorageVersions), translateTimestampSince(obj.CreationTimestamp))
return []metav1.TableRow{row}, nil
}
func formatStorageVersions(storageVersions []apiserverinternal.ServerStorageVersion) string {
list := []string{}
max := 3
more := false
count := 0
for _, sv := range storageVersions {
if len(list) < max {
list = append(list, fmt.Sprintf("%s=%s", sv.APIServerID, sv.EncodingVersion))
} else if len(list) == max {
more = true
}
count++
}
return listWithMoreString(list, more, count, max)
}
func printStorageVersionList(list *apiserverinternal.StorageVersionList, options printers.GenerateOptions) ([]metav1.TableRow, error) {
rows := make([]metav1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printStorageVersion(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printPriorityLevelConfiguration(obj *flowcontrol.PriorityLevelConfiguration, options printers.GenerateOptions) ([]metav1.TableRow, error) {
row := metav1.TableRow{
Object: runtime.RawExtension{Object: obj},

View File

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
@ -5311,3 +5312,125 @@ func TestPrintPriorityLevelConfiguration(t *testing.T) {
}
}
}
func TestPrintStorageVersion(t *testing.T) {
commonEncodingVersion := "v1"
tests := []struct {
sv apiserverinternal.StorageVersion
expected []metav1.TableRow
}{
{
sv: apiserverinternal.StorageVersion{
ObjectMeta: metav1.ObjectMeta{
Name: "empty",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Status: apiserverinternal.StorageVersionStatus{},
},
// Columns: Name, CommonEncodingVersion, StorageVersions, Age
expected: []metav1.TableRow{{Cells: []interface{}{"empty", "<unset>", "<unset>", "0s"}}},
},
{
sv: apiserverinternal.StorageVersion{
ObjectMeta: metav1.ObjectMeta{
Name: "valid",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Status: apiserverinternal.StorageVersionStatus{
StorageVersions: []apiserverinternal.ServerStorageVersion{
{
APIServerID: "1",
EncodingVersion: "v1",
DecodableVersions: []string{"v1"},
},
{
APIServerID: "2",
EncodingVersion: "v1",
DecodableVersions: []string{"v1", "v2"},
},
},
CommonEncodingVersion: &commonEncodingVersion,
},
},
// Columns: Name, CommonEncodingVersion, StorageVersions, Age
expected: []metav1.TableRow{{Cells: []interface{}{"valid", "v1", "1=v1,2=v1", "0s"}}},
},
{
sv: apiserverinternal.StorageVersion{
ObjectMeta: metav1.ObjectMeta{
Name: "disagree",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Status: apiserverinternal.StorageVersionStatus{
StorageVersions: []apiserverinternal.ServerStorageVersion{
{
APIServerID: "1",
EncodingVersion: "v1",
DecodableVersions: []string{"v1"},
},
{
APIServerID: "2",
EncodingVersion: "v1",
DecodableVersions: []string{"v1", "v2"},
},
{
APIServerID: "3",
EncodingVersion: "v2",
DecodableVersions: []string{"v2"},
},
},
},
},
// Columns: Name, CommonEncodingVersion, StorageVersions, Age
expected: []metav1.TableRow{{Cells: []interface{}{"disagree", "<unset>", "1=v1,2=v1,3=v2", "0s"}}},
},
{
sv: apiserverinternal.StorageVersion{
ObjectMeta: metav1.ObjectMeta{
Name: "agreeWithMore",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Status: apiserverinternal.StorageVersionStatus{
StorageVersions: []apiserverinternal.ServerStorageVersion{
{
APIServerID: "1",
EncodingVersion: "v1",
DecodableVersions: []string{"v1"},
},
{
APIServerID: "2",
EncodingVersion: "v1",
DecodableVersions: []string{"v1", "v2"},
},
{
APIServerID: "3",
EncodingVersion: "v1",
DecodableVersions: []string{"v1", "v2"},
},
{
APIServerID: "4",
EncodingVersion: "v1",
DecodableVersions: []string{"v1", "v2", "v3alpha1"},
},
},
CommonEncodingVersion: &commonEncodingVersion,
},
},
// Columns: Name, CommonEncodingVersion, StorageVersions, Age
expected: []metav1.TableRow{{Cells: []interface{}{"agreeWithMore", "v1", "1=v1,2=v1,3=v1 + 1 more...", "0s"}}},
},
}
for i, test := range tests {
rows, err := printStorageVersion(&test.sv, printers.GenerateOptions{})
if err != nil {
t.Fatal(err)
}
for i := range rows {
rows[i].Object.Object = nil
}
if !reflect.DeepEqual(test.expected, rows) {
t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows))
}
}
}

View File

@ -25,6 +25,8 @@ filegroup(
"//pkg/registry/admissionregistration/mutatingwebhookconfiguration:all-srcs",
"//pkg/registry/admissionregistration/rest:all-srcs",
"//pkg/registry/admissionregistration/validatingwebhookconfiguration:all-srcs",
"//pkg/registry/apiserverinternal/rest:all-srcs",
"//pkg/registry/apiserverinternal/storageversion:all-srcs",
"//pkg/registry/apps/controllerrevision:all-srcs",
"//pkg/registry/apps/daemonset:all-srcs",
"//pkg/registry/apps/deployment:all-srcs",

View File

@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/apiserverinternal/rest",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/registry/apiserverinternal/storageversion/storage:go_default_library",
"//staging/src/k8s.io/api/apiserverinternal/v1alpha1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/storage: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,62 @@
/*
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 rest
import (
apiserverv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
storageversionstorage "k8s.io/kubernetes/pkg/registry/apiserverinternal/storageversion/storage"
)
// StorageProvider is a REST storage provider for internal.apiserver.k8s.io
type StorageProvider struct{}
// NewRESTStorage returns a StorageProvider
func (p StorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiserverinternal.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
if apiResourceConfigSource.VersionEnabled(apiserverv1alpha1.SchemeGroupVersion) {
storageMap, err := p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter)
if err != nil {
return genericapiserver.APIGroupInfo{}, false, err
}
apiGroupInfo.VersionedResourcesStorageMap[apiserverv1alpha1.SchemeGroupVersion.Version] = storageMap
}
return apiGroupInfo, true, nil
}
func (p StorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
storage := map[string]rest.Storage{}
s, status, err := storageversionstorage.NewREST(restOptionsGetter)
if err != nil {
return nil, err
}
storage["storageversions"] = s
storage["storageversions/status"] = status
return storage, nil
}
// GroupName is the group name for the storage provider
func (p StorageProvider) GroupName() string {
return apiserverinternal.GroupName
}

View File

@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"strategy.go",
],
importpath = "k8s.io/kubernetes/pkg/registry/apiserverinternal/storageversion",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/apis/apiserverinternal/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/registry/apiserverinternal/storageversion/storage:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,19 @@
/*
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 storageversion provides Registry interface and it's RESTStorage
// implementation for storing StorageVersion api objects.
package storageversion // import "k8s.io/kubernetes/pkg/registry/apiserverinternal/storageversion"

View File

@ -0,0 +1,34 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/apiserverinternal/storageversion/storage",
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/apiserverinternal:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/printers/storage:go_default_library",
"//pkg/registry/apiserverinternal/storageversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest: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,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 storage
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
strategy "k8s.io/kubernetes/pkg/registry/apiserverinternal/storageversion"
)
// REST implements a RESTStorage for storage version against etcd
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against storageVersions
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &apiserverinternal.StorageVersion{} },
NewListFunc: func() runtime.Object { return &apiserverinternal.StorageVersionList{} },
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*apiserverinternal.StorageVersion).Name, nil
},
DefaultQualifiedResource: apiserverinternal.Resource("storageversions"),
CreateStrategy: strategy.Strategy,
UpdateStrategy: strategy.Strategy,
DeleteStrategy: strategy.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
return nil, nil, err
}
statusStore := *store
statusStore.UpdateStrategy = strategy.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
// StatusREST implements the REST endpoint for changing the status of a storageVersion
type StatusREST struct {
store *genericregistry.Store
}
// New creates a new StorageVersion object.
func (r *StatusREST) New() runtime.Object {
return &apiserverinternal.StorageVersion{}
}
// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return r.store.Get(ctx, name, options)
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}

View File

@ -0,0 +1,103 @@
/*
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 storageversion
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
"k8s.io/kubernetes/pkg/apis/apiserverinternal/validation"
)
// storageVersionStrategy implements verification logic for StorageVersion.
type storageVersionStrategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// Strategy is the default logic that applies when creating and updating StorageVersion objects.
var Strategy = storageVersionStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// NamespaceScoped returns false because all StorageVersion's need to be cluster scoped
func (storageVersionStrategy) NamespaceScoped() bool {
return false
}
// PrepareForCreate clears the status of an StorageVersion before creation.
func (storageVersionStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
sv := obj.(*apiserverinternal.StorageVersion)
sv.Status = apiserverinternal.StorageVersionStatus{}
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (storageVersionStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
sv := obj.(*apiserverinternal.StorageVersion)
sv.Status = old.(*apiserverinternal.StorageVersion).Status
}
// Validate validates a new storageVersion.
func (storageVersionStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
sv := obj.(*apiserverinternal.StorageVersion)
return validation.ValidateStorageVersion(sv)
}
// Canonicalize normalizes the object after validation.
func (storageVersionStrategy) Canonicalize(obj runtime.Object) {
}
// Does not allow creating a StorageVersion object with a PUT request.
func (storageVersionStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (storageVersionStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newStorageVersion := obj.(*apiserverinternal.StorageVersion)
oldStorageVersion := old.(*apiserverinternal.StorageVersion)
validationErrorList := validation.ValidateStorageVersionUpdate(newStorageVersion, oldStorageVersion)
return validationErrorList
}
// AllowUnconditionalUpdate is the default update policy for storageVersion objects. Status update should
// only be allowed if version match.
func (storageVersionStrategy) AllowUnconditionalUpdate() bool {
return false
}
type storageVersionStatusStrategy struct {
storageVersionStrategy
}
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = storageVersionStatusStrategy{Strategy}
func (storageVersionStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newSV := obj.(*apiserverinternal.StorageVersion)
oldSV := old.(*apiserverinternal.StorageVersion)
newSV.Spec = oldSV.Spec
metav1.ResetObjectMetaForStatus(&newSV.ObjectMeta, &oldSV.ObjectMeta)
}
func (storageVersionStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateStorageVersionStatusUpdate(obj.(*apiserverinternal.StorageVersion), old.(*apiserverinternal.StorageVersion))
}

View File

@ -102,7 +102,7 @@ message StorageVersionStatus {
// The reported versions per API server instance.
// +optional
// +listType=map
// +listMapKey=apiserverID
// +listMapKey=apiServerID
repeated ServerStorageVersion storageVersions = 1;
// If all API server instances agree on the same encoding storage version,

View File

@ -47,7 +47,7 @@ type StorageVersionStatus struct {
// The reported versions per API server instance.
// +optional
// +listType=map
// +listMapKey=apiserverID
// +listMapKey=apiServerID
StorageVersions []ServerStorageVersion `json:"storageVersions,omitempty" protobuf:"bytes,1,opt,name=storageVersions"`
// If all API server instances agree on the same encoding storage version,
// then this field is set to that version. Otherwise this field is left empty.

View File

@ -28,6 +28,7 @@ go_test(
"//pkg/features:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//staging/src/k8s.io/api/apiserverinternal/v1alpha1:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v2alpha1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",

View File

@ -57,6 +57,7 @@ var statusData = map[schema.GroupVersionResource]string{
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus"}]}}`,
gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus", "status": "True"}]}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
}
const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"true"}]}}`
@ -69,6 +70,12 @@ var ignoreList = map[schema.GroupVersionResource]struct{}{
gvr("apiregistration.k8s.io", "v1", "apiservices"): {},
}
// Some status-only APIs have empty object on creation. Therefore we don't expect create_test
// managedFields for these APIs
var ignoreCreateManagementList = map[schema.GroupVersionResource]struct{}{
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {},
}
func gvr(g, v, r string) schema.GroupVersionResource {
return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
}
@ -200,7 +207,11 @@ func TestApplyStatus(t *testing.T) {
t.Fatalf("Couldn't find apply_status_test: %v", managedFields)
}
if !findManager(managedFields, "create_test") {
t.Fatalf("Couldn't find create_test: %v", managedFields)
if _, ok := ignoreCreateManagementList[mapping.Resource]; !ok {
t.Fatalf("Couldn't find create_test: %v", managedFields)
}
} else if _, ok := ignoreCreateManagementList[mapping.Resource]; ok {
t.Fatalf("found create_test in ignoreCreateManagementList resource: %v", managedFields)
}
if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil {

View File

@ -27,6 +27,7 @@ import (
"testing"
"time"
apiserverinternalv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
discoveryv1alpha1 "k8s.io/api/discovery/v1alpha1"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
@ -172,6 +173,7 @@ func TestServerSidePrint(t *testing.T) {
extensionsv1beta1.SchemeGroupVersion,
nodev1alpha1.SchemeGroupVersion,
flowcontrolv1alpha1.SchemeGroupVersion,
apiserverinternalv1alpha1.SchemeGroupVersion,
},
[]schema.GroupVersionResource{},
)

View File

@ -508,6 +508,13 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes
ExpectedEtcdPath: "/registry/runtimeclasses/rc2",
},
// --
// k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {
Stub: `{"metadata":{"name":"sv1.test"},"spec":{}}`,
ExpectedEtcdPath: "/registry/storageversions/sv1.test",
},
// --
}
// add csinodes if CSINodeInfo feature gate is enabled