diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go index a9d8e3f2737..c96f4cb8be5 100644 --- a/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go @@ -20,15 +20,87 @@ import ( "context" "fmt" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" "knative.dev/pkg/apis" + + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" ) // ConvertTo implements apis.Convertible -func (source *InMemoryChannel) ConvertTo(ctx context.Context, sink apis.Convertible) error { - return fmt.Errorf("v1beta1 is the highest known version, got: %T", sink) +// Converts source (from v1beta1.InMemoryChannel) into v1.InMemoryChannel +func (source *InMemoryChannel) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.InMemoryChannel: + sink.ObjectMeta = source.ObjectMeta + if sink.Annotations == nil { + sink.Annotations = make(map[string]string) + } + sink.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1" + source.Status.ConvertTo(ctx, &sink.Status) + return source.Spec.ConvertTo(ctx, &sink.Spec) + default: + return fmt.Errorf("unknown version, got: %T", sink) + } +} + +// ConvertTo helps implement apis.Convertible +func (source *InMemoryChannelSpec) ConvertTo(ctx context.Context, sink *v1.InMemoryChannelSpec) error { + if source.Delivery != nil { + sink.Delivery = &eventingduckv1.DeliverySpec{} + if err := source.Delivery.ConvertTo(ctx, sink.Delivery); err != nil { + return err + } + } + sink.SubscribableSpec = eventingduckv1.SubscribableSpec{} + source.SubscribableSpec.ConvertTo(ctx, &sink.SubscribableSpec) + return nil +} + +// ConvertTo helps implement apis.Convertible +func (source *InMemoryChannelStatus) ConvertTo(ctx context.Context, sink *v1.InMemoryChannelStatus) { + source.Status.ConvertTo(ctx, &sink.Status) + sink.AddressStatus = source.AddressStatus + sink.SubscribableStatus = eventingduckv1.SubscribableStatus{} + source.SubscribableStatus.ConvertTo(ctx, &sink.SubscribableStatus) +} + +// ConvertFrom implements apis.Convertible. +// Converts obj v1.InMemoryChannel into v1beta1.InMemoryChannel +func (sink *InMemoryChannel) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.InMemoryChannel: + sink.ObjectMeta = source.ObjectMeta + sink.Status.ConvertFrom(ctx, source.Status) + sink.Spec.ConvertFrom(ctx, source.Spec) + if sink.Annotations == nil { + sink.Annotations = make(map[string]string) + } + sink.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1beta1" + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} + +// ConvertFrom helps implement apis.Convertible +func (sink *InMemoryChannelSpec) ConvertFrom(ctx context.Context, source v1.InMemoryChannelSpec) error { + if source.Delivery != nil { + sink.Delivery = &eventingduckv1beta1.DeliverySpec{} + if err := sink.Delivery.ConvertFrom(ctx, source.Delivery); err != nil { + return err + } + } + sink.SubscribableSpec = eventingduckv1beta1.SubscribableSpec{} + sink.SubscribableSpec.ConvertFrom(ctx, source.SubscribableSpec) + return nil } -// ConvertFrom implements apis.Convertible -func (sink *InMemoryChannel) ConvertFrom(ctx context.Context, source apis.Convertible) error { - return fmt.Errorf("v1beta1 is the highest known version, got: %T", source) +// ConvertFrom helps implement apis.Convertible +func (sink *InMemoryChannelStatus) ConvertFrom(ctx context.Context, source v1.InMemoryChannelStatus) { + source.Status.ConvertTo(ctx, &sink.Status) + sink.AddressStatus = source.AddressStatus + sink.SubscribableStatus = eventingduckv1beta1.SubscribableStatus{} + sink.SubscribableStatus.ConvertFrom(ctx, source.SubscribableStatus) } diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go index 0ea99b69081..993ad7fdfd2 100644 --- a/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go @@ -19,10 +19,21 @@ package v1beta1 import ( "context" "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) func TestInMemoryChannelConversionBadType(t *testing.T) { - good, bad := &InMemoryChannel{}, &InMemoryChannel{} + good, bad := &InMemoryChannel{}, &Subscription{} if err := good.ConvertTo(context.Background(), bad); err == nil { t.Errorf("ConvertTo() = %#v, wanted error", bad) @@ -32,3 +43,265 @@ func TestInMemoryChannelConversionBadType(t *testing.T) { t.Errorf("ConvertFrom() = %#v, wanted error", good) } } + +// Test v1beta1 -> v1 -> v1beta1 +func TestInMemoryChannelConversion(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.InMemoryChannel{}} + + linear := eventingduck.BackoffPolicyLinear + + tests := []struct { + name string + in *InMemoryChannel + }{{ + name: "min configuration", + in: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: InMemoryChannelSpec{}, + Status: InMemoryChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + }, + }, { + name: "full configuration", + in: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + // DeadLetterSinkURI: apis.HTTP("dlc.reply.example.com"), + Delivery: &eventingduck.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("subscriber.dls.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + Delivery: &eventingduck.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: InMemoryChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduck.SubscribableStatus{ + Subscribers: []eventingduck.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Errorf("ConvertTo() = %v", err) + } + got := &InMemoryChannel{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + // Make sure the annotation specifies the correct duck. + if test.in.Annotations == nil { + test.in.Annotations = make(map[string]string) + } + test.in.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1beta1" + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Errorf("roundtrip (-want, +got) = %v", diff) + } + }) + } + } +} + +// Test v1 -> v1beta1 -> v1 +func TestInMemoryChannelConversionWithV1Beta1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&InMemoryChannel{}} + + linear := eventingduckv1.BackoffPolicyLinear + + tests := []struct { + name string + in *v1.InMemoryChannel + }{{ + name: "min configuration", + in: &v1.InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.InMemoryChannelSpec{}, + Status: v1.InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + }, + }, { + name: "full configuration", + in: &v1.InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.InMemoryChannelSpec{ + ChannelableSpec: eventingduckv1.ChannelableSpec{ + SubscribableSpec: eventingduckv1.SubscribableSpec{ + Subscribers: []eventingduckv1.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + // DeadLetterSinkURI: apis.HTTP("dlc.reply.example.com"), + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("subscriber.dls.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: v1.InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduckv1.SubscribableStatus{ + Subscribers: []eventingduckv1.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := version.ConvertFrom(context.Background(), test.in); err != nil { + t.Errorf("ConvertTo() = %v", err) + } + got := &v1.InMemoryChannel{} + if err := ver.ConvertTo(context.Background(), got); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + // Make sure the annotation specifies the correct duck. + if test.in.Annotations == nil { + test.in.Annotations = make(map[string]string) + } + test.in.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1" + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Errorf("roundtrip (-want, +got) = %v", diff) + } + }) + } + } +}