diff --git a/hack/.golint_failures b/hack/.golint_failures index ea1de427e9d..dc29d3cb9ec 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -309,6 +309,8 @@ staging/src/k8s.io/apiserver/pkg/apis/apiserver staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1 staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1 staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1beta1 +staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal +staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1 staging/src/k8s.io/apiserver/pkg/apis/audit staging/src/k8s.io/apiserver/pkg/apis/audit/v1 staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1 diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/doc.go new file mode 100644 index 00000000000..df3193a924a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2019 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. +*/ + +// +k8s:deepcopy-gen=package +// +groupName=internal.apiserver.k8s.io + +// Package apiserverinternal contains the "internal" version of the API used by +// the apiservers themselves. +package apiserverinternal // import "k8s.io/apiserver/pkg/apis/apiserverinternal" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer/BUILD new file mode 100644 index 00000000000..469c0f18f1b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer/BUILD @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fuzzer.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer", + importpath = "k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer", + visibility = ["//visibility:public"], + deps = ["//staging/src/k8s.io/apimachinery/pkg/runtime/serializer: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"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer/fuzzer.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer/fuzzer.go new file mode 100644 index 00000000000..e054abd144d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer/fuzzer.go @@ -0,0 +1,26 @@ +/* +Copyright 2019 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 fuzzer + +import ( + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// Funcs returns the fuzzer functions for the apiserverinternal api group. +func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{} +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/BUILD new file mode 100644 index 00000000000..38bbeed2348 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/BUILD @@ -0,0 +1,43 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/apiserverinternal/install", + importpath = "k8s.io/apiserver/pkg/apis/apiserverinternal/install", + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +go_test( + name = "go_default_test", + srcs = ["roundtrip_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/install.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/install.go new file mode 100644 index 00000000000..0ecd186949f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/install.go @@ -0,0 +1,33 @@ +/* +Copyright 2019 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 install installs the experimental API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/apis/apiserverinternal" + "k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1" +) + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(apiserverinternal.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(scheme.SetVersionPriority(v1alpha1.SchemeGroupVersion)) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/roundtrip_test.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/roundtrip_test.go new file mode 100644 index 00000000000..3f48ab06310 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/install/roundtrip_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2019 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 install + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" + "k8s.io/apiserver/pkg/apis/apiserverinternal/fuzzer" +) + +func TestRoundTrip(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, fuzzer.Funcs) + roundtrip.RoundTripProtobufTestForAPIGroup(t, Install, fuzzer.Funcs) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/register.go new file mode 100644 index 00000000000..1958c824223 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/register.go @@ -0,0 +1,53 @@ +/* +Copyright 2019 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 apiserverinternal + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "internal.apiserver.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // SchemeBuilder is the scheme builder with scheme init functions to run for this API package. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme is a global function that registers this API group & version to a scheme + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &StorageVersion{}, + &StorageVersionList{}, + ) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/types.go new file mode 100644 index 00000000000..54142205a2d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/types.go @@ -0,0 +1,120 @@ +/* +Copyright 2019 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 apiserverinternal + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Storage version of a specific resource. +type StorageVersion struct { + metav1.TypeMeta + // The name is .. + metav1.ObjectMeta + + // Spec is an empty spec. It is here to comply with Kubernetes API style. + Spec StorageVersionSpec + + // API server instances report the version they can decode and the version they + // encode objects to when persisting objects in the backend. + Status StorageVersionStatus +} + +// StorageVersionSpec is an empty spec. +type StorageVersionSpec struct{} + +// API server instances report the versions they can decode and the version they +// encode objects to when persisting objects in the backend. +type StorageVersionStatus struct { + // The reported versions per API server instance. + // +optional + StorageVersions []ServerStorageVersion + // 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. + // API servers should finish updating its storageVersionStatus entry before + // serving write operations, so that this field will be in sync with the reality. + // +optional + CommonEncodingVersion *string + + // The latest available observations of the storageVersion's state. + // +optional + Conditions []StorageVersionCondition +} + +// An API server instance reports the version it can decode and the version it +// encodes objects to when persisting objects in the backend. +type ServerStorageVersion struct { + // The ID of the reporting API server. + APIServerID string + + // The API server encodes the object to this version when persisting it in + // the backend (e.g., etcd). + EncodingVersion string + + // The API server can decode objects encoded in these versions. + // The encodingVersion must be included in the decodableVersions. + DecodableVersions []string +} + +type StorageVersionConditionType string + +const ( + // Indicates that encoding storage versions reported by all servers are equal. + AllEncodingVersionsEqual StorageVersionConditionType = "AllEncodingVersionsEqual" +) + +type ConditionStatus string + +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// Describes the state of the storageVersion at a certain point. +type StorageVersionCondition struct { + // Type of the condition. + // +optional + Type StorageVersionConditionType + // Status of the condition, one of True, False, Unknown. + // +required + Status ConditionStatus + // If set, this represents the .metadata.generation that the condition was set based upon. + // +optional + ObservedGeneration int64 + // Last time the condition transitioned from one status to another. + // +required + LastTransitionTime metav1.Time + // The reason for the condition's last transition. + // +required + Reason string + // A human readable message indicating details about the transition. + // +required + Message string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// A list of StorageVersions. +type StorageVersionList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + Items []StorageVersion +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/doc.go new file mode 100644 index 00000000000..9e8ec0cfc5d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/doc.go @@ -0,0 +1,27 @@ +/* +Copyright 2019 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:protobuf-gen=package +// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/apiserverinternal +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta + +// +groupName=internal.apiserver.k8s.io + +// Package v1alpha1 contains the v1alpha1 version of the API used by the +// apiservers themselves. +package v1alpha1 // import "k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/register.go new file mode 100644 index 00000000000..e510140fa83 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2019 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "internal.apiserver.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &StorageVersion{}, + &StorageVersionList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/types.go new file mode 100644 index 00000000000..0d80fcfb9fb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1/types.go @@ -0,0 +1,127 @@ +/* +Copyright 2019 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Storage version of a specific resource. +type StorageVersion struct { + metav1.TypeMeta `json:",inline"` + // The name is .. + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec is an empty spec. It is here to comply with Kubernetes API style. + Spec StorageVersionSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // API server instances report the version they can decode and the version they + // encode objects to when persisting objects in the backend. + Status StorageVersionStatus `json:"status" protobuf:"bytes,3,opt,name=status"` +} + +// StorageVersionSpec is an empty spec. +type StorageVersionSpec struct{} + +// API server instances report the versions they can decode and the version they +// encode objects to when persisting objects in the backend. +type StorageVersionStatus struct { + // The reported versions per API server instance. + // +optional + // +listType=map + // +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. + // API servers should finish updating its storageVersionStatus entry before + // serving write operations, so that this field will be in sync with the reality. + // +optional + CommonEncodingVersion *string `json:"commonEncodingVersion,omitempty" protobuf:"bytes,2,opt,name=commonEncodingVersion"` + + // The latest available observations of the storageVersion's state. + // +optional + // +listType=map + // +listMapKey=type + Conditions []StorageVersionCondition `json:"conditions,omitempty" protobuf:"bytes,3,opt,name=conditions"` +} + +// An API server instance reports the version it can decode and the version it +// encodes objects to when persisting objects in the backend. +type ServerStorageVersion struct { + // The ID of the reporting API server. + APIServerID string `json:"apiServerID,omitempty" protobuf:"bytes,1,opt,name=apiServerID"` + + // The API server encodes the object to this version when persisting it in + // the backend (e.g., etcd). + EncodingVersion string `json:"encodingVersion,omitempty" protobuf:"bytes,2,opt,name=encodingVersion"` + + // The API server can decode objects encoded in these versions. + // The encodingVersion must be included in the decodableVersions. + // +listType=set + DecodableVersions []string `json:"decodableVersions,omitempty" protobuf:"bytes,3,opt,name=decodableVersions"` +} + +type StorageVersionConditionType string + +const ( + // Indicates that encoding storage versions reported by all servers are equal. + AllEncodingVersionsEqual StorageVersionConditionType = "AllEncodingVersionsEqual" +) + +type ConditionStatus string + +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// Describes the state of the storageVersion at a certain point. +type StorageVersionCondition struct { + // Type of the condition. + // +required + Type StorageVersionConditionType `json:"type" protobuf:"bytes,1,opt,name=type"` + // Status of the condition, one of True, False, Unknown. + // +required + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"` + // If set, this represents the .metadata.generation that the condition was set based upon. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` + // Last time the condition transitioned from one status to another. + // +required + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +required + Reason string `json:"reason" protobuf:"bytes,5,opt,name=reason"` + // A human readable message indicating details about the transition. + // +required + Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// A list of StorageVersions. +type StorageVersionList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StorageVersion `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/BUILD new file mode 100644 index 00000000000..27e5d8ce21a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/BUILD @@ -0,0 +1,27 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/apiserverinternal/validation", + importpath = "k8s.io/apiserver/pkg/apis/apiserverinternal/validation", + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/validation.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/validation.go new file mode 100644 index 00000000000..d1886af99b4 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/validation.go @@ -0,0 +1,128 @@ +/* +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 ( + "fmt" + "strings" + + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/util/validation" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/apis/apiserverinternal" +) + +// ValidateStorageVersion validate the storage version object. +func ValidateStorageVersion(sv *apiserverinternal.StorageVersion) field.ErrorList { + var allErrs field.ErrorList + allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...) + return allErrs +} + +func validateStorageVersionStatus(ss apiserverinternal.StorageVersionStatus, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + for i, ssv := range ss.StorageVersions { + allErrs = append(allErrs, validateServerStorageVersion(ssv, fldPath.Child("storageVersions").Index(i))...) + } + if err := validateCommonVersion(ss, fldPath); err != nil { + allErrs = append(allErrs, err) + } + allErrs = append(allErrs, validateStorageVersionCondition(ss.Conditions, fldPath)...) + return allErrs +} + +func validateServerStorageVersion(ssv apiserverinternal.ServerStorageVersion, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(ssv.APIServerID, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiServerID"), ssv.APIServerID, msg)) + } + if errs := utilvalidation.IsDNS1035Label(ssv.EncodingVersion); len(errs) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("encodingVersion"), ssv.EncodingVersion, strings.Join(errs, ","))) + } + + found := false + for i, dv := range ssv.DecodableVersions { + if errs := utilvalidation.IsDNS1035Label(dv); len(errs) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("decodableVersions").Index(i), dv, strings.Join(errs, ","))) + } + if dv == ssv.EncodingVersion { + found = true + } + } + if !found { + allErrs = append(allErrs, field.Invalid(fldPath.Child("decodableVersions"), ssv.DecodableVersions, fmt.Sprintf("decodableVersions must include encodingVersion %s", ssv.EncodingVersion))) + } + return allErrs +} + +func commonVersion(ssv []apiserverinternal.ServerStorageVersion) *string { + if len(ssv) == 0 { + return nil + } + commonVersion := ssv[0].EncodingVersion + for _, v := range ssv[1:] { + if v.EncodingVersion != commonVersion { + return nil + } + } + return &commonVersion +} + +func validateCommonVersion(svs apiserverinternal.StorageVersionStatus, fldPath *field.Path) *field.Error { + actualCommonVersion := commonVersion(svs.StorageVersions) + if actualCommonVersion == nil && svs.CommonEncodingVersion == nil { + return nil + } + if actualCommonVersion == nil && svs.CommonEncodingVersion != nil { + return field.Invalid(fldPath.Child("commonEncodingVersion"), *svs.CommonEncodingVersion, "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet") + } + if actualCommonVersion != nil && svs.CommonEncodingVersion == nil { + return field.Invalid(fldPath.Child("commonEncodingVersion"), svs.CommonEncodingVersion, fmt.Sprintf("the common encoding version is %s", *actualCommonVersion)) + } + if actualCommonVersion != nil && svs.CommonEncodingVersion != nil && *actualCommonVersion != *svs.CommonEncodingVersion { + return field.Invalid(fldPath.Child("commonEncodingVersion"), *svs.CommonEncodingVersion, fmt.Sprintf("the actual common encoding version is %s", *actualCommonVersion)) + } + return nil +} + +func validateStorageVersionCondition(conditions []apiserverinternal.StorageVersionCondition, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // We do not verify that the condition type or the condition status is + // a predefined one because we might add more type or status later. + seenType := make(map[apiserverinternal.StorageVersionConditionType]int) + for i, condition := range conditions { + if ii, ok := seenType[condition.Type]; ok { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), string(condition.Type), + fmt.Sprintf("the type of the condition is not unique, it also appears in conditions[%d]", ii))) + } + seenType[condition.Type] = i + for _, msg := range validation.IsQualifiedName(string(condition.Type)) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), string(condition.Type), msg)) + } + for _, msg := range validation.IsQualifiedName(string(condition.Status)) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("status"), string(condition.Type), msg)) + } + if condition.Reason == "" { + allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("reason"), "reason cannot be empty")) + } + if condition.Message == "" { + allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("message"), "message cannot be empty")) + } + } + return allErrs +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/validation_test.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/validation_test.go new file mode 100644 index 00000000000..d0d70611f22 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserverinternal/validation/validation_test.go @@ -0,0 +1,286 @@ +/* +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 ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/apis/apiserverinternal" +) + +func TestValidateServerStorageVersion(t *testing.T) { + cases := []struct { + ssv apiserverinternal.ServerStorageVersion + expectedErr string + }{ + { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "-fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1alpha1", "v1"}, + }, + expectedErr: "apiServerID: Invalid value", + }, + { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1beta1", "v1"}, + }, + expectedErr: "decodableVersions must include encodingVersion", + }, + { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1alpha1", "v1", "-fea"}, + }, + expectedErr: "decodableVersions[2]: Invalid value", + }, + { + ssv: apiserverinternal.ServerStorageVersion{ + APIServerID: "fea", + EncodingVersion: "v1alpha1", + DecodableVersions: []string{"v1alpha1", "v1"}, + }, + expectedErr: "", + }, + } + + for _, tc := range cases { + err := validateServerStorageVersion(tc.ssv, field.NewPath("")).ToAggregate() + if err == nil && len(tc.expectedErr) == 0 { + continue + } + if err != nil && len(tc.expectedErr) == 0 { + t.Errorf("unexpected error %v", err) + continue + } + if err == nil && len(tc.expectedErr) != 0 { + t.Errorf("unexpected empty error") + continue + } + if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) + } + } +} + +func TestValidateCommonVersion(t *testing.T) { + cases := []struct { + status apiserverinternal.StorageVersionStatus + expectedErr string + }{ + { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{}, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), + }, + expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", + }, + { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{ + { + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, + { + APIServerID: "2", + EncodingVersion: "v1", + }, + }, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), + }, + expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", + }, + { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{ + { + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, + { + APIServerID: "2", + EncodingVersion: "v1alpha1", + }, + }, + CommonEncodingVersion: nil, + }, + expectedErr: "Invalid value: \"null\": the common encoding version is v1alpha1", + }, + { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{ + { + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, + { + APIServerID: "2", + EncodingVersion: "v1alpha1", + }, + }, + CommonEncodingVersion: func() *string { a := "v1"; return &a }(), + }, + expectedErr: "Invalid value: \"v1\": the actual common encoding version is v1alpha1", + }, + { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{ + { + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, + { + APIServerID: "2", + EncodingVersion: "v1alpha1", + }, + }, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), + }, + expectedErr: "", + }, + { + status: apiserverinternal.StorageVersionStatus{ + StorageVersions: []apiserverinternal.ServerStorageVersion{ + { + APIServerID: "1", + EncodingVersion: "v1alpha1", + }, + }, + CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), + }, + expectedErr: "", + }, + } + for _, tc := range cases { + err := validateCommonVersion(tc.status, field.NewPath("")) + if err == nil && len(tc.expectedErr) == 0 { + continue + } + if err != nil && len(tc.expectedErr) == 0 { + t.Errorf("unexpected error %v", err) + continue + } + if err == nil && len(tc.expectedErr) != 0 { + t.Errorf("unexpected empty error") + continue + } + if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) + } + } +} + +func TestValidateStorageVersionCondition(t *testing.T) { + cases := []struct { + conditions []apiserverinternal.StorageVersionCondition + expectedErr string + }{ + { + conditions: []apiserverinternal.StorageVersionCondition{ + { + Type: "-fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }, + }, + expectedErr: "type: Invalid value", + }, + { + conditions: []apiserverinternal.StorageVersionCondition{ + { + Type: "fea", + Status: "-True", + Reason: "unknown", + Message: "unknown", + }, + }, + expectedErr: "status: Invalid value", + }, + { + conditions: []apiserverinternal.StorageVersionCondition{ + { + Type: "fea", + Status: "True", + Message: "unknown", + }, + }, + expectedErr: "Required value: reason cannot be empty", + }, + { + conditions: []apiserverinternal.StorageVersionCondition{ + { + Type: "fea", + Status: "True", + Reason: "unknown", + }, + }, + expectedErr: "Required value: message cannot be empty", + }, + { + conditions: []apiserverinternal.StorageVersionCondition{ + { + Type: "fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }, + { + Type: "fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }, + }, + expectedErr: `"fea": the type of the condition is not unique, it also appears in conditions[0]`, + }, + { + conditions: []apiserverinternal.StorageVersionCondition{ + { + Type: "fea", + Status: "True", + Reason: "unknown", + Message: "unknown", + }, + }, + expectedErr: "", + }, + } + for _, tc := range cases { + err := validateStorageVersionCondition(tc.conditions, field.NewPath("")).ToAggregate() + if err == nil && len(tc.expectedErr) == 0 { + continue + } + if err != nil && len(tc.expectedErr) == 0 { + t.Errorf("unexpected error %v", err) + continue + } + if err == nil && len(tc.expectedErr) != 0 { + t.Errorf("unexpected empty error") + continue + } + if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) + } + } +}