diff --git a/pkg/api/conversion_test.go b/pkg/api/conversion_test.go index 0b20f98fdd9..f691d2669f5 100644 --- a/pkg/api/conversion_test.go +++ b/pkg/api/conversion_test.go @@ -50,11 +50,11 @@ func BenchmarkPodConversion(b *testing.B) { var result *api.Pod for i := 0; i < b.N; i++ { pod := &items[i%width] - versionedObj, err := scheme.ConvertToVersion(pod, *testapi.Default.GroupVersion()) + versionedObj, err := scheme.UnsafeConvertToVersion(pod, *testapi.Default.GroupVersion()) if err != nil { b.Fatalf("Conversion error: %v", err) } - obj, err := scheme.ConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()) + obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()) if err != nil { b.Fatalf("Conversion error: %v", err) } @@ -76,11 +76,11 @@ func BenchmarkNodeConversion(b *testing.B) { scheme := api.Scheme var result *api.Node for i := 0; i < b.N; i++ { - versionedObj, err := scheme.ConvertToVersion(&node, *testapi.Default.GroupVersion()) + versionedObj, err := scheme.UnsafeConvertToVersion(&node, *testapi.Default.GroupVersion()) if err != nil { b.Fatalf("Conversion error: %v", err) } - obj, err := scheme.ConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()) + obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()) if err != nil { b.Fatalf("Conversion error: %v", err) } @@ -104,11 +104,11 @@ func BenchmarkReplicationControllerConversion(b *testing.B) { scheme := api.Scheme var result *api.ReplicationController for i := 0; i < b.N; i++ { - versionedObj, err := scheme.ConvertToVersion(&replicationController, *testapi.Default.GroupVersion()) + versionedObj, err := scheme.UnsafeConvertToVersion(&replicationController, *testapi.Default.GroupVersion()) if err != nil { b.Fatalf("Conversion error: %v", err) } - obj, err := scheme.ConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()) + obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion()) if err != nil { b.Fatalf("Conversion error: %v", err) } diff --git a/pkg/runtime/helper.go b/pkg/runtime/helper.go index 8cb4c207d56..4606ddea7cd 100644 --- a/pkg/runtime/helper.go +++ b/pkg/runtime/helper.go @@ -43,10 +43,31 @@ func (t objectTyperToTyper) ObjectKind(obj Object) (*unversioned.GroupVersionKin return &gvk, unversionedType, nil } +// ObjectTyperToTyper casts the old typer interface to the new typer interface func ObjectTyperToTyper(typer ObjectTyper) Typer { return objectTyperToTyper{typer: typer} } +// unsafeObjectConvertor implements ObjectConvertor using the unsafe conversion path. +type unsafeObjectConvertor struct { + *Scheme +} + +var _ ObjectConvertor = unsafeObjectConvertor{} + +// 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 unversioned.GroupVersion) (Object, error) { + return c.Scheme.UnsafeConvertToVersion(in, outVersion) +} + +// UnsafeObjectConvertor performs object conversion without copying the object structure, +// for use when the converted object will not be reused or mutated. Primarily for use within +// versioned codecs, which use the external object for serialization but do not return it. +func UnsafeObjectConvertor(scheme *Scheme) ObjectConvertor { + return unsafeObjectConvertor{scheme} +} + // fieldPtr puts the address of fieldName, which must be a member of v, // into dest, which must be an address of a variable to which this field's // address can be assigned. diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 61537f06046..203d5fa43b7 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -523,11 +523,80 @@ func (s *Scheme) ConvertToVersion(in Object, outVersion unversioned.GroupVersion return out, nil } +// UnsafeConvertToVersion will convert in to the provided outVersion 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, outVersion unversioned.GroupVersion) (Object, error) { + switch t := in.(type) { + case *Unknown: + t.APIVersion = outVersion.String() + return t, nil + case *Unstructured: + t.SetAPIVersion(outVersion.String()) + return t, nil + case *UnstructuredList: + t.SetAPIVersion(outVersion.String()) + return t, nil + } + + // determine the incoming kinds with as few allocations as possible. + t := reflect.TypeOf(in) + if t.Kind() != reflect.Ptr { + return nil, fmt.Errorf("only pointer types may be converted: %v", t) + } + t = t.Elem() + if t.Kind() != reflect.Struct { + return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t) + } + kinds, ok := s.typeToGVK[t] + if !ok || len(kinds) == 0 { + return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outVersion) + } + + // if the Go type is also registered to the destination kind, no conversion is necessary + for i := range kinds { + if kinds[i].Version == outVersion.Version && kinds[i].Group == outVersion.Group { + setTargetKind(in, kinds[i]) + return in, nil + } + } + + // type is unversioned, no conversion necessary + // it should be possible to avoid this allocation + if unversionedKind, ok := s.unversionedTypes[t]; ok { + kind := unversionedKind + outKind := outVersion.WithKind(kind.Kind) + setTargetKind(in, outKind) + return in, nil + } + + // allocate a new object as the target using the target kind + // TODO: this should look in the target group version and find the first kind that matches, rather than the + // first kind registered in typeToGVK + kind := kinds[0] + kind.Version = outVersion.Version + kind.Group = outVersion.Group + out, err := s.New(kind) + if err != nil { + return nil, err + } + + // TODO: try to avoid the allocations here - in fast paths we are not likely to need these flags or meta + flags, meta := s.converter.DefaultMeta(t) + if err := s.converter.Convert(in, out, flags, meta); err != nil { + return nil, err + } + + setTargetKind(out, kind) + return out, nil +} + // generateConvertMeta constructs the meta value we pass to Convert. func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) { return s.converter.DefaultMeta(reflect.TypeOf(in)) } +// setTargetVersion is deprecated and should be replaced by use of setTargetKind func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) { if gv.Version == APIVersionInternal { // internal is a special case @@ -537,3 +606,14 @@ func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) { gvk, _ := raw.ObjectKind(obj) obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind}) } + +// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version. +func setTargetKind(obj Object, kind unversioned.GroupVersionKind) { + if kind.Version == APIVersionInternal { + // internal is a special case + // TODO: look at removing the need to special case this + obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{}) + return + } + obj.GetObjectKind().SetGroupVersionKind(kind) +} diff --git a/pkg/runtime/serializer/versioning/versioning.go b/pkg/runtime/serializer/versioning/versioning.go index 1d0c076d1d5..389c31e4dd3 100644 --- a/pkg/runtime/serializer/versioning/versioning.go +++ b/pkg/runtime/serializer/versioning/versioning.go @@ -71,7 +71,7 @@ func NewCodecForScheme( encodeVersion []unversioned.GroupVersion, decodeVersion []unversioned.GroupVersion, ) runtime.Codec { - return NewCodec(encoder, decoder, scheme, scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion) + return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion) } // NewCodec takes objects in their internal versions and converts them to external versions before @@ -263,21 +263,19 @@ func (c *codec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unv } // Perform a conversion if necessary - if gvk.GroupVersion() != targetGV { - out, err := c.convertor.ConvertToVersion(obj, targetGV) - if err != nil { - if ok { - return err - } - } else { - obj = out - } - } - objectKind := obj.GetObjectKind() old := objectKind.GroupVersionKind() - objectKind.SetGroupVersionKind(unversioned.GroupVersionKind{Group: targetGV.Group, Version: targetGV.Version, Kind: gvk.Kind}) + out, err := c.convertor.ConvertToVersion(obj, targetGV) + if err != nil { + if ok { + return err + } + } else { + obj = out + } + // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object err = c.encoder.EncodeToStream(obj, w, overrides...) + // restore the old GVK, in case conversion returned the same object objectKind.SetGroupVersionKind(old) return err }