Expose storage version hash
Kubernetes-commit: 3b618af0d435628feedf06f97bd1c69340d07d95
This commit is contained in:
parent
7ec69625ac
commit
d99ef88606
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageVersionHash calculates the storage version hash for a
|
||||||
|
// <group/version/kind> tuple.
|
||||||
|
// WARNING: this function is subject to change. Clients shouldn't depend on
|
||||||
|
// this function.
|
||||||
|
func StorageVersionHash(group, version, kind string) string {
|
||||||
|
gvk := group + "/" + version + "/" + kind
|
||||||
|
if gvk == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
bytes := sha256.Sum256([]byte(gvk))
|
||||||
|
// Assuming there are N kinds in the cluster, and the hash is X-byte long,
|
||||||
|
// the chance of colliding hash P(N,X) approximates to 1-e^(-(N^2)/2^(8X+1)).
|
||||||
|
// P(10,000, 8) ~= 2.7*10^(-12), which is low enough.
|
||||||
|
// See https://en.wikipedia.org/wiki/Birthday_problem#Approximations.
|
||||||
|
return base64.StdEncoding.EncodeToString(
|
||||||
|
bytes[:8])
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers"
|
"k8s.io/apiserver/pkg/endpoints/handlers"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
|
|
@ -133,6 +134,20 @@ func (a *APIInstaller) newWebService() *restful.WebService {
|
||||||
return ws
|
return ws
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate the storage gvk, the gvk objects are converted to before persisted to the etcd.
|
||||||
|
func getStorageVersionKind(storageVersioner runtime.GroupVersioner, storage rest.Storage, typer runtime.ObjectTyper) (schema.GroupVersionKind, error) {
|
||||||
|
object := storage.New()
|
||||||
|
fqKinds, _, err := typer.ObjectKinds(object)
|
||||||
|
if err != nil {
|
||||||
|
return schema.GroupVersionKind{}, err
|
||||||
|
}
|
||||||
|
gvk, ok := storageVersioner.KindForGroupVersionKinds(fqKinds)
|
||||||
|
if !ok {
|
||||||
|
return schema.GroupVersionKind{}, fmt.Errorf("cannot find the storage version kind for %v", reflect.TypeOf(object))
|
||||||
|
}
|
||||||
|
return gvk, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetResourceKind returns the external group version kind registered for the given storage
|
// GetResourceKind returns the external group version kind registered for the given storage
|
||||||
// object. If the storage object is a subresource and has an override supplied for it, it returns
|
// object. If the storage object is a subresource and has an override supplied for it, it returns
|
||||||
// the group version kind supplied in the override.
|
// the group version kind supplied in the override.
|
||||||
|
|
@ -227,6 +242,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||||
watcher, isWatcher := storage.(rest.Watcher)
|
watcher, isWatcher := storage.(rest.Watcher)
|
||||||
connecter, isConnecter := storage.(rest.Connecter)
|
connecter, isConnecter := storage.(rest.Connecter)
|
||||||
storageMeta, isMetadata := storage.(rest.StorageMetadata)
|
storageMeta, isMetadata := storage.(rest.StorageMetadata)
|
||||||
|
storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
|
||||||
if !isMetadata {
|
if !isMetadata {
|
||||||
storageMeta = defaultStorageMetadata{}
|
storageMeta = defaultStorageMetadata{}
|
||||||
}
|
}
|
||||||
|
|
@ -365,6 +381,17 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||||
tableProvider, _ := storage.(rest.TableConvertor)
|
tableProvider, _ := storage.(rest.TableConvertor)
|
||||||
|
|
||||||
var apiResource metav1.APIResource
|
var apiResource metav1.APIResource
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionHash) &&
|
||||||
|
isStorageVersionProvider &&
|
||||||
|
storageVersionProvider.StorageVersion() != nil {
|
||||||
|
versioner := storageVersionProvider.StorageVersion()
|
||||||
|
gvk, err := getStorageVersionKind(versioner, storage, a.group.Typer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiResource.StorageVersionHash = discovery.StorageVersionHash(gvk.Group, gvk.Version, gvk.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the list of actions for the given scope.
|
// Get the list of actions for the given scope.
|
||||||
switch {
|
switch {
|
||||||
case !namespaceScoped:
|
case !namespaceScoped:
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,13 @@ const (
|
||||||
// Server-side apply. Merging happens on the server.
|
// Server-side apply. Merging happens on the server.
|
||||||
ServerSideApply utilfeature.Feature = "ServerSideApply"
|
ServerSideApply utilfeature.Feature = "ServerSideApply"
|
||||||
|
|
||||||
|
// owner: @caesarxuchao
|
||||||
|
// alpha: v1.14
|
||||||
|
//
|
||||||
|
// Allow apiservers to expose the storage version hash in the discovery
|
||||||
|
// document.
|
||||||
|
StorageVersionHash utilfeature.Feature = "StorageVersionHash"
|
||||||
|
|
||||||
// owner: @ksubrmnn
|
// owner: @ksubrmnn
|
||||||
// alpha: v1.14
|
// alpha: v1.14
|
||||||
//
|
//
|
||||||
|
|
@ -118,6 +125,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||||
APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
|
APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
DryRun: {Default: true, PreRelease: utilfeature.Beta},
|
DryRun: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha},
|
ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
StorageVersionHash: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
WinOverlay: {Default: false, PreRelease: utilfeature.Alpha},
|
WinOverlay: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
WinDSR: {Default: false, PreRelease: utilfeature.Alpha},
|
WinDSR: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,12 @@ type Store struct {
|
||||||
// resource. It is wrapped into a "DryRunnableStorage" that will
|
// resource. It is wrapped into a "DryRunnableStorage" that will
|
||||||
// either pass-through or simply dry-run.
|
// either pass-through or simply dry-run.
|
||||||
Storage DryRunnableStorage
|
Storage DryRunnableStorage
|
||||||
|
// StorageVersioner outputs the <group/version/kind> an object will be
|
||||||
|
// converted to before persisted in etcd, given a list of possible
|
||||||
|
// kinds of the object.
|
||||||
|
// If the StorageVersioner is nil, apiserver will leave the
|
||||||
|
// storageVersionHash as empty in the discovery document.
|
||||||
|
StorageVersioner runtime.GroupVersioner
|
||||||
// Called to cleanup clients used by the underlying Storage; optional.
|
// Called to cleanup clients used by the underlying Storage; optional.
|
||||||
DestroyFunc func()
|
DestroyFunc func()
|
||||||
}
|
}
|
||||||
|
|
@ -1287,6 +1293,7 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
|
||||||
attrFunc,
|
attrFunc,
|
||||||
triggerFunc,
|
triggerFunc,
|
||||||
)
|
)
|
||||||
|
e.StorageVersioner = opts.StorageConfig.EncodeVersioner
|
||||||
|
|
||||||
if opts.CountMetricPollPeriod > 0 {
|
if opts.CountMetricPollPeriod > 0 {
|
||||||
stopFunc := e.startObservingCount(opts.CountMetricPollPeriod)
|
stopFunc := e.startObservingCount(opts.CountMetricPollPeriod)
|
||||||
|
|
@ -1327,3 +1334,7 @@ func (e *Store) ConvertToTable(ctx context.Context, object runtime.Object, table
|
||||||
}
|
}
|
||||||
return rest.NewDefaultTableConvertor(e.qualifiedResourceFromContext(ctx)).ConvertToTable(ctx, object, tableOptions)
|
return rest.NewDefaultTableConvertor(e.qualifiedResourceFromContext(ctx)).ConvertToTable(ctx, object, tableOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Store) StorageVersion() runtime.GroupVersioner {
|
||||||
|
return e.StorageVersioner
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -332,3 +332,12 @@ type StorageMetadata interface {
|
||||||
// it is not nil. Only the type of the return object matters, the value will be ignored.
|
// it is not nil. Only the type of the return object matters, the value will be ignored.
|
||||||
ProducesObject(verb string) interface{}
|
ProducesObject(verb string) interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StorageVersionProvider is an optional interface that a storage object can
|
||||||
|
// implement if it wishes to disclose its storage version.
|
||||||
|
type StorageVersionProvider interface {
|
||||||
|
// StorageVersion returns a group versioner, which will outputs the gvk
|
||||||
|
// an object will be converted to before persisted in etcd, given a
|
||||||
|
// list of kinds the object might belong to.
|
||||||
|
StorageVersion() runtime.GroupVersioner
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,15 +40,15 @@ type StorageCodecConfig struct {
|
||||||
|
|
||||||
// NewStorageCodec assembles a storage codec for the provided storage media type, the provided serializer, and the requested
|
// NewStorageCodec assembles a storage codec for the provided storage media type, the provided serializer, and the requested
|
||||||
// storage and memory versions.
|
// storage and memory versions.
|
||||||
func NewStorageCodec(opts StorageCodecConfig) (runtime.Codec, error) {
|
func NewStorageCodec(opts StorageCodecConfig) (runtime.Codec, runtime.GroupVersioner, error) {
|
||||||
mediaType, _, err := mime.ParseMediaType(opts.StorageMediaType)
|
mediaType, _, err := mime.ParseMediaType(opts.StorageMediaType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%q is not a valid mime-type", opts.StorageMediaType)
|
return nil, nil, fmt.Errorf("%q is not a valid mime-type", opts.StorageMediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer, ok := runtime.SerializerInfoForMediaType(opts.StorageSerializer.SupportedMediaTypes(), mediaType)
|
serializer, ok := runtime.SerializerInfoForMediaType(opts.StorageSerializer.SupportedMediaTypes(), mediaType)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unable to find serializer for %q", mediaType)
|
return nil, nil, fmt.Errorf("unable to find serializer for %q", mediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := serializer.Serializer
|
s := serializer.Serializer
|
||||||
|
|
@ -74,14 +74,16 @@ func NewStorageCodec(opts StorageCodecConfig) (runtime.Codec, error) {
|
||||||
decoders = opts.DecoderDecoratorFn(decoders)
|
decoders = opts.DecoderDecoratorFn(decoders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the storage receives the correct version.
|
encodeVersioner := runtime.NewMultiGroupVersioner(
|
||||||
encoder = opts.StorageSerializer.EncoderForVersion(
|
|
||||||
encoder,
|
|
||||||
runtime.NewMultiGroupVersioner(
|
|
||||||
opts.StorageVersion,
|
opts.StorageVersion,
|
||||||
schema.GroupKind{Group: opts.StorageVersion.Group},
|
schema.GroupKind{Group: opts.StorageVersion.Group},
|
||||||
schema.GroupKind{Group: opts.MemoryVersion.Group},
|
schema.GroupKind{Group: opts.MemoryVersion.Group},
|
||||||
),
|
)
|
||||||
|
|
||||||
|
// Ensure the storage receives the correct version.
|
||||||
|
encoder = opts.StorageSerializer.EncoderForVersion(
|
||||||
|
encoder,
|
||||||
|
encodeVersioner,
|
||||||
)
|
)
|
||||||
decoder := opts.StorageSerializer.DecoderToVersion(
|
decoder := opts.StorageSerializer.DecoderToVersion(
|
||||||
recognizer.NewDecoder(decoders...),
|
recognizer.NewDecoder(decoders...),
|
||||||
|
|
@ -92,5 +94,5 @@ func NewStorageCodec(opts StorageCodecConfig) (runtime.Codec, error) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return runtime.NewCodec(encoder, decoder), nil
|
return runtime.NewCodec(encoder, decoder), encodeVersioner, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ type DefaultStorageFactory struct {
|
||||||
APIResourceConfigSource APIResourceConfigSource
|
APIResourceConfigSource APIResourceConfigSource
|
||||||
|
|
||||||
// newStorageCodecFn exists to be overwritten for unit testing.
|
// newStorageCodecFn exists to be overwritten for unit testing.
|
||||||
newStorageCodecFn func(opts StorageCodecConfig) (codec runtime.Codec, err error)
|
newStorageCodecFn func(opts StorageCodecConfig) (codec runtime.Codec, encodeVersioner runtime.GroupVersioner, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type groupResourceOverrides struct {
|
type groupResourceOverrides struct {
|
||||||
|
|
@ -278,7 +278,7 @@ func (s *DefaultStorageFactory) NewConfig(groupResource schema.GroupResource) (*
|
||||||
}
|
}
|
||||||
codecConfig.Config = storageConfig
|
codecConfig.Config = storageConfig
|
||||||
|
|
||||||
storageConfig.Codec, err = s.newStorageCodecFn(codecConfig)
|
storageConfig.Codec, storageConfig.EncodeVersioner, err = s.newStorageCodecFn(codecConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,11 @@ type Config struct {
|
||||||
Paging bool
|
Paging bool
|
||||||
|
|
||||||
Codec runtime.Codec
|
Codec runtime.Codec
|
||||||
|
// EncodeVersioner is the same groupVersioner used to build the
|
||||||
|
// storage encoder. Given a list of kinds the input object might belong
|
||||||
|
// to, the EncodeVersioner outputs the gvk the object will be
|
||||||
|
// converted to before persisted in etcd.
|
||||||
|
EncodeVersioner runtime.GroupVersioner
|
||||||
// Transformer allows the value to be transformed prior to persisting into etcd.
|
// Transformer allows the value to be transformed prior to persisting into etcd.
|
||||||
Transformer value.Transformer
|
Transformer value.Transformer
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue