diff --git a/test/conformance/channel_spec_test.go b/test/conformance/channel_spec_test.go new file mode 100644 index 00000000000..ec137d2a93b --- /dev/null +++ b/test/conformance/channel_spec_test.go @@ -0,0 +1,30 @@ +// +build e2e + +/* +Copyright 2020 The Knative 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 conformance + +import ( + "testing" + + "knative.dev/eventing/test/conformance/helpers" + "knative.dev/eventing/test/lib" +) + +func TestChannelSpec(t *testing.T) { + helpers.ChannelSpecTestHelperWithChannelTestRunner(t, channelTestRunner, lib.SetupClientOptionNoop) +} diff --git a/test/conformance/helpers/channel.go b/test/conformance/helpers/channel.go index 097ea75da78..a574ef4dd53 100644 --- a/test/conformance/helpers/channel.go +++ b/test/conformance/helpers/channel.go @@ -25,12 +25,30 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" eventingduckv1alpha1 "knative.dev/eventing/pkg/apis/duck/v1alpha1" eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + + messagingv1alpha1 "knative.dev/eventing/pkg/apis/messaging/v1alpha1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" ) const ( SubscribableAnnotationKey = "messaging.knative.dev/subscribable" ) +var ( + channelv1alpha1GVK = (&messagingv1alpha1.Channel{}).GetGroupVersionKind() + channelv1beta1GVK = (&messagingv1beta1.Channel{}).GetGroupVersionKind() + + channelv1alpha1 = metav1.TypeMeta{ + Kind: channelv1alpha1GVK.Kind, + APIVersion: channelv1alpha1GVK.GroupVersion().String(), + } + + channelv1beta1 = metav1.TypeMeta{ + Kind: channelv1beta1GVK.Kind, + APIVersion: channelv1beta1GVK.GroupVersion().String(), + } +) + func getChannelDuckTypeSupportVersion(channelName string, client *lib.Client, channel *metav1.TypeMeta) (string, error) { metaResource := resources.NewMetaResource(channelName, client.Namespace, channel) obj, err := duck.GetGenericObject(client.Dynamic, metaResource, &eventingduckv1beta1.Channelable{}) diff --git a/test/conformance/helpers/channel_spec_test_helper.go b/test/conformance/helpers/channel_spec_test_helper.go new file mode 100644 index 00000000000..191714cb872 --- /dev/null +++ b/test/conformance/helpers/channel_spec_test_helper.go @@ -0,0 +1,135 @@ +/* +Copyright 2020 The Knative 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 helpers + +import ( + "testing" + + "encoding/json" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apiserver/pkg/storage/names" + + eventingduckv1alpha1 "knative.dev/eventing/pkg/apis/duck/v1alpha1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/test/lib" + "knative.dev/pkg/apis" +) + +func ChannelSpecTestHelperWithChannelTestRunner( + t *testing.T, + channelTestRunner lib.ChannelTestRunner, + options ...lib.SetupClientOption, +) { + + channelTestRunner.RunTests(t, lib.FeatureBasic, func(st *testing.T, channel metav1.TypeMeta) { + client := lib.Setup(st, true, options...) + defer lib.TearDown(client) + + t.Run("Channel spec allows subscribers", func(t *testing.T) { + if channel == channelv1alpha1 || channel == channelv1beta1 { + t.Skip("Not running spec.subscribers array test for generic Channel") + } + channelSpecAllowsSubscribersArray(st, client, channel) + }) + }) +} + +func channelSpecAllowsSubscribersArray(st *testing.T, client *lib.Client, channel metav1.TypeMeta, options ...lib.SetupClientOption) { + st.Logf("Running channel spec conformance test with channel %s", channel) + + dtsv, err := getChannelDuckTypeSupportVersionFromTypeMeta(client, channel) + if err != nil { + st.Fatalf("Unable to check Channel duck type support version for %s: %s", channel, err) + } + + channelName := names.SimpleNameGenerator.GenerateName("channel-spec-subscribers-") + client.T.Logf("Creating channel %+v-%s", channel, channelName) + client.CreateChannelOrFail(channelName, &channel) + client.WaitForResourceReadyOrFail(channelName, &channel) + + sampleUrl, _ := apis.ParseURL("http://example.com") + gvr, _ := meta.UnsafeGuessKindToResource(channel.GroupVersionKind()) + + var ch interface{} + + if dtsv == "" || dtsv == "v1alpha1" { + // treat missing annotation value as v1alpha1, as written in the spec + channelable, err := getChannelAsV1Alpha1Channelable(channelName, client, channel) + if err != nil { + st.Fatalf("Unable to get channel %s to v1alpha1 duck type: %s", channel, err) + } + + // SPEC: each channel CRD MUST contain an array of subscribers: spec.subscribable.subscribers + channelable.Spec.Subscribable = &eventingduckv1alpha1.Subscribable{ + Subscribers: []eventingduckv1alpha1.SubscriberSpec{ + { + UID: "1234", + ReplyURI: sampleUrl, + }, + }, + } + + ch = channelable + + } else if dtsv == "v1beta1" { + channelable, err := getChannelAsV1Beta1Channelable(channelName, client, channel) + if err != nil { + st.Fatalf("Unable to get channel %s to v1beta1 duck type: %s", channel, err) + } + + // SPEC: each channel CRD MUST contain an array of subscribers: spec.subscribers + channelable.Spec.Subscribers = []eventingduckv1beta1.SubscriberSpec{ + { + UID: "1234", + ReplyURI: sampleUrl, + }, + } + + ch = channelable + } else { + st.Fatalf("Channel doesn't support v1alpha1 nor v1beta1 Channel duck types: %s", channel) + } + + raw, err := json.Marshal(ch) + if err != nil { + st.Fatalf("Error marshaling %s: %s", channel, err) + } + u := &unstructured.Unstructured{} + err = json.Unmarshal(raw, u) + if err != nil { + st.Fatalf("Error unmarshaling %s: %s", u, err) + } + + _, err = client.Dynamic.Resource(gvr).Namespace(client.Namespace).Update(u, metav1.UpdateOptions{}) + if err != nil { + st.Fatalf("Error updating %s with subscribers in spec: %s", channel, err) + } +} + +func getChannelDuckTypeSupportVersionFromTypeMeta(client *lib.Client, channel metav1.TypeMeta) (string, error) { + // the only way is to create one and see + channelName := names.SimpleNameGenerator.GenerateName("channel-tmp-") + + client.T.Logf("Creating channel %+v-%s", channel, channelName) + client.CreateChannelOrFail(channelName, &channel) + client.WaitForResourceReadyOrFail(channelName, &channel) + + return getChannelDuckTypeSupportVersion(channelName, client, &channel) +}