在Kubernetes源碼分析-- API Server之API Install篇中,我們了解到K8S可以支持多版本的API,但是Rest API的不同版本中接口的輸入輸出參數(shù)的格式是有差別的,Kubernetes是怎么處理這個問題的呢?另外Kubernetes支持yaml、json兩個格式的配置,同時又能夠支持json、yaml和pb等格式的編解碼進(jìn)行協(xié)議傳輸,那么Kubernetes又是如何實(shí)現(xiàn)各種數(shù)據(jù)對象的序列化、反序列化的呢?
runtime.Scheme
為了同時解決數(shù)據(jù)對象的序列化、反序列化與多版本數(shù)據(jù)對象的兼容和轉(zhuǎn)換問題,Kubernetes設(shè)計(jì)了一套復(fù)雜的機(jī)制,首先,它設(shè)計(jì)了runtime.Scheme這個結(jié)構(gòu)體(k8s.io/apimachinery/pkg/runtime/schema.go),以下是對它的定義。
type Scheme struct {
// versionMap allows one to figure out the go type of an object with
// the given version and name.
gvkToType map[schema.GroupVersionKind]reflect.Type
// typeToGroupVersion allows one to find metadata for a given go object.
// The reflect.Type we index by should *not* be a pointer.
typeToGVK map[reflect.Type][]schema.GroupVersionKind
// unversionedTypes are transformed without conversion in ConvertToVersion.
unversionedTypes map[reflect.Type]schema.GroupVersionKind
// unversionedKinds are the names of kinds that can be created in the context of any group
// or version
// TODO: resolve the status of unversioned types.
unversionedKinds map[string]reflect.Type
// Map from version and resource to the corresponding func to convert
// resource field labels in that version to internal version.
fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc
// defaulterFuncs is an array of interfaces to be called with an object to provide defaulting
// the provided object must be a pointer.
defaulterFuncs map[reflect.Type]func(interface{})
// converter stores all registered conversion functions. It also has
// default coverting behavior.
converter *conversion.Converter
}
runtime.Scheme是帶版本的API與帶版本的配置文件的基礎(chǔ)。
runtime.Scheme定義了以下內(nèi)容:
- 序列化和反序列化API對象的方法
- 類型登記處:用于在不同版本的Go shemas之間轉(zhuǎn)換group 、version和kind信息
從上述代碼中可以看到,typeToGVK與gkvToType屬性就是為了解決數(shù)據(jù)對象的序列化與反序列化問題,converter屬性則負(fù)責(zé)不同版本的數(shù)據(jù)對象轉(zhuǎn)換問題;unversionedKind用于映射哪些能夠在任意group和version情況下的類型,key是一個string,也就是kind;fieldLabelConversionFuncs:用于解決數(shù)據(jù)對象的屬性名稱的兼容性轉(zhuǎn)換和檢驗(yàn),比如講需要兼容Pod的spec.Host屬性改為spec.nodeName的情況。
Kubernetes這個設(shè)計(jì)思路簡單方便地建解決多版本的序列化和數(shù)據(jù)轉(zhuǎn)換問題,下面是runtime.Scheme里序列化、反序列化的核心方法New()的代碼:通過查找gkvToType里匹配的諸惡類型,以反射方法生成一個空的數(shù)據(jù)對象:
// New returns a new API object of the given version and name, or an error if it hasn't
// been registered. The version and kind fields must be specified.
func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) {
if t, exists := s.gvkToType[kind]; exists {
return reflect.New(t).Interface().(Object), nil
}
if t, exists := s.unversionedKinds[kind.Kind]; exists {
return reflect.New(t).Interface().(Object), nil
}
return nil, NewNotRegisteredErrForKind(kind)
}
由于這個方法的實(shí)現(xiàn),也讓Scheme實(shí)現(xiàn)了ObjectCreater接口。
注意到runtime.Scheme只是實(shí)現(xiàn)了一個序列化與類型轉(zhuǎn)換的框架API,提供了注冊資源數(shù)據(jù)類型與轉(zhuǎn)換函數(shù)的功能,那么具體的資源數(shù)據(jù)對象、轉(zhuǎn)換函數(shù)又是在哪個包里實(shí)現(xiàn)的呢?答案是k8s.io/api/{resource}/{version}/register.go,而核心資源在k8s.io/api/core/v1/register.go中完成注冊,以核心資源為例說明一下目錄下的關(guān)鍵的代碼
- types.go:k8s.io/api/core/v1/types.go
定義了Rest API接口中設(shè)計(jì)的所有核心的數(shù)據(jù)類型 - register.go 負(fù)責(zé)將types.go里定義的數(shù)據(jù)類型注冊到runtime.Scheme里。
在register.go中,我們看到這里可以看到兩個定義:
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
但是只提供了注冊的方法,其實(shí)還沒有執(zhí)行注冊的過程。什么時候執(zhí)行這個過程呢?
在k8s.io/client-go/kubernetes/scheme/register.go中,有一個 init方法,在引用的時候就會被調(diào)用。
func init() {
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
AddToScheme(Scheme)
}
該方法中的AddToScheme方法,會調(diào)用各個資源、版本的AddToScheme方法,從而把所有的核心數(shù)據(jù)資源都注冊進(jìn)了runtime.Scheme實(shí)例中了。
- generated.proto
基于genclient自動創(chuàng)建的clientsets Go Client,在types.go中,我們可以看到有些對象加了一行注釋:“+genclient=true”,那么這些對象,就會被自動genclient工具自動創(chuàng)建出相應(yīng)的client。 - zz_generated.deepcopy.go
自動創(chuàng)建的資源數(shù)據(jù)對象的DeepCopy方法,實(shí)現(xiàn)資源對象的深度拷貝能力。
Serializer
Serializer是用于對象于序列化格式之間進(jìn)行轉(zhuǎn)的核心接口,它的定義如下所示:
// Encoders write objects to a serialized form
type Encoder interface {
// Encode writes an object to a stream. Implementations may return errors if the versions are
// incompatible, or if no conversion is defined.
Encode(obj Object, w io.Writer) error
}
// Decoders attempt to load an object from data.
type Decoder interface {
// Decode attempts to deserialize the provided data using either the innate typing of the scheme or the
// default kind, group, and version provided. It returns a decoded object as well as the kind, group, and
// version from the serialized data, or an error. If into is non-nil, it will be used as the target type
// and implementations may choose to use it rather than reallocating an object. However, the object is not
// guaranteed to be populated. The returned object is not guaranteed to match into. If defaults are
// provided, they are applied to the data by default. If no defaults or partial defaults are provided, the
// type of the into may be used to guide conversion decisions.
Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
}
// Serializer is the core interface for transforming objects into a serialized format and back.
// Implementations may choose to perform conversion of the object, but no assumptions should be made.
type Serializer interface {
Encoder
Decoder
}
目前系統(tǒng)支持的序列化格式有三種:json、yaml和protobuf??梢詤⒖枷乱还?jié)CodecFactory,這里描述了三種序列化格式的初始化。
- json與yaml序列化
json與yaml序列化的代碼在k8s.io/apimachinary/pkg/runtime/serializer/json/json.go,他們共享一個結(jié)構(gòu),如下所示:
type Serializer struct {
meta MetaFactory
creater runtime.ObjectCreater // 用于創(chuàng)建對應(yīng)Group、版本、kind的資源數(shù)據(jù)對象實(shí)例,一般來說這里會傳入Scheme對象
typer runtime.ObjectTyper // 用于從資源數(shù)據(jù)對象獲取對應(yīng)的Group、Version和Kind,一般來說這里會傳入Scheme對象
yaml bool // 是否yaml
pretty bool // 是否pretty格式,只有josn支持
}
通過成員 yaml來區(qū)分是json還是yaml序列化器;creater和typer成員傳入的都是Scheme對象,前面我們分析過Scheme,它可以讓我們根據(jù)GVK來創(chuàng)建對應(yīng)的實(shí)例,并可以判斷一個對象的GVK,這將在我們后續(xù)的編解碼過程中起到重要作用;而meta成員是一個MetaFactory的對象,它主要用于從序列化數(shù)據(jù)(這里就是json或者yaml)中獲得對象的GroupVersionKind。(下一節(jié)也有說明)
下面我們看看Encode方法,Encode方法比較簡單,只需要按需把對象,轉(zhuǎn)換為json或yaml格式即可。代碼如下所示:
// Encode serializes the provided object to the given writer.
func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
// 看起來針對yaml和pretty需要用到gitlib下面的jsoniter庫來完成
if s.yaml {
json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj)
if err != nil {
return err
}
data, err := yaml.JSONToYAML(json)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
if s.pretty {
data, err := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
// 而針對普通的json序列器,直接通過json.Encoder編碼即可
encoder := json.NewEncoder(w)
return encoder.Encode(obj)
}
Decode比較復(fù)雜,它嘗試把提供的數(shù)據(jù)轉(zhuǎn)換成YAML或者JSON,從中提取出其中存儲的schema、kind,并合并缺省的gvk,然后在數(shù)據(jù)加載到對象中,注意該對象滿足前面指定的schema、kind。或者也可以指定into字段(這時候會加載到into對象中)。
- 如果into為*runtime.Unknown,則不會執(zhí)行解碼操鄒,而直接把裸數(shù)據(jù)提取出來。
- 如果into沒有在typer(也就是Scheme)中注冊,那么對象將會從JSON/YAML中直接解碼(unmarshalling)
- 如果into為nil或者提供的數(shù)據(jù)對象的gvk與into的gvk不同,那么將會基于ObjectCreater.New(gvk)創(chuàng)建出一個新的對象
從上面的分析,可以看出:gvk的計(jì)算優(yōu)先級為:originalData > default gvk > into。具體的代碼如下所示:
func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
if versioned, ok := into.(*runtime.VersionedObjects); ok {
into = versioned.Last()
obj, actual, err := s.Decode(originalData, gvk, into)
if err != nil {
return nil, actual, err
}
versioned.Objects = []runtime.Object{obj}
return versioned, actual, nil
}
data := originalData
if s.yaml {
altered, err := yaml.YAMLToJSON(data)
if err != nil {
return nil, nil, err
}
data = altered
}
actual, err := s.meta.Interpret(data)
if err != nil {
return nil, nil, err
}
if gvk != nil {
*actual = gvkWithDefaults(*actual, *gvk)
}
if unk, ok := into.(*runtime.Unknown); ok && unk != nil {
unk.Raw = originalData
unk.ContentType = runtime.ContentTypeJSON
unk.GetObjectKind().SetGroupVersionKind(*actual)
return unk, actual, nil
}
if into != nil {
_, isUnstructured := into.(runtime.Unstructured)
types, _, err := s.typer.ObjectKinds(into)
switch {
case runtime.IsNotRegisteredError(err), isUnstructured:
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, into); err != nil {
return nil, actual, err
}
return into, actual, nil
case err != nil:
return nil, actual, err
default:
*actual = gvkWithDefaults(*actual, types[0])
}
}
if len(actual.Kind) == 0 {
return nil, actual, runtime.NewMissingKindErr(string(originalData))
}
if len(actual.Version) == 0 {
return nil, actual, runtime.NewMissingVersionErr(string(originalData))
}
// use the target if necessary
obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
if err != nil {
return nil, actual, err
}
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, obj); err != nil {
return nil, actual, err
}
return obj, actual, nil
}
CodecFactory
在這里,我們需要首先說明一下CodecFactory實(shí)現(xiàn)的接口StorageSerializer,StorageSerializer是一個接口,用來獲取encoders,decoders和serailaizers,通過這些序列化器我們可以基于REST來讀寫數(shù)據(jù)。通常被client工具使用,這些客戶端工具用于讀取文件或者持久化restful對象的server存儲接口。
而CodecFactory就是StorageSerializer的實(shí)現(xiàn)類。在BuildStorageFactory中,我們可以看到調(diào)用kubeapiserver.NewStorageFactory方法時傳入了legacyscheme.Codecs到第三個參數(shù)(該參數(shù)類型為runtime.StorageSerializer)。
CodecFactory提供了方法來獲取某個指定版本和內(nèi)容類型的codecs和serializers,它的定義如下所示:
type CodecFactory struct {
scheme *runtime.Scheme
serializers []serializerType
universal runtime.Decoder
accepts []runtime.SerializerInfo
legacySerializer runtime.Serializer
}
NewCodecFactory提供了方法來獲取序列化對象,這些序列化對象為支持的傳輸格式進(jìn)行編解碼,也能為首選的內(nèi)部和外部版本的轉(zhuǎn)換封裝。
在將來,內(nèi)部版本會越來越少使用,調(diào)用者可能會使用缺省序列化器來取而代之,然后只需要轉(zhuǎn)回內(nèi)部共享的對象,如:Status,常用的API對象。
func NewCodecFactory(scheme *runtime.Scheme) CodecFactory {
serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory)
return newCodecFactory(scheme, serializers)
}
這里分為兩個步驟:
- 第一個步驟:生成所有支持的序列化器
這里會采用一些缺省支持的協(xié)議,一般包括:json、yaml、protobuf。從newSerializersForScheme的代碼可以看出。
func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType {
jsonSerializer := json.NewSerializer(mf, scheme, scheme, false)
jsonPrettySerializer := json.NewSerializer(mf, scheme, scheme, true)
yamlSerializer := json.NewYAMLSerializer(mf, scheme, scheme)
serializers := []serializerType{
{
AcceptContentTypes: []string{"application/json"},
ContentType: "application/json",
FileExtensions: []string{"json"},
EncodesAsText: true,
Serializer: jsonSerializer,
PrettySerializer: jsonPrettySerializer,
Framer: json.Framer,
StreamSerializer: jsonSerializer,
},
{
AcceptContentTypes: []string{"application/yaml"},
ContentType: "application/yaml",
FileExtensions: []string{"yaml"},
EncodesAsText: true,
Serializer: yamlSerializer,
},
}
for _, fn := range serializerExtensions {
if serializer, ok := fn(scheme); ok {
serializers = append(serializers, serializer)
}
}
return serializers
}
那么這里沒有看到protobuf的身影,但是上面的代碼中,用到了serializerExtensions,它是一個全局變量,我們在k8s.io/apimachinenry/pkg/runtime/serializer/protobuf_extension.go文件中,找到了它的身影:
func init() {
serializerExtensions = append(serializerExtensions, protobufSerializer)
}
另外一個MetaFactory的作用是什么?
MetaFactory用于從字符串中解析出version和kind數(shù)據(jù)。一般是json數(shù)據(jù)格式的,它有個缺省實(shí)現(xiàn)為DefaultMetaFactory,它的實(shí)現(xiàn)如下:
輸入的二進(jìn)制數(shù)據(jù):{"apiVersion":"apiextensions.k8s.io/v1beta1", "kind": "object"}
那么解析出來的得到:schema.GroupVersionKind{Group:"apiextensions.k8s.io", Version:"v1beta1", Kind: "object"}
- 第二個步驟:生成Codec工廠實(shí)例
newCodeFactory是一個幫助類,用于生成CodecFactory實(shí)例,它的主要功能,基于輸入的序列化對象集合生成以下幾項(xiàng):
1 統(tǒng)一的decode
2 傳統(tǒng)serializer(一般是Json)
3 可接受的編碼類型
然后基于這幾項(xiàng)數(shù)據(jù),生成CodecFactory實(shí)例。
func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory {
decoders := make([]runtime.Decoder, 0, len(serializers))
var accepts []runtime.SerializerInfo
alreadyAccepted := make(map[string]struct{})
var legacySerializer runtime.Serializer
for _, d := range serializers {
decoders = append(decoders, d.Serializer)
for _, mediaType := range d.AcceptContentTypes {
if _, ok := alreadyAccepted[mediaType]; ok {
continue
}
alreadyAccepted[mediaType] = struct{}{}
info := runtime.SerializerInfo{
MediaType: d.ContentType,
EncodesAsText: d.EncodesAsText,
Serializer: d.Serializer,
PrettySerializer: d.PrettySerializer,
}
if d.StreamSerializer != nil {
info.StreamSerializer = &runtime.StreamSerializerInfo{
Serializer: d.StreamSerializer,
EncodesAsText: d.EncodesAsText,
Framer: d.Framer,
}
}
accepts = append(accepts, info)
if mediaType == runtime.ContentTypeJSON {
legacySerializer = d.Serializer
}
}
}
if legacySerializer == nil {
legacySerializer = serializers[0].Serializer
}
return CodecFactory{
scheme: scheme,
serializers: serializers,
universal: recognizer.NewDecoder(decoders...),
accepts: accepts,
legacySerializer: legacySerializer,
}
}
我們先看一下StorageSerializer的定義:
type StorageSerializer interface {
// SupportedMediaTypes are the media types supported for reading and writing objects.
SupportedMediaTypes() []SerializerInfo
// UniversalDeserializer returns a Serializer that can read objects in multiple supported formats
// by introspecting the data at rest.
UniversalDeserializer() Decoder
// 返回一個encoder,該encoder能夠保證寫入底層序列化器的對象是指定的GV
// EncoderForVersion returns an encoder that ensures objects being written to the provided
// serializer are in the provided group version.
EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder
// 返回一個decoder,該decoder能夠保證被底層序列化器的反序列化的對象是指定的GV
// DecoderForVersion returns a decoder that ensures objects being read by the provided
// serializer are in the provided group version by default.
DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder
}
Encoder是一個接口,它負(fù)責(zé)把對象序列化寫入到io中,但是它不關(guān)心具體的對象的GVK,同樣Decoder也是如此,從字節(jié)碼解碼出對象出來。
而K8S在考慮了對多種版本的支撐能力,而它需要在存儲時保證寫入指定的版本,但是在接口層面程序?qū)用嬗帜軌蛑味喾N版本,所以Codec就應(yīng)運(yùn)而生,Codec的定義如下所示:
Codec is a Serializer that deals with the details of versioning objects. It offers the same
// interface as Serializer, so this is a marker to consumers that care about the version of the objects
// they receive.
type Codec Serializer
Codec也是由兩部分組成:編碼器與解碼器,但是它要考慮版本功能,所以StorageSerializer提供了兩個方法EncoderForVersion和DecoderToVersion來生成支持按指定版本進(jìn)行序列化與反序列化的編解碼器。我們來看看CodecFactory的這兩個函數(shù)的具體實(shí)現(xiàn)。
// CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list,
// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not
// converted. If encode or decode are nil, no conversion is performed.
func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec {
// TODO: these are for backcompat, remove them in the future
if encode == nil {
encode = runtime.DisabledGroupVersioner
}
if decode == nil {
decode = runtime.InternalGroupVersioner
}
return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode)
}
// DecoderToVersion returns a decoder that targets the provided group version.
func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return f.CodecForVersions(nil, decoder, nil, gv)
}
// EncoderForVersion returns an encoder that targets the provided group version.
func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return f.CodecForVersions(encoder, nil, gv, nil)
}
從上代碼來看,EncoderForVersion與DecodeToVersion都最終調(diào)用了同樣的方法,如下所示:
func NewDefaultingCodecForScheme(
// TODO: I should be a scheme interface?
scheme *runtime.Scheme,
encoder runtime.Encoder,
decoder runtime.Decoder,
encodeVersion runtime.GroupVersioner,
decodeVersion runtime.GroupVersioner,
) runtime.Codec {
return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion)
}
- 生成對象版本轉(zhuǎn)換實(shí)例
這里我們首先分析一下UnsafeObjectConverter的調(diào)用,它負(fù)責(zé)把scheme變成一個ObjectConvertor接口,ObjectConvertor負(fù)責(zé)把一個對象轉(zhuǎn)換為另外一個不同的版本,對應(yīng)的代碼如下所示:
func UnsafeObjectConvertor(scheme *Scheme) ObjectConvertor {
return unsafeObjectConvertor{scheme}
}
// unsafeObjectConvertor的定義
type unsafeObjectConvertor struct {
*Scheme
}
type ObjectConvertor interface {
// Convert attempts to convert one object into another, or returns an error. This method does
// not guarantee the in object is not mutated. The context argument will be passed to
// all nested conversions.
Convert(in, out, context interface{}) error
// ConvertToVersion takes the provided object and converts it the provided version. This
// method does not guarantee that the in object is not mutated. This method is similar to
// Convert() but handles specific details of choosing the correct output version.
ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
ConvertFieldLabel(version, kind, label, value string) (string, string, error)
}
下面分析一下unsafeObjectObject的具體方法的實(shí)現(xiàn),我們在最終變換版本的時候,是如何實(shí)現(xiàn)的:
// ConvertToVersion converts in to the provided outVersion without copying the input first, which
// is only safe if the output object is not mutated or reused.
func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion GroupVersioner) (Object, error) {
return c.Scheme.UnsafeConvertToVersion(in, outVersion)
}
再次回到Scheme結(jié)構(gòu)負(fù)責(zé)版本轉(zhuǎn)換的方法,這里調(diào)用的UnsafeConvertToVersion方法,Scheme還有一個ConvertToVersion的方法,兩者都是調(diào)用底層的convertToVersion方法, 只是第一個參數(shù)不同。
安全轉(zhuǎn)換在兩種情況下會報錯:目標(biāo)版本不包括inKind類型的資源數(shù)據(jù),轉(zhuǎn)換不能得到有效的對象。
不安全轉(zhuǎn)換輸出的對象不與輸入對象共享字段,會嘗試以最高效率執(zhí)行轉(zhuǎn)換操作。
// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
// but does not guarantee the output object does not share fields with the input object. It attempts to be as
// efficient as possible when doing conversion.
func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) {
return s.convertToVersion(false, in, target)
}
// convertToVersion handles conversion with an optional copy.
func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
var t reflect.Type
if u, ok := in.(Unstructured); ok {
typed, err := s.unstructuredToTyped(u)
if err != nil {
return nil, err
}
in = typed
// unstructuredToTyped returns an Object, which must be a pointer to a struct.
t = reflect.TypeOf(in).Elem() // 從指針轉(zhuǎn)換為一個結(jié)構(gòu)對象
} else {
// determine the incoming kinds with as few allocations as possible.
t = reflect.TypeOf(in) // 只有指針對象才能被轉(zhuǎn)換
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem() // 得到指針的結(jié)構(gòu)對象
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
}
kinds, ok := s.typeToGVK[t] // 得到存儲在scheme中的GVK數(shù)據(jù),可能有多個
if !ok || len(kinds) == 0 {
return nil, NewNotRegisteredErrForType(t)
}
gvk, ok := target.KindForGroupVersionKinds(kinds) // 基于GroupVersioner從源GVKs得到統(tǒng)一的最終目標(biāo)GVK,
// 譬如存儲到ETCD來說,同時希望按照統(tǒng)一的版本來存儲。
if !ok {
// try to see if this type is listed as unversioned (for legacy support)
// TODO: when we move to server API versions, we should completely remove the unversioned concept
if unversionedKind, ok := s.unversionedTypes[t]; ok {
if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
return copyAndSetTargetKind(copy, in, gvk)
}
return copyAndSetTargetKind(copy, in, unversionedKind)
}
return nil, NewNotRegisteredErrForTarget(t, target)
}
// target wants to use the existing type, set kind and return (no conversion necessary)
for _, kind := range kinds { // 如果gvk 是kinds中的一種,那說明版本一致,資源對象結(jié)構(gòu)一致,我們只需要把對象內(nèi)容拷貝出來,并設(shè)置好目標(biāo)GVK即可。
if gvk == kind {
return copyAndSetTargetKind(copy, in, gvk)
}
}
// 如果類型是unversioned,那么沒有轉(zhuǎn)化你的必要
// type is unversioned, no conversion necessary
if unversionedKind, ok := s.unversionedTypes[t]; ok {
if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
return copyAndSetTargetKind(copy, in, gvk)
}
return copyAndSetTargetKind(copy, in, unversionedKind)
}
out, err := s.New(gvk) // 從Scheme中,按照gvk創(chuàng)建出一個對應(yīng)的gvk的對象出來。
if err != nil {
return nil, err
}
// copy 標(biāo)識是需要安全轉(zhuǎn)換,安全轉(zhuǎn)換會執(zhí)行DeepCopy,這樣就不會存在目標(biāo)對象于源對象共享 Field的情況了。
if copy {
in = in.DeepCopyObject()
}
// 類型轉(zhuǎn)換,基于converter成員來完成轉(zhuǎn)換工作,這塊需要專門分析
flags, meta := s.generateConvertMeta(in)
meta.Context = target
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
// 設(shè)置目標(biāo)GVK
setTargetKind(out, gvk)
return out, nil
}
TODO:具體的converter如何實(shí)現(xiàn)不同版本對象的轉(zhuǎn)換,我們分出新的章節(jié)來討論。
- 回到NewDefaultingCodecForScheme函數(shù)中對NewCodec函數(shù)的調(diào)用
前面已經(jīng)分析了runtime.UnsafeObjectConvertor會生成一個ObjectConvetor,它會負(fù)責(zé)把對象轉(zhuǎn)換成我們的目標(biāo)GVK?,F(xiàn)在回到NewCodec方法的內(nèi)容。這里的代碼也就不貼出來了,其實(shí)他就是生成了一個codec實(shí)例。下面是codec結(jié)構(gòu)的定義,它實(shí)現(xiàn)了Codec(Serializer)接口:
type codec struct {
encoder runtime.Encoder
decoder runtime.Decoder
convertor runtime.ObjectConvertor
creater runtime.ObjectCreater
typer runtime.ObjectTyper
defaulter runtime.ObjectDefaulter
encodeVersion runtime.GroupVersioner
decodeVersion runtime.GroupVersioner
}
觀察一下它的Encode方法,如下所示,它會在有必要的情況調(diào)用convertor把源對象轉(zhuǎn)換成目標(biāo)GVK的對象后,在調(diào)用encoder進(jìn)行序列化。
// Encode ensures the provided object is output in the appropriate group and version, invoking
// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is.
func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
switch obj.(type) {
case *runtime.Unknown, runtime.Unstructured:
return c.encoder.Encode(obj, w)
}
gvks, isUnversioned, err := c.typer.ObjectKinds(obj)
if err != nil {
return err
}
if c.encodeVersion == nil || isUnversioned {
if e, ok := obj.(runtime.NestedObjectEncoder); ok {
if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
return err
}
}
objectKind := obj.GetObjectKind()
old := objectKind.GroupVersionKind()
objectKind.SetGroupVersionKind(gvks[0])
err = c.encoder.Encode(obj, w)
objectKind.SetGroupVersionKind(old)
return err
}
// Perform a conversion if necessary
objectKind := obj.GetObjectKind()
old := objectKind.GroupVersionKind()
out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion)
if err != nil {
return err
}
if e, ok := out.(runtime.NestedObjectEncoder); ok {
if err := e.EncodeNestedObjects(DirectEncoder{Version: c.encodeVersion, Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
return err
}
}
// Conversion is responsible for setting the proper group, version, and kind onto the outgoing object
err = c.encoder.Encode(out, w)
// restore the old GVK, in case conversion returned the same object
objectKind.SetGroupVersionKind(old)
return err
}