From e4c2c42abb4fab96ecdf91a3f6d0fc0e20be5565 Mon Sep 17 00:00:00 2001 From: Chi Zhang Date: Wed, 28 Aug 2019 11:51:35 -0700 Subject: [PATCH 1/3] add E2E test for defaultchannel --- pkg/defaultchannel/channel_defaulter.go | 10 +- pkg/defaultchannel/channel_defaulter_test.go | 18 +- test/base/resources/constants.go | 1 + test/common/creation.go | 14 +- test/common/typemeta.go | 14 +- test/e2e/channel_defaulter_test.go | 35 ++++ .../helpers/channel_defaulter_test_helper.go | 178 ++++++++++++++++++ test/performance/latency_test.go | 2 +- 8 files changed, 248 insertions(+), 24 deletions(-) create mode 100644 test/e2e/channel_defaulter_test.go create mode 100644 test/e2e/helpers/channel_defaulter_test_helper.go diff --git a/pkg/defaultchannel/channel_defaulter.go b/pkg/defaultchannel/channel_defaulter.go index 87071d65d3b..a028a950615 100644 --- a/pkg/defaultchannel/channel_defaulter.go +++ b/pkg/defaultchannel/channel_defaulter.go @@ -32,12 +32,12 @@ const ( // channel CRD. ConfigMapName = "default-ch-webhook" - // channelDefaulterKey is the key in the ConfigMap to get the name of the default + // ChannelDefaulterKey is the key in the ConfigMap to get the name of the default // Channel CRD. - channelDefaulterKey = "default-ch-config" + ChannelDefaulterKey = "default-ch-config" ) -// Configuration is the data structure serialized to YAML in the config map. When a Channel needs to be +// Config is the data structure serialized to YAML in the config map. When a Channel needs to be // defaulted, the Channel's namespace will be used as a key into NamespaceDefaults, if there is // something present, then that is used. If not, then the ClusterDefault is used. type Config struct { @@ -77,9 +77,9 @@ func (cd *ChannelDefaulter) UpdateConfigMap(cm *corev1.ConfigMap) { cd.logger.Info("UpdateConfigMap on a nil map") return } - defaultChannelConfig, present := cm.Data[channelDefaulterKey] + defaultChannelConfig, present := cm.Data[ChannelDefaulterKey] if !present { - cd.logger.Info("ConfigMap is missing key", zap.String("key", channelDefaulterKey), zap.Any("configMap", cm)) + cd.logger.Info("ConfigMap is missing key", zap.String("key", ChannelDefaulterKey), zap.Any("configMap", cm)) return } diff --git a/pkg/defaultchannel/channel_defaulter_test.go b/pkg/defaultchannel/channel_defaulter_test.go index 85ea1b88b27..a5d861f7e21 100644 --- a/pkg/defaultchannel/channel_defaulter_test.go +++ b/pkg/defaultchannel/channel_defaulter_test.go @@ -119,7 +119,7 @@ func TestChannelDefaulter_UpdateConfigMap(t *testing.T) { "key missing in update": { initialConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: configYaml, + ChannelDefaulterKey: configYaml, }, }, expectedAfterInitial: config.ClusterDefault, @@ -129,13 +129,13 @@ func TestChannelDefaulter_UpdateConfigMap(t *testing.T) { "bad yaml is ignored": { initialConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: configYaml, + ChannelDefaulterKey: configYaml, }, }, expectedAfterInitial: config.ClusterDefault, updatedConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: "foo -> bar", + ChannelDefaulterKey: "foo -> bar", }, }, expectedAfterUpdate: config.ClusterDefault, @@ -143,13 +143,13 @@ func TestChannelDefaulter_UpdateConfigMap(t *testing.T) { "empty config is accepted": { initialConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: configYaml, + ChannelDefaulterKey: configYaml, }, }, expectedAfterInitial: config.ClusterDefault, updatedConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: "{}", + ChannelDefaulterKey: "{}", }, }, expectedAfterUpdate: nil, @@ -157,13 +157,13 @@ func TestChannelDefaulter_UpdateConfigMap(t *testing.T) { "empty string is ignored": { initialConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: configYaml, + ChannelDefaulterKey: configYaml, }, }, expectedAfterInitial: config.ClusterDefault, updatedConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: "", + ChannelDefaulterKey: "", }, }, expectedAfterUpdate: config.ClusterDefault, @@ -171,13 +171,13 @@ func TestChannelDefaulter_UpdateConfigMap(t *testing.T) { "update to same channel": { initialConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: configYaml, + ChannelDefaulterKey: configYaml, }, }, expectedAfterInitial: config.ClusterDefault, updatedConfig: &corev1.ConfigMap{ Data: map[string]string{ - channelDefaulterKey: configYaml, + ChannelDefaulterKey: configYaml, }, }, expectedAfterUpdate: config.ClusterDefault, diff --git a/test/base/resources/constants.go b/test/base/resources/constants.go index 486f44518e1..fd7cef848bc 100644 --- a/test/base/resources/constants.go +++ b/test/base/resources/constants.go @@ -44,6 +44,7 @@ const ( const ( InMemoryChannelKind string = "InMemoryChannel" + ChannelKind string = "Channel" SequenceKind string = "Sequence" ChoiceKind string = "Choice" ) diff --git a/test/common/creation.go b/test/common/creation.go index ad5c6028a0e..4144af839d6 100644 --- a/test/common/creation.go +++ b/test/common/creation.go @@ -36,7 +36,7 @@ var coreAPIVersion = corev1.SchemeGroupVersion.Version var rbacAPIGroup = rbacv1.SchemeGroupVersion.Group var rbacAPIVersion = rbacv1.SchemeGroupVersion.Version -// CreateChannelOrFail will create a Channel Resource in Eventing. +// CreateChannelOrFail will create a typed Channel Resource in Eventing or fail the test if there is an error. func (client *Client) CreateChannelOrFail(name string, channelTypeMeta *metav1.TypeMeta) { namespace := client.Namespace metaResource := resources.NewMetaResource(name, namespace, channelTypeMeta) @@ -47,13 +47,23 @@ func (client *Client) CreateChannelOrFail(name string, channelTypeMeta *metav1.T client.Tracker.Add(gvr.Group, gvr.Version, gvr.Resource, namespace, name) } -// CreateChannelsOrFail will create a list of Channel Resources in Eventing. +// CreateChannelsOrFail will create a list of typed Channel Resources in Eventing or fail the test if there is an error. func (client *Client) CreateChannelsOrFail(names []string, channelTypeMeta *metav1.TypeMeta) { for _, name := range names { client.CreateChannelOrFail(name, channelTypeMeta) } } +// CreateDefaultChannelOrFail will create a default Channel Resource in Eventing or fail the test if there is an error. +func (client *Client) CreateDefaultChannelOrFail(channel *messagingv1alpha1.Channel) { + channels := client.Eventing.MessagingV1alpha1().Channels(client.Namespace) + _, err := channels.Create(channel) + if err != nil { + client.T.Fatalf("Failed to create channel %q: %v", channel.Name, err) + } + client.Tracker.AddObj(channel) +} + // CreateSubscriptionOrFail will create a Subscription or fail the test if there is an error. func (client *Client) CreateSubscriptionOrFail( name, channelName string, diff --git a/test/common/typemeta.go b/test/common/typemeta.go index b50a5413074..4ead0d4905d 100644 --- a/test/common/typemeta.go +++ b/test/common/typemeta.go @@ -55,8 +55,13 @@ func SourcesTypeMeta(kind string) *metav1.TypeMeta { } } -// InMemoryChannelTypeMeta is the TypeMeta ref for InMemoryChannel. -var InMemoryChannelTypeMeta = MessagingTypeMeta(resources.InMemoryChannelKind) +// GetChannelTypeMeta gets the actual typemeta of the typed channel. +func GetChannelTypeMeta(channelKind string) *metav1.TypeMeta { + return MessagingTypeMeta(channelKind) +} + +// ChannelTypeMeta is the TypeMeta ref for Channel. +var ChannelTypeMeta = MessagingTypeMeta(resources.ChannelKind) // SequenceTypeMeta is the TypeMeta ref for Sequence. var SequenceTypeMeta = MessagingTypeMeta(resources.SequenceKind) @@ -71,8 +76,3 @@ func MessagingTypeMeta(kind string) *metav1.TypeMeta { APIVersion: resources.MessagingAPIVersion, } } - -// GetChannelTypeMeta gets the actual typemeta of the channel. -func GetChannelTypeMeta(channelName string) *metav1.TypeMeta { - return MessagingTypeMeta(channelName) -} diff --git a/test/e2e/channel_defaulter_test.go b/test/e2e/channel_defaulter_test.go new file mode 100644 index 00000000000..a636135ed2f --- /dev/null +++ b/test/e2e/channel_defaulter_test.go @@ -0,0 +1,35 @@ +// +build e2e + +/* +Copyright 2019 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 e2e + +import ( + "testing" + + "knative.dev/eventing/test/e2e/helpers" +) + +// TestChannelClusterDefaulter tests a cluster defaulted channel can be created with the template specified through configmap. +func TestChannelClusterDefaulter(t *testing.T) { + helpers.ChannelClusterDefaulterTestHelper(t, channelTestRunner) +} + +// TestChannelNamespaceDefaulter tests a namespace defaulted channel can be created with the template specified through configmap. +func TestChannelNamespaceDefaulter(t *testing.T) { + helpers.ChannelNamespaceDefaulterTestHelper(t, channelTestRunner) +} diff --git a/test/e2e/helpers/channel_defaulter_test_helper.go b/test/e2e/helpers/channel_defaulter_test_helper.go new file mode 100644 index 00000000000..8c7268aedd3 --- /dev/null +++ b/test/e2e/helpers/channel_defaulter_test_helper.go @@ -0,0 +1,178 @@ +/* +Copyright 2019 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 ( + "fmt" + "testing" + + "github.com/ghodss/yaml" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/uuid" + eventingduck "knative.dev/eventing/pkg/apis/duck/v1alpha1" + "knative.dev/eventing/pkg/defaultchannel" + eventingtesting "knative.dev/eventing/pkg/reconciler/testing" + "knative.dev/eventing/test/base" + "knative.dev/eventing/test/base/resources" + "knative.dev/eventing/test/common" +) + +const ( + // configMapName is the name of the ConfigMap that contains the configuration for the default + // channel CRD. + configMapName = defaultchannel.ConfigMapName + + // channelDefaulterKey is the key in the ConfigMap to get the name of the default + // Channel CRD. + channelDefaulterKey = defaultchannel.ChannelDefaulterKey +) + +// ChannelClusterDefaulterTestHelper is the helper function for channel_defaulter_test +func ChannelClusterDefaulterTestHelper(t *testing.T, channelTestRunner common.ChannelTestRunner) { + channelTestRunner.RunTests(t, common.FeatureBasic, func(st *testing.T, channel string) { + // these tests cannot be run in parallel as they have cluster-wide impact + client := common.Setup(st, false) + defer common.TearDown(client) + + if err := updateDefaultChannelCM(client, func(conf *defaultchannel.Config) { + setClusterDefaultChannel(conf, channel) + }); err != nil { + st.Fatalf("Failed to update the defaultchannel configmap: %v", err) + } + + defaultChannelTestHelper(st, client, channel) + }) +} + +// ChannelNamespaceDefaulterTestHelper is the helper function for channel_defaulter_test +func ChannelNamespaceDefaulterTestHelper(t *testing.T, channelTestRunner common.ChannelTestRunner) { + channelTestRunner.RunTests(t, common.FeatureBasic, func(st *testing.T, channel string) { + client := common.Setup(st, true) + defer common.TearDown(client) + + if err := updateDefaultChannelCM(client, func(conf *defaultchannel.Config) { + setNamespaceDefaultChannel(conf, client.Namespace, channel) + }); err != nil { + st.Fatalf("Failed to update the defaultchannel configmap: %v", err) + } + + defaultChannelTestHelper(st, client, channel) + }) +} + +func defaultChannelTestHelper(t *testing.T, client *common.Client, expectedChannelKind string) { + channelName := "e2e-defaulter-channel" + senderName := "e2e-defaulter-sender" + subscriptionName := "e2e-defaulter-subscription" + loggerPodName := "e2e-defaulter-logger-pod" + + // create channel + client.CreateDefaultChannelOrFail(eventingtesting.NewChannel(channelName, client.Namespace)) + + // create logger service as the subscriber + pod := resources.EventLoggerPod(loggerPodName) + client.CreatePodOrFail(pod, common.WithService(loggerPodName)) + + // create subscription to subscribe the channel, and forward the received events to the logger service + client.CreateSubscriptionOrFail( + subscriptionName, + channelName, + common.ChannelTypeMeta, + resources.WithSubscriberForSubscription(loggerPodName), + ) + + // wait for all test resources to be ready, so that we can start sending events + if err := client.WaitForAllTestResourcesReady(); err != nil { + t.Fatalf("Failed to get all test resources ready: %v", err) + } + + // check if the defaultchannel creates exactly one underlying channel given the spec + metaResourceList := resources.NewMetaResourceList(client.Namespace, common.GetChannelTypeMeta(expectedChannelKind)) + objs, err := base.GetGenericObjectList(client.Dynamic, metaResourceList, &eventingduck.SubscribableType{}) + if err != nil { + t.Fatalf("Failed to list the underlying channels: %v", err) + } + if len(objs) != 1 { + t.Fatalf("The defaultchannel is expected to create 1 underlying channel, but got %d", len(objs)) + } + + // send fake CloudEvent to the channel + body := fmt.Sprintf("TestSingleEvent %s", uuid.NewUUID()) + event := &resources.CloudEvent{ + Source: senderName, + Type: resources.CloudEventDefaultType, + Data: fmt.Sprintf(`{"msg":%q}`, body), + Encoding: resources.CloudEventDefaultEncoding, + } + + if err := client.SendFakeEventToAddressable(senderName, channelName, common.ChannelTypeMeta, event); err != nil { + t.Fatalf("Failed to send fake CloudEvent to the channel %q", channelName) + } + + // verify the logger service receives the event + if err := client.CheckLog(loggerPodName, common.CheckerContains(body)); err != nil { + t.Fatalf("String %q not found in logs of logger pod %q: %v", body, loggerPodName, err) + } +} + +// updateDefaultChannelCM will update the default channel configmap +func updateDefaultChannelCM(client *common.Client, updateConfig func(config *defaultchannel.Config)) error { + systemNamespace := resources.SystemNamespace + cmInterface := client.Kube.Kube.CoreV1().ConfigMaps(systemNamespace) + // get the defaultchannel configmap + configMap, err := cmInterface.Get(configMapName, metav1.GetOptions{}) + if err != nil { + return err + } + // get the defaultchannel config value + defaultChannelConfig, hasDefault := configMap.Data[channelDefaulterKey] + config := &defaultchannel.Config{} + if hasDefault { + if err := yaml.Unmarshal([]byte(defaultChannelConfig), config); err != nil { + return err + } + } + + // update the defaultchannel config + updateConfig(config) + configBytes, err := yaml.Marshal(*config) + if err != nil { + return err + } + // update the defaultchannel configmap + configMap.Data[channelDefaulterKey] = string(configBytes) + _, err = cmInterface.Update(configMap) + return err +} + +// setClusterDefaultChannel will set the default channel for cluster-wide +func setClusterDefaultChannel(config *defaultchannel.Config, channel string) { + config.ClusterDefault.TypeMeta = *common.GetChannelTypeMeta(channel) +} + +// setNamespaceDefaultChannel will set the default channel for namespace-wide +func setNamespaceDefaultChannel(config *defaultchannel.Config, namespace, channel string) { + namespaceDefaults := config.NamespaceDefaults + if spec, exists := namespaceDefaults[namespace]; exists { + spec.TypeMeta = *common.GetChannelTypeMeta(channel) + } else { + spec = &eventingduck.ChannelTemplateSpec{ + TypeMeta: *common.GetChannelTypeMeta(channel), + } + namespaceDefaults[namespace] = spec + } +} diff --git a/test/performance/latency_test.go b/test/performance/latency_test.go index 5a8d4db8a6b..17ac933508c 100644 --- a/test/performance/latency_test.go +++ b/test/performance/latency_test.go @@ -32,7 +32,7 @@ import ( ) func TestLatencyForInMemoryBrokerTrigger(t *testing.T) { - testLatencyForBrokerTrigger(t, common.InMemoryChannelTypeMeta) + testLatencyForBrokerTrigger(t, common.GetChannelTypeMeta(resources.InMemoryChannelKind)) } func testLatencyForBrokerTrigger(t *testing.T, channelTypeMeta *metav1.TypeMeta) { From 0bf3ece4b338e101817e19b3075312c61f0e8469 Mon Sep 17 00:00:00 2001 From: Chi Zhang Date: Tue, 3 Sep 2019 12:37:38 -0700 Subject: [PATCH 2/3] address feedbacks --- test/common/creation.go | 4 ++-- test/e2e/helpers/channel_defaulter_test_helper.go | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test/common/creation.go b/test/common/creation.go index 4144af839d6..90f448b13c7 100644 --- a/test/common/creation.go +++ b/test/common/creation.go @@ -54,8 +54,8 @@ func (client *Client) CreateChannelsOrFail(names []string, channelTypeMeta *meta } } -// CreateDefaultChannelOrFail will create a default Channel Resource in Eventing or fail the test if there is an error. -func (client *Client) CreateDefaultChannelOrFail(channel *messagingv1alpha1.Channel) { +// CreateChannelWithDefaultOrFail will create a default Channel Resource in Eventing or fail the test if there is an error. +func (client *Client) CreateChannelWithDefaultOrFail(channel *messagingv1alpha1.Channel) { channels := client.Eventing.MessagingV1alpha1().Channels(client.Namespace) _, err := channels.Create(channel) if err != nil { diff --git a/test/e2e/helpers/channel_defaulter_test_helper.go b/test/e2e/helpers/channel_defaulter_test_helper.go index 8c7268aedd3..99cc581e780 100644 --- a/test/e2e/helpers/channel_defaulter_test_helper.go +++ b/test/e2e/helpers/channel_defaulter_test_helper.go @@ -17,6 +17,7 @@ limitations under the License. package helpers import ( + "context" "fmt" "testing" @@ -29,6 +30,7 @@ import ( "knative.dev/eventing/test/base" "knative.dev/eventing/test/base/resources" "knative.dev/eventing/test/common" + "knative.dev/pkg/logging" ) const ( @@ -61,7 +63,9 @@ func ChannelClusterDefaulterTestHelper(t *testing.T, channelTestRunner common.Ch // ChannelNamespaceDefaulterTestHelper is the helper function for channel_defaulter_test func ChannelNamespaceDefaulterTestHelper(t *testing.T, channelTestRunner common.ChannelTestRunner) { channelTestRunner.RunTests(t, common.FeatureBasic, func(st *testing.T, channel string) { - client := common.Setup(st, true) + // we cannot run these tests in parallel as the updateDefaultChannelCM function is not thread-safe + // TODO(Fredy-Z): make updateDefaultChannelCM thread-safe and run in parallel if the tests are taking too long to finish + client := common.Setup(st, false) defer common.TearDown(client) if err := updateDefaultChannelCM(client, func(conf *defaultchannel.Config) { @@ -81,7 +85,7 @@ func defaultChannelTestHelper(t *testing.T, client *common.Client, expectedChann loggerPodName := "e2e-defaulter-logger-pod" // create channel - client.CreateDefaultChannelOrFail(eventingtesting.NewChannel(channelName, client.Namespace)) + client.CreateChannelWithDefaultOrFail(eventingtesting.NewChannel(channelName, client.Namespace)) // create logger service as the subscriber pod := resources.EventLoggerPod(loggerPodName) @@ -155,8 +159,9 @@ func updateDefaultChannelCM(client *common.Client, updateConfig func(config *def } // update the defaultchannel configmap configMap.Data[channelDefaulterKey] = string(configBytes) - _, err = cmInterface.Update(configMap) - return err + channelDefaulter := defaultchannel.New(logging.FromContext(context.Background()).Desugar()) + channelDefaulter.UpdateConfigMap(configMap) + return nil } // setClusterDefaultChannel will set the default channel for cluster-wide From d31e4c74877e08ae808958340613cb81c5dd3512 Mon Sep 17 00:00:00 2001 From: Chi Zhang Date: Tue, 3 Sep 2019 12:45:48 -0700 Subject: [PATCH 3/3] restore using configmap interface --- test/e2e/helpers/channel_defaulter_test_helper.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/e2e/helpers/channel_defaulter_test_helper.go b/test/e2e/helpers/channel_defaulter_test_helper.go index 99cc581e780..1aa7f8f1628 100644 --- a/test/e2e/helpers/channel_defaulter_test_helper.go +++ b/test/e2e/helpers/channel_defaulter_test_helper.go @@ -17,7 +17,6 @@ limitations under the License. package helpers import ( - "context" "fmt" "testing" @@ -30,7 +29,6 @@ import ( "knative.dev/eventing/test/base" "knative.dev/eventing/test/base/resources" "knative.dev/eventing/test/common" - "knative.dev/pkg/logging" ) const ( @@ -159,8 +157,7 @@ func updateDefaultChannelCM(client *common.Client, updateConfig func(config *def } // update the defaultchannel configmap configMap.Data[channelDefaulterKey] = string(configBytes) - channelDefaulter := defaultchannel.New(logging.FromContext(context.Background()).Desugar()) - channelDefaulter.UpdateConfigMap(configMap) + _, err = cmInterface.Update(configMap) return nil }