From 99fddec90b5249f5246eb2a1793c5e51d0ec96b7 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Fri, 5 Apr 2019 10:13:12 -0700 Subject: [PATCH 01/13] WIP --- cmd/fanoutsidecar/channelwatcher.go | 59 +++ cmd/fanoutsidecar/main.go | 141 ++++--- pkg/provisioners/channel_util.go | 5 +- .../filesystem/filesystem_watcher.go | 126 ------ .../filesystem/filesystem_watcher_test.go | 379 ------------------ pkg/sidecar/configmap/parse.go | 54 --- pkg/sidecar/configmap/parse_test.go | 213 ---------- pkg/sidecar/configmap/watcher/watcher.go | 49 --- pkg/sidecar/configmap/watcher/watcher_test.go | 125 ------ .../multi_channel_fanout_handler.go | 28 +- 10 files changed, 147 insertions(+), 1032 deletions(-) create mode 100644 cmd/fanoutsidecar/channelwatcher.go delete mode 100644 pkg/sidecar/configmap/filesystem/filesystem_watcher.go delete mode 100644 pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go delete mode 100644 pkg/sidecar/configmap/parse.go delete mode 100644 pkg/sidecar/configmap/parse_test.go delete mode 100644 pkg/sidecar/configmap/watcher/watcher.go delete mode 100644 pkg/sidecar/configmap/watcher/watcher_test.go diff --git a/cmd/fanoutsidecar/channelwatcher.go b/cmd/fanoutsidecar/channelwatcher.go new file mode 100644 index 00000000000..d29884e43d7 --- /dev/null +++ b/cmd/fanoutsidecar/channelwatcher.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/logging" + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +type WatchHandlerFunc func(context.Context, client.Client, types.NamespacedName) error + +type reconciler struct { + client client.Client + logger *zap.Logger + handler WatchHandlerFunc +} + +func (r *reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) { + ctx := logging.WithLogger(context.TODO(), r.logger.With(zap.Any("request", req))) + r.logger.Info("New update for channel.") + if err := r.handler(ctx, r.client, req.NamespacedName); err != nil { + r.logger.Error("WatchHandlerFunc returned error", zap.Error(err)) + return reconcile.Result{}, err + } + return reconcile.Result{}, nil +} + +func New(mgr manager.Manager, logger *zap.Logger, watchHandler WatchHandlerFunc) error { + c, err := controller.New("ChannelWatcher", mgr, controller.Options{ + Reconciler: &reconciler{ + client: mgr.GetClient(), + logger: logger, + handler: watchHandler, + }, + }) + if err != nil { + logger.Error("Unable to create controller for channelwatcher.", zap.Error(err)) + return err + } + + // Watch Channels. + err = c.Watch(&source.Kind{ + Type: &v1alpha1.Channel{}, + }, &handler.EnqueueRequestForObject{}) + if err != nil { + logger.Error("Unable to watch Channels.", zap.Error(err), zap.Any("type", &v1alpha1.Channel{})) + return err + } + return nil +} diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 59e8ce8892b..52a71d65b75 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -25,49 +25,46 @@ import ( "fmt" "log" "net/http" - "strings" "time" - "github.com/knative/eventing/pkg/sidecar/configmap/filesystem" - "github.com/knative/eventing/pkg/sidecar/configmap/watcher" + "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/logging" + "github.com/knative/eventing/pkg/sidecar/fanout" + "github.com/knative/eventing/pkg/sidecar/multichannelfanout" "github.com/knative/eventing/pkg/sidecar/swappable" - "github.com/knative/eventing/pkg/utils" - "github.com/knative/pkg/system" "go.uber.org/zap" "go.uber.org/zap/zapcore" - "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" -) - -const ( - defaultConfigMapName = "in-memory-channel-dispatcher-config-map" - // The following are the only valid values of the config_map_noticer flag. - cmnfVolume = "volume" - cmnfWatcher = "watcher" + // uncomment this line to debug in GKE from local machine + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) var ( readTimeout = 1 * time.Minute writeTimeout = 1 * time.Minute - port int - configMapNoticer string - configMapNamespace string - configMapName string + port int + channelProvisioners listFlags ) -func init() { - flag.IntVar(&port, "sidecar_port", -1, "The port to run the sidecar on.") - flag.StringVar(&configMapNoticer, "config_map_noticer", "", fmt.Sprintf("The system to notice changes to the ConfigMap. Valid values are: %s", configMapNoticerValues())) - flag.StringVar(&configMapNamespace, "config_map_namespace", system.Namespace(), "The namespace of the ConfigMap that is watched for configuration.") - flag.StringVar(&configMapName, "config_map_name", defaultConfigMapName, "The name of the ConfigMap that is watched for configuration.") +type listFlags []string + +func (l *listFlags) String() string { + return "" +} +func (l *listFlags) Set(value string) error { + *l = append(*l, value) + return nil } -func configMapNoticerValues() string { - return strings.Join([]string{cmnfVolume, cmnfWatcher}, ", ") +func init() { + flag.IntVar(&port, "sidecar_port", -1, "The port to run the sidecar on.") + flag.Var(&channelProvisioners, "channel_provisioners", "The provisioner of the channels that will be watched.") } func main() { @@ -84,14 +81,18 @@ func main() { logger.Fatal("--sidecar_port flag must be set") } + if len(channelProvisioners) < 1 { + logger.Fatal("--channel_provisioners must be specified") + } + sh, err := swappable.NewEmptyHandler(logger) if err != nil { logger.Fatal("Unable to create swappable.Handler", zap.Error(err)) } - mgr, err := setupConfigMapNoticer(logger, sh.UpdateConfig) + mgr, err := setupChannelWatcher(logger, sh.UpdateConfig) if err != nil { - logger.Fatal("Unable to create configMap noticer.", zap.Error(err)) + logger.Fatal("Unable to create channel watcher.", zap.Error(err)) } s := &http.Server{ @@ -125,57 +126,73 @@ func main() { } } -func setupConfigMapNoticer(logger *zap.Logger, configUpdated swappable.UpdateConfig) (manager.Manager, error) { - mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) - if err != nil { - logger.Error("Error starting manager.", zap.Error(err)) - return nil, err - } - - switch configMapNoticer { - case cmnfVolume: - err = setupConfigMapVolume(logger, mgr, configUpdated) - case cmnfWatcher: - err = setupConfigMapWatcher(logger, mgr, configUpdated) - default: - err = fmt.Errorf("need to provide the --config_map_noticer flag (valid values are %s)", configMapNoticerValues()) - } +func setupChannelWatcher(logger *zap.Logger, configUpdated swappable.UpdateConfig) (manager.Manager, error) { + mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) // TODO: Add scheme if err != nil { + logger.Error("Error creating new maanger.", zap.Error(err)) return nil, err } + v1alpha1.AddToScheme(mgr.GetScheme()) + New(mgr, logger, updateChannelConfig(configUpdated)) return mgr, nil } -func setupConfigMapVolume(logger *zap.Logger, mgr manager.Manager, configUpdated swappable.UpdateConfig) error { - cmn, err := filesystem.NewConfigMapWatcher(logger, filesystem.ConfigDir, configUpdated) - if err != nil { - logger.Error("Unable to create filesystem.ConifgMapWatcher", zap.Error(err)) - return err +func updateChannelConfig(updateConfig swappable.UpdateConfig) WatchHandlerFunc { + return func(ctx context.Context, c client.Client, chanNamespacedName types.NamespacedName) error { + channels, err := listAllChannels(ctx, c) + if err != nil { + logging.FromContext(ctx).Info("Unable to list channels", zap.Error(err)) + return err + } + config := multiChannelFanoutConfig(channels) + return updateConfig(config) } - if err = mgr.Add(cmn); err != nil { - logger.Error("Unable to add the config map watcher", zap.Error(err)) - return err - } - return nil } -func setupConfigMapWatcher(logger *zap.Logger, mgr manager.Manager, configUpdated swappable.UpdateConfig) error { - kc, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return err +func listAllChannels(ctx context.Context, c client.Client) ([]v1alpha1.Channel, error) { + channels := make([]v1alpha1.Channel, 0) + cl := &v1alpha1.ChannelList{} + if err := c.List(ctx, &client.ListOptions{}, cl); err != nil { + return nil, err + } + for _, c := range cl.Items { + if c.Status.IsReady() && shouldWatch(&c) { + channels = append(channels, c) + } } + return channels, nil +} - cmw, err := watcher.NewWatcher(logger, kc, configMapNamespace, configMapName, configUpdated) - if err != nil { - return err +func shouldWatch(ch *v1alpha1.Channel) bool { + if ch.Spec.Provisioner != nil && ch.Spec.Provisioner.Namespace == "" { + for _, v := range channelProvisioners { + if v == ch.Spec.Provisioner.Name { + return true + } + } } + return false +} - if err = mgr.Add(utils.NewBlockingStart(logger, cmw)); err != nil { - logger.Error("Unable to add the config map watcher", zap.Error(err)) - return err +func multiChannelFanoutConfig(channels []v1alpha1.Channel) *multichannelfanout.Config { + cc := make([]multichannelfanout.ChannelConfig, 0) + for _, c := range channels { + channelConfig := multichannelfanout.ChannelConfig{ + Namespace: c.Namespace, + Name: c.Name, + HostName: c.Status.Address.Hostname, + } + if c.Spec.Subscribable != nil { + channelConfig.FanoutConfig = fanout.Config{ + Subscriptions: c.Spec.Subscribable.Subscribers, + } + } + cc = append(cc, channelConfig) + } + return &multichannelfanout.Config{ + ChannelConfigs: cc, } - return nil } // runnableServer is a small wrapper around http.Server so that it matches the manager.Runnable diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index a4261fe8a1e..a6a58011042 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -73,9 +73,8 @@ func CreateK8sService(ctx context.Context, client runtimeClient.Client, c *event func getK8sService(ctx context.Context, client runtimeClient.Client, c *eventingv1alpha1.Channel) (*corev1.Service, error) { list := &corev1.ServiceList{} opts := &runtimeClient.ListOptions{ - Namespace: c.Namespace, - // TODO After the full release start selecting on new set of labels by using k8sServiceLabels(c) - LabelSelector: labels.SelectorFromSet(k8sOldServiceLabels(c)), + Namespace: c.Namespace, + LabelSelector: labels.SelectorFromSet(k8sServiceLabels(c)), // Set Raw because if we need to get more than one page, then we will put the continue token // into opts.Raw.Continue. Raw: &metav1.ListOptions{}, diff --git a/pkg/sidecar/configmap/filesystem/filesystem_watcher.go b/pkg/sidecar/configmap/filesystem/filesystem_watcher.go deleted file mode 100644 index 12f5042d51e..00000000000 --- a/pkg/sidecar/configmap/filesystem/filesystem_watcher.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright 2018 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 filesystem - -import ( - "errors" - - "github.com/fsnotify/fsnotify" - sidecarconfigmap "github.com/knative/eventing/pkg/sidecar/configmap" - "github.com/knative/eventing/pkg/sidecar/multichannelfanout" - "github.com/knative/eventing/pkg/sidecar/swappable" - "github.com/knative/pkg/configmap" - "go.uber.org/zap" -) - -const ( - // ConfigDir is the mount path of the configMap volume. - ConfigDir = "/etc/config/fanout_sidecar" -) - -// Monitors an attached ConfigMap volume for updated configuration and calls `configUpdated` when -// the value changes. -type ConfigMapWatcher struct { - logger *zap.Logger - // The directory to read the configMap from. - dir string - // Stop the watcher by closing this channel. - watcherStopCh chan<- bool - - // The function to call when the configuration is updated. - configUpdated swappable.UpdateConfig -} - -// NewConfigMapWatcher creates a new filesystem.ConfigMapWatcher. The caller is responsible for -// calling Start(<-chan), likely via a controller-runtime Manager. -func NewConfigMapWatcher(logger *zap.Logger, dir string, updateConfig swappable.UpdateConfig) (*ConfigMapWatcher, error) { - conf, err := readConfigMap(logger, dir) - if err != nil { - logger.Error("Unable to read configMap", zap.Error(err)) - return nil, err - } - - logger.Info("Read initial configMap", zap.Any("conf", conf)) - - err = updateConfig(conf) - if err != nil { - logger.Error("Unable to use the initial configMap: %v", zap.Error(err)) - return nil, err - } - - cmw := &ConfigMapWatcher{ - logger: logger, - dir: dir, - configUpdated: updateConfig, - } - return cmw, nil -} - -// readConfigMap attempts to read the configMap from the attached volume. -func readConfigMap(logger *zap.Logger, dir string) (*multichannelfanout.Config, error) { - cm, err := configmap.Load(dir) - if err != nil { - return nil, err - } - return sidecarconfigmap.NewFanoutConfig(logger, cm) -} - -// updateConfig reads the configMap data and calls `configUpdated` with the updated value. -func (cmw *ConfigMapWatcher) updateConfig() { - conf, err := readConfigMap(cmw.logger, cmw.dir) - if err != nil { - cmw.logger.Error("Unable to read the configMap", zap.Error(err)) - return - } - err = cmw.configUpdated(conf) - if err != nil { - cmw.logger.Error("Unable to update config", zap.Error(err)) - return - } -} - -// Start implements controller runtime's manager.Runnable. -func (cmw *ConfigMapWatcher) Start(stopCh <-chan struct{}) error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - - err = watcher.Add(cmw.dir) - if err != nil { - return err - } - - for { - select { - case _, ok := <-watcher.Events: - if !ok { - // Channel closed. - return errors.New("watcher.Events channel closed") - } - cmw.updateConfig() - case e, ok := <-watcher.Errors: - if !ok { - // Channel closed. - return errors.New("watcher.Errors channel closed") - } - cmw.logger.Error("watcher.Errors", zap.Error(e)) - case <-stopCh: - return watcher.Close() - } - } -} diff --git a/pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go b/pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go deleted file mode 100644 index 84a0ac83912..00000000000 --- a/pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go +++ /dev/null @@ -1,379 +0,0 @@ -/* -Copyright 2018 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 filesystem - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "strings" - "sync" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" - "github.com/knative/eventing/pkg/sidecar/configmap" - "github.com/knative/eventing/pkg/sidecar/fanout" - "github.com/knative/eventing/pkg/sidecar/multichannelfanout" - "github.com/knative/eventing/pkg/utils" - "go.uber.org/zap" - yaml "gopkg.in/yaml.v2" -) - -func TestReadConfigMap(t *testing.T) { - testCases := []struct { - name string - createDir bool - config string - expected *multichannelfanout.Config - expectedErr bool - }{ - { - name: "dir does not exist", - createDir: false, - }, - { - name: "no data", - createDir: true, - expectedErr: true, - }, - { - name: "invalid YAML", - createDir: true, - config: ` - key: - - value - - different indent level - `, - expectedErr: true, - }, - { - name: "valid YAML -- invalid JSON", - config: "{ nil: Key }", - createDir: true, - expectedErr: true, - }, - { - name: "unknown field", - config: "{ channelConfigs: [ { not: a-defined-field } ] }", - createDir: true, - expectedErr: true, - }, - { - name: "valid", - createDir: true, - config: ` - channelConfigs: - - namespace: default - name: c1 - fanoutConfig: - subscriptions: - - subscriberURI: event-changer.default.svc.` + utils.GetClusterDomainName() + ` - replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` - - subscriberURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` - - replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` - - namespace: default - name: c2 - fanoutConfig: - subscriptions: - - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` - - namespace: other - name: c3 - fanoutConfig: - subscriptions: - - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName(), - expected: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "c1", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - SubscriberURI: "event-changer.default.svc." + utils.GetClusterDomainName(), - ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), - }, - { - SubscriberURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), - }, - { - ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), - }, - }, - }, - }, - { - Namespace: "default", - Name: "c2", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), - }, - }, - }, - }, - { - Namespace: "other", - Name: "c3", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), - }, - }, - }, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var dir string - if tc.createDir { - var cleanup func() - dir, cleanup = createTempDir(t) - defer cleanup() - } else { - dir = "/tmp/doesNotExist" - } - writeConfigString(t, dir, tc.config) - c, e := readConfigMap(zap.NewNop(), dir) - if tc.expectedErr { - if e == nil { - t.Errorf("Expected an error, actual nil") - } - return - } - if !cmp.Equal(c, tc.expected) { - t.Errorf("Unexpected config. Expected '%v'. Actual '%v'.", tc.expected, c) - } - }) - } -} - -func TestWatch(t *testing.T) { - testCases := map[string]struct { - initialConfigErr error - initialConfig *multichannelfanout.Config - updateConfigErr error - updateConfig *multichannelfanout.Config - }{ - "error applying initial config": { - initialConfig: &multichannelfanout.Config{}, - initialConfigErr: errors.New("test-induced error"), - }, - "read initial config": { - initialConfig: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "c1", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "foo.bar", - }, - }, - }, - }, - }, - }, - }, - "error apply updated config": { - initialConfig: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "c1", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "foo.bar", - }, - }, - }, - }, - }, - }, - updateConfigErr: errors.New("test-induced error"), - }, - "update config": { - initialConfig: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "c1", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "foo.bar", - }, - }, - }, - }, - }, - }, - updateConfig: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "new-channel", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - SubscriberURI: "baz.qux", - }, - }, - }, - }, - }, - }, - }, - } - for n, tc := range testCases { - t.Run(n, func(t *testing.T) { - dir, cleanup := createTempDir(t) - defer cleanup() - writeConfig(t, dir, tc.initialConfig) - - cuc := &configUpdatedChecker{ - updateConfigErr: tc.initialConfigErr, - } - cmw, err := NewConfigMapWatcher(zap.NewNop(), dir, cuc.updateConfig) - if err != nil { - if tc.initialConfigErr != err { - t.Errorf("Unexpected error making ConfigMapWatcher. Expected: '%v'. Actual '%v'", tc.initialConfigErr, err) - } - return - } - ac := cuc.getConfig() - if !cmp.Equal(tc.initialConfig, ac) { - t.Errorf("Unexpected initial config. Expected '%v'. Actual '%v'", tc.initialConfig, ac) - } - - stopCh := make(chan struct{}) - go func() { - _ = cmw.Start(stopCh) - }() - defer func() { - close(stopCh) - }() - // Sadly, the test is flaky unless we sleep here, waiting for the file system - // watcher to truly start. - time.Sleep(100 * time.Millisecond) - - if tc.updateConfigErr != nil { - cuc.updateConfigErr = tc.updateConfigErr - } - - expected := tc.initialConfig - if tc.updateConfig != nil { - expected = tc.updateConfig - } - - cuc.updateCalled = make(chan struct{}, 1) - writeConfig(t, dir, expected) - // The watcher is running in another goroutine, give it some time to notice the - // change. - select { - case <-cuc.updateCalled: - break - case <-time.After(5 * time.Second): - t.Errorf("Time out waiting for watcher to notice change.") - } - - ac = cuc.getConfig() - if !cmp.Equal(ac, expected) { - t.Errorf("Unexpected update config. Expected '%v'. Actual '%v'", expected, ac) - } - }) - } -} - -type configUpdatedChecker struct { - configLock sync.Mutex - config *multichannelfanout.Config - updateCalled chan struct{} - updateConfigErr error -} - -func (cuc *configUpdatedChecker) updateConfig(config *multichannelfanout.Config) error { - cuc.configLock.Lock() - defer cuc.configLock.Unlock() - cuc.config = config - if cuc.updateCalled != nil { - cuc.updateCalled <- struct{}{} - } - return cuc.updateConfigErr -} - -func (cuc *configUpdatedChecker) getConfig() *multichannelfanout.Config { - cuc.configLock.Lock() - defer cuc.configLock.Unlock() - return cuc.config -} - -func createTempDir(t *testing.T) (string, func()) { - dir, err := ioutil.TempDir("", "configMapHandlerTest") - if err != nil { - t.Errorf("Unable to make temp directory: %v", err) - } - return dir, func() { - _ = os.RemoveAll(dir) - } -} - -func writeConfig(t *testing.T, dir string, config *multichannelfanout.Config) { - if config != nil { - yb, err := yaml.Marshal(config) - if err != nil { - t.Errorf("Unable to marshal the config") - } - writeConfigString(t, dir, string(yb)) - } -} - -func writeConfigString(t *testing.T, dir, config string) { - if config != "" { - // Golang editors tend to replace leading spaces with tabs. YAML is left whitespace - // sensitive, so let's replace the tabs with spaces. - leftSpaceConfig := strings.Replace(config, "\t", " ", -1) - err := atomicWriteFile(t, fmt.Sprintf("%s/%s", dir, configmap.MultiChannelFanoutConfigKey), []byte(leftSpaceConfig), 0700) - if err != nil { - t.Errorf("Problem writing the config file: %v", err) - } - } -} - -func atomicWriteFile(t *testing.T, file string, bytes []byte, perm os.FileMode) error { - // In order to more closely replicate how K8s writes ConfigMaps to the file system, we will - // atomically swap out the file by writing it to a temp directory, then renaming it into the - // directory we are watching. - tempDir, cleanup := createTempDir(t) - defer cleanup() - - tempFile := fmt.Sprintf("%s/%s", tempDir, "temp") - err := ioutil.WriteFile(tempFile, bytes, perm) - if err != nil { - return err - } - return os.Rename(tempFile, file) -} diff --git a/pkg/sidecar/configmap/parse.go b/pkg/sidecar/configmap/parse.go deleted file mode 100644 index ba6da64f12c..00000000000 --- a/pkg/sidecar/configmap/parse.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright 2018 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 configmap - -import ( - "encoding/json" - "fmt" - - "github.com/knative/eventing/pkg/sidecar/multichannelfanout" - "go.uber.org/zap" -) - -const ( - // MultiChannelFanoutConfigKey is the key in the ConfigMap that contains all the configuration - // data. - MultiChannelFanoutConfigKey = "multiChannelFanoutConfig" -) - -// NewFanoutConfig attempts to parse the config map's data into a multichannelfanout.Config. -// orig == NewFanoutConfig(SerializeConfig(orig)) -func NewFanoutConfig(logger *zap.Logger, data map[string]string) (*multichannelfanout.Config, error) { - str, present := data[MultiChannelFanoutConfigKey] - if !present { - logger.Error("Expected key not found", zap.String("key", MultiChannelFanoutConfigKey)) - return nil, fmt.Errorf("expected key not found: %v", MultiChannelFanoutConfigKey) - } - return multichannelfanout.Parse(logger, str) -} - -// SerializeConfig takes in a multichannelfanout.Config and generates the ConfigMap equivalent. -// orig == NewFanoutConfig(SerializeConfig(orig)) -func SerializeConfig(config multichannelfanout.Config) (map[string]string, error) { - jb, err := json.Marshal(config) - if err != nil { - return nil, err - } - return map[string]string{ - MultiChannelFanoutConfigKey: string(jb), - }, nil -} diff --git a/pkg/sidecar/configmap/parse_test.go b/pkg/sidecar/configmap/parse_test.go deleted file mode 100644 index cee271ce090..00000000000 --- a/pkg/sidecar/configmap/parse_test.go +++ /dev/null @@ -1,213 +0,0 @@ -/* -Copyright 2018 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 configmap - -import ( - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" - "github.com/knative/eventing/pkg/sidecar/fanout" - "github.com/knative/eventing/pkg/sidecar/multichannelfanout" - "github.com/knative/eventing/pkg/utils" - "go.uber.org/zap" -) - -func TestNewFanoutConfig(t *testing.T) { - testCases := []struct { - name string - config string - expected *multichannelfanout.Config - expectedErr bool - }{ - { - name: "no data", - expectedErr: true, - }, - { - name: "invalid YAML", - config: ` - key: - - value - - different indent level - `, - expectedErr: true, - }, - { - name: "valid YAML -- invalid JSON", - config: "{ nil: Key }", - expectedErr: true, - }, - { - name: "unknown field", - config: "{ channelConfigs: [ { not: a-defined-field } ] }", - expectedErr: true, - }, - { - name: "valid", - config: ` - channelConfigs: - - namespace: default - name: c1 - fanoutConfig: - subscriptions: - - subscriberURI: event-changer.default.svc.` + utils.GetClusterDomainName() + ` - replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` - - subscriberURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` - - replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` - - namespace: default - name: c2 - fanoutConfig: - subscriptions: - - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` - - namespace: other - name: c3 - fanoutConfig: - subscriptions: - - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName(), - expected: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "c1", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - SubscriberURI: "event-changer.default.svc." + utils.GetClusterDomainName(), - ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), - }, - { - SubscriberURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), - }, - { - ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), - }, - }, - }, - }, - { - Namespace: "default", - Name: "c2", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), - }, - }, - }, - }, - { - Namespace: "other", - Name: "c3", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), - }, - }, - }, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - data := formatData(tc.config) - c, e := NewFanoutConfig(zap.NewNop(), data) - if tc.expectedErr { - if e == nil { - t.Errorf("Expected an error, actual nil") - } - return - } - if !cmp.Equal(c, tc.expected) { - t.Errorf("Unexpected config. Expected '%v'. Actual '%v'.", tc.expected, c) - } - }) - } -} - -func TestSerializeConfig(t *testing.T) { - testCases := map[string]struct { - config *multichannelfanout.Config - }{ - "empty config": { - config: &multichannelfanout.Config{}, - }, - "full config": { - config: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "default", - Name: "c1", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - SubscriberURI: "foo.example.com", - ReplyURI: "bar.example.com", - }, - { - ReplyURI: "qux.example.com", - }, - { - SubscriberURI: "baz.example.com", - }, - {}, - }, - }, - }, - { - Namespace: "other", - Name: "no-subs", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{}, - }, - }, - }, - }, - }, - } - - for n, tc := range testCases { - t.Run(n, func(t *testing.T) { - s, err := SerializeConfig(*tc.config) - if err != nil { - t.Errorf("Unexpected error serializing config: %v", err) - } - rt, err := NewFanoutConfig(zap.NewNop(), s) - if err != nil { - t.Errorf("Unexpected error deserializing: %v", err) - } - if diff := cmp.Diff(tc.config, rt); diff != "" { - t.Errorf("Unexpected error roundtripping the config (-want, +got): %v", diff) - } - }) - } -} - -func formatData(config string) map[string]string { - data := make(map[string]string) - if config != "" { - // Golang editors tend to replace leading spaces with tabs. YAML is left whitespace - // sensitive and disallows tabs, so let's replace the tabs with four spaces. - leftSpaceConfig := strings.Replace(config, "\t", " ", -1) - data[MultiChannelFanoutConfigKey] = leftSpaceConfig - } - return data -} diff --git a/pkg/sidecar/configmap/watcher/watcher.go b/pkg/sidecar/configmap/watcher/watcher.go deleted file mode 100644 index 01dc5d7af9a..00000000000 --- a/pkg/sidecar/configmap/watcher/watcher.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2018 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 watcher - -import ( - sidecarconfigmap "github.com/knative/eventing/pkg/sidecar/configmap" - "github.com/knative/eventing/pkg/sidecar/swappable" - "github.com/knative/pkg/configmap" - "go.uber.org/zap" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -// NewWatcher creates a new InformedWatcher that watches the specified ConfigMap and on any change -// that results in a valid multichannelfanout.Config calls configUpdated. -func NewWatcher(logger *zap.Logger, kc kubernetes.Interface, cmNamespace, cmName string, configUpdated swappable.UpdateConfig) (manager.Runnable, error) { - iw := configmap.NewInformedWatcher(kc, cmNamespace) - iw.Watch(cmName, func(cm *corev1.ConfigMap) { - config, err := sidecarconfigmap.NewFanoutConfig(logger, cm.Data) - if err != nil { - logger.Error("Could not parse ConfigMap", zap.Error(err), - zap.Any("configMap.Data", cm.Data)) - return - } - - err = configUpdated(config) - if err != nil { - logger.Error("Unable to update config", zap.Error(err)) - return - } - }) - - return iw, nil -} diff --git a/pkg/sidecar/configmap/watcher/watcher_test.go b/pkg/sidecar/configmap/watcher/watcher_test.go deleted file mode 100644 index 6164c38cd63..00000000000 --- a/pkg/sidecar/configmap/watcher/watcher_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -Copyright 2018 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 watcher - -import ( - "errors" - "testing" - - "github.com/google/go-cmp/cmp" - eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" - sidecarconfigmap "github.com/knative/eventing/pkg/sidecar/configmap" - "github.com/knative/eventing/pkg/sidecar/fanout" - "github.com/knative/eventing/pkg/sidecar/multichannelfanout" - "github.com/knative/pkg/configmap" - "go.uber.org/zap" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - namespace = "test-namespace" - name = "test-name" -) - -func TestReconcile(t *testing.T) { - testCases := map[string]struct { - config map[string]string - updateConfigErr error - expectedConfig *multichannelfanout.Config - }{ - "missing key": { - config: map[string]string{}, - expectedConfig: nil, - }, - "cannot parse cm": { - config: map[string]string{ - sidecarconfigmap.MultiChannelFanoutConfigKey: "invalid config", - }, - expectedConfig: nil, - }, - "configUpdated fails": { - config: map[string]string{ - sidecarconfigmap.MultiChannelFanoutConfigKey: "", - }, - updateConfigErr: errors.New("test-error"), - expectedConfig: &multichannelfanout.Config{}, - }, - "success": { - config: map[string]string{ - sidecarconfigmap.MultiChannelFanoutConfigKey: ` - channelConfigs: - - name: foo - namespace: bar - fanoutConfig: - subscriptions: - - subscriberURI: subscriber - replyURI: reply`, - }, - expectedConfig: &multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Name: "foo", - Namespace: "bar", - FanoutConfig: fanout.Config{ - Subscriptions: []eventingduck.ChannelSubscriberSpec{ - { - SubscriberURI: "subscriber", - ReplyURI: "reply", - }, - }, - }, - }, - }, - }, - }, - } - for n, tc := range testCases { - t.Run(n, func(t *testing.T) { - cuc := &configUpdatedChecker{ - updateConfigErr: tc.updateConfigErr, - } - - r, err := NewWatcher(zap.NewNop(), nil, namespace, name, cuc.updateConfig) - if err != nil { - t.Errorf("Error creating watcher: %v", err) - } - iw := r.(*configmap.InformedWatcher) - iw.OnChange(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Data: tc.config, - }) - - if diff := cmp.Diff(tc.expectedConfig, cuc.config); diff != "" { - t.Errorf("Unexpected config (-want +got): %v", diff) - } - }) - } -} - -type configUpdatedChecker struct { - config *multichannelfanout.Config - updateConfigErr error -} - -func (cuc *configUpdatedChecker) updateConfig(config *multichannelfanout.Config) error { - cuc.config = config - return cuc.updateConfigErr -} diff --git a/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go b/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go index 8872b7026ac..a2f24cbc6d8 100644 --- a/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go +++ b/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go @@ -30,7 +30,6 @@ import ( "net/http" "github.com/google/go-cmp/cmp" - "github.com/knative/eventing/pkg/provisioners" "github.com/knative/eventing/pkg/sidecar/fanout" "go.uber.org/zap" ) @@ -43,29 +42,21 @@ type Config struct { // ChannelConfig is the configuration for a single Channel. type ChannelConfig struct { - Namespace string `json:"namespace"` - Name string `json:"name"` + Namespace string `json:"namespace"` + Name string `json:"name"` + HostName string FanoutConfig fanout.Config `json:"fanoutConfig"` } -// MakeChannelKey creates the key used for this Channel in the Handler's handlers map. -func makeChannelKey(namespace, name string) string { - return fmt.Sprintf("%s/%s", namespace, name) -} - // makeChannelKeyFromConfig creates the channel key for a given channelConfig. It is a helper around // MakeChannelKey. func makeChannelKeyFromConfig(config ChannelConfig) string { - return makeChannelKey(config.Namespace, config.Name) + return config.HostName } // getChannelKey extracts the channel key from the given HTTP request. -func getChannelKey(r *http.Request) (string, error) { - cr, err := provisioners.ParseChannel(r.Host) - if err != nil { - return "", err - } - return makeChannelKey(cr.Namespace, cr.Name), nil +func getChannelKey(r *http.Request) string { + return r.Host } // Handler is an http.Handler that introspects the incoming request to determine what Channel it is @@ -114,12 +105,7 @@ func (h *Handler) CopyWithNewConfig(conf Config) (*Handler, error) { // ServeHTTP delegates the actual handling of the request to a fanout.Handler, based on the // request's channel key. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - channelKey, err := getChannelKey(r) - if err != nil { - h.logger.Error("Unable to extract channelKey", zap.Error(err)) - w.WriteHeader(http.StatusInternalServerError) - return - } + channelKey := getChannelKey(r) fh, ok := h.handlers[channelKey] if !ok { h.logger.Error("Unable to find a handler for request", zap.String("channelKey", channelKey)) From c642ceafe38fcf775d3a65a8794b9a3598b79049 Mon Sep 17 00:00:00 2001 From: akashrv Date: Sat, 6 Apr 2019 10:37:40 -0700 Subject: [PATCH 02/13] WIP - In-memory working with E2E tests --- cmd/fanoutsidecar/main.go | 9 +- .../in-memory-channel/in-memory-channel.yaml | 27 +----- .../pkg/controller/channel/reconcile.go | 14 +-- .../channelwatcher}/channelwatcher.go | 2 +- pkg/provisioners/channel_util.go | 10 +- .../inmemory/channel/controller.go | 19 +--- .../inmemory/channel/reconcile.go | 92 ++----------------- pkg/provisioners/inmemory/controller/main.go | 3 + .../v1alpha1/broker/resources/ingress.go | 6 +- pkg/sidecar/swappable/swappable.go | 4 + test/crd.go | 5 +- 11 files changed, 37 insertions(+), 154 deletions(-) rename {cmd/fanoutsidecar => pkg/channelwatcher}/channelwatcher.go (98%) diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 52a71d65b75..9787fdfaecb 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -28,6 +28,7 @@ import ( "time" "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/channelwatcher" "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/sidecar/fanout" "github.com/knative/eventing/pkg/sidecar/multichannelfanout" @@ -64,7 +65,7 @@ func (l *listFlags) Set(value string) error { func init() { flag.IntVar(&port, "sidecar_port", -1, "The port to run the sidecar on.") - flag.Var(&channelProvisioners, "channel_provisioners", "The provisioner of the channels that will be watched.") + flag.Var(&channelProvisioners, "channel_provisioner", "The provisioner of the channels that will be watched.") } func main() { @@ -127,18 +128,18 @@ func main() { } func setupChannelWatcher(logger *zap.Logger, configUpdated swappable.UpdateConfig) (manager.Manager, error) { - mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) // TODO: Add scheme + mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) if err != nil { logger.Error("Error creating new maanger.", zap.Error(err)) return nil, err } v1alpha1.AddToScheme(mgr.GetScheme()) - New(mgr, logger, updateChannelConfig(configUpdated)) + channelwatcher.New(mgr, logger, updateChannelConfig(configUpdated)) return mgr, nil } -func updateChannelConfig(updateConfig swappable.UpdateConfig) WatchHandlerFunc { +func updateChannelConfig(updateConfig swappable.UpdateConfig) channelwatcher.WatchHandlerFunc { return func(ctx context.Context, c client.Client, chanNamespacedName types.NamespacedName) error { channels, err := listAllChannels(ctx, c) if err != nil { diff --git a/config/provisioners/in-memory-channel/in-memory-channel.yaml b/config/provisioners/in-memory-channel/in-memory-channel.yaml index e0191da4081..cec26e323e3 100644 --- a/config/provisioners/in-memory-channel/in-memory-channel.yaml +++ b/config/provisioners/in-memory-channel/in-memory-channel.yaml @@ -87,8 +87,6 @@ rules: - "" # Core API Group. resources: - configmaps - resourceNames: - - in-memory-channel-dispatcher-config-map verbs: - update - apiGroups: @@ -168,9 +166,10 @@ metadata: name: in-memory-channel-dispatcher rules: - apiGroups: - - "" # Core API group. + - "eventing.knative.dev" resources: - - configmaps + - "channels" + - "channels/status" verbs: - get - list @@ -206,8 +205,6 @@ spec: role: dispatcher template: metadata: - annotations: - sidecar.istio.io/inject: "true" labels: *labels spec: serviceAccountName: in-memory-channel-dispatcher @@ -216,24 +213,10 @@ spec: image: github.com/knative/eventing/cmd/fanoutsidecar args: - --sidecar_port=8080 - - --config_map_noticer=watcher - - --config_map_namespace=knative-eventing - - --config_map_name=in-memory-channel-dispatcher-config-map + - --channel_provisioner=in-memory + - --channel_provisioner=in-memory-channel env: - name: SYSTEM_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - ---- - -# Create the ConfigMap, because if we don't the dispatcher will flap when it first comes online and -# this can cause the integration tests to fail. - -apiVersion: v1 -kind: ConfigMap -metadata: - name: in-memory-channel-dispatcher-config-map - namespace: knative-eventing -data: - multiChannelFanoutConfig: '{}' diff --git a/contrib/gcppubsub/pkg/controller/channel/reconcile.go b/contrib/gcppubsub/pkg/controller/channel/reconcile.go index b446f11de3c..a1836d83e8a 100644 --- a/contrib/gcppubsub/pkg/controller/channel/reconcile.go +++ b/contrib/gcppubsub/pkg/controller/channel/reconcile.go @@ -121,9 +121,6 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err } logging.FromContext(ctx).Info("Reconciling Channel") - // Modify a copy, not the original. - c = c.DeepCopy() - ctx = logging.WithLogger(ctx, logging.FromContext(ctx).With(zap.Any("channel", c))) requeue, reconcileErr := r.reconcile(ctx, c) if reconcileErr != nil { @@ -163,9 +160,8 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) // We are syncing four things: // 1. The K8s Service to talk to this Channel. - // 2. The Istio VirtualService to talk to this Channel. - // 3. The GCP PubSub Topic (one for the Channel). - // 4. The GCP PubSub Subscriptions (one for each Subscriber of the Channel). + // 2. The GCP PubSub Topic (one for the Channel). + // 3. The GCP PubSub Subscriptions (one for each Subscriber of the Channel). // First we will plan all the names out for steps 3 and 4 persist them to status.internal. Then, on a // subsequent reconcile, we manipulate all the GCP resources in steps 3 and 4. @@ -237,12 +233,6 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) return false, err } - err = r.createVirtualService(ctx, c, svc) - if err != nil { - r.recorder.Eventf(c, v1.EventTypeWarning, virtualServiceCreateFailed, "Failed to reconcile Virtual Service for the Channel: %v", err) - return false, err - } - topic, err := r.createTopic(ctx, plannedPCS, gcpCreds) if err != nil { r.recorder.Eventf(c, v1.EventTypeWarning, topicCreateFailed, "Failed to reconcile Topic for the Channel: %v", err) diff --git a/cmd/fanoutsidecar/channelwatcher.go b/pkg/channelwatcher/channelwatcher.go similarity index 98% rename from cmd/fanoutsidecar/channelwatcher.go rename to pkg/channelwatcher/channelwatcher.go index d29884e43d7..1b9f7dcbb2a 100644 --- a/cmd/fanoutsidecar/channelwatcher.go +++ b/pkg/channelwatcher/channelwatcher.go @@ -1,4 +1,4 @@ -package main +package channelwatcher import ( "context" diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index a6a58011042..7df9356cc5e 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -248,6 +248,8 @@ func UpdateChannel(ctx context.Context, client runtimeClient.Client, u *eventing // OwnerReferences on the resource so handleObject can discover the Channel resource that 'owns' it. // As well as being garbage collected when the Channel is deleted. func newK8sService(c *eventingv1alpha1.Channel) *corev1.Service { + // TODO: Need to check if generated name truncates the channel name in case channel name is tool long + // Add annotations return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ GenerateName: channelServiceName(c.ObjectMeta.Name), @@ -262,12 +264,8 @@ func newK8sService(c *eventingv1alpha1.Channel) *corev1.Service { }, }, Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: PortName, - Port: PortNumber, - }, - }, + Type: "ExternalName", + ExternalName: names.ServiceHostName(channelDispatcherServiceName(c.Spec.Provisioner.Name), system.Namespace()), }, } } diff --git a/pkg/provisioners/inmemory/channel/controller.go b/pkg/provisioners/inmemory/channel/controller.go index 7ff6128759a..6edf558e411 100644 --- a/pkg/provisioners/inmemory/channel/controller.go +++ b/pkg/provisioners/inmemory/channel/controller.go @@ -19,10 +19,8 @@ package channel import ( eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3" - "github.com/knative/pkg/system" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -33,18 +31,6 @@ const ( // controllerAgentName is the string used by this controller to identify // itself when creating events. controllerAgentName = "in-memory-channel-controller" - - // ConfigMapName is the name of the ConfigMap in the knative-eventing namespace that contains - // the subscription information for all in-memory Channels. The Provisioner writes to it and the - // Dispatcher reads from it. - ConfigMapName = "in-memory-channel-dispatcher-config-map" -) - -var ( - defaultConfigMapKey = types.NamespacedName{ - Namespace: system.Namespace(), - Name: ConfigMapName, - } ) // ProvideController returns a Controller that represents the in-memory-channel Provisioner. @@ -52,9 +38,8 @@ func ProvideController(mgr manager.Manager, logger *zap.Logger) (controller.Cont // Setup a new controller to Reconcile Channels that belong to this Cluster Provisioner // (in-memory channels). r := &reconciler{ - configMapKey: defaultConfigMapKey, - recorder: mgr.GetRecorder(controllerAgentName), - logger: logger, + recorder: mgr.GetRecorder(controllerAgentName), + logger: logger, } c, err := controller.New(controllerAgentName, mgr, controller.Options{ Reconciler: r, diff --git a/pkg/provisioners/inmemory/channel/reconcile.go b/pkg/provisioners/inmemory/channel/reconcile.go index 53ed753244e..5db47a21063 100644 --- a/pkg/provisioners/inmemory/channel/reconcile.go +++ b/pkg/provisioners/inmemory/channel/reconcile.go @@ -21,7 +21,6 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" @@ -32,7 +31,6 @@ import ( util "github.com/knative/eventing/pkg/provisioners" ccpcontroller "github.com/knative/eventing/pkg/provisioners/inmemory/clusterchannelprovisioner" "github.com/knative/eventing/pkg/reconciler/names" - "github.com/knative/eventing/pkg/sidecar/configmap" "github.com/knative/eventing/pkg/sidecar/fanout" "github.com/knative/eventing/pkg/sidecar/multichannelfanout" ) @@ -53,8 +51,6 @@ type reconciler struct { client client.Client recorder record.EventRecorder logger *zap.Logger - - configMapKey client.ObjectKey } // Verify the struct implements reconcile.Reconciler @@ -93,9 +89,6 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err } logger.Info("Reconciling Channel") - // Modify a copy, not the original. - c = c.DeepCopy() - err = r.reconcile(ctx, c) if err != nil { logger.Info("Error reconciling Channel", zap.Error(err)) @@ -130,16 +123,8 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) // We are syncing three things: // 1. The K8s Service to talk to this Channel. - // 2. The Istio VirtualService to talk to this Channel. // 3. The configuration of all Channel subscriptions. - // We always need to sync the Channel config, so do it first. - if err := r.syncChannelConfig(ctx); err != nil { - logger.Info("Error syncing the Channel config", zap.Error(err)) - r.recorder.Eventf(c, corev1.EventTypeWarning, channelConfigSyncFailed, "Failed to sync Channel config: %v", err) - return err - } - if c.DeletionTimestamp != nil { // K8s garbage collection will delete the K8s service and VirtualService for this channel. // We use a finalizer to ensure the channel config has been synced. @@ -149,89 +134,24 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) util.AddFinalizer(c, finalizerName) + // We use a single dispatcher for both in-memory and in-memory-channel provisioners. + // + originalProvisionerName := c.Spec.Provisioner.Name + c.Spec.Provisioner.Name = defaultProvisionerName svc, err := util.CreateK8sService(ctx, r.client, c) if err != nil { logger.Info("Error creating the Channel's K8s Service", zap.Error(err)) r.recorder.Eventf(c, corev1.EventTypeWarning, k8sServiceCreateFailed, "Failed to reconcile Channel's K8s Service: %v", err) return err } - c.Status.SetAddress(names.ServiceHostName(svc.Name, svc.Namespace)) + c.Spec.Provisioner.Name = originalProvisionerName - if c.Spec.Provisioner.Name == defaultProvisionerName { - _, err = util.CreateVirtualService(ctx, r.client, c, svc) - if err != nil { - logger.Info("Error creating the Virtual Service for the Channel", zap.Error(err)) - r.recorder.Eventf(c, corev1.EventTypeWarning, virtualServiceCreateFailed, "Failed to reconcile Virtual Service for the Channel: %v", err) - return err - } - } else { - // We need to have a single dispatcher that is pointed at by _both_ - // ClusterChannelProvisioners. So fake the channel, by saying that its provisioner is the - // one with the single dispatcher. The faked provisioner is used only to determine the - // dispatcher Service's name. - cCopy := c.DeepCopy() - cCopy.Spec.Provisioner.Name = defaultProvisionerName - _, err = util.CreateVirtualService(ctx, r.client, cCopy, svc) - if err != nil { - logger.Info("Error creating the Virtual Service for the Channel", zap.Error(err)) - r.recorder.Eventf(c, corev1.EventTypeWarning, virtualServiceCreateFailed, "Failed to reconcile Virtual Service for the Channel: %v", err) - return err - } - } + c.Status.SetAddress(names.ServiceHostName(svc.Name, svc.Namespace)) c.Status.MarkProvisioned() return nil } -func (r *reconciler) syncChannelConfig(ctx context.Context) error { - channels, err := r.listAllChannels(ctx) - if err != nil { - r.logger.Info("Unable to list channels", zap.Error(err)) - return err - } - config := multiChannelFanoutConfig(channels) - return r.writeConfigMap(ctx, config) -} - -func (r *reconciler) writeConfigMap(ctx context.Context, config *multichannelfanout.Config) error { - logger := r.logger.With(zap.Any("configMap", r.configMapKey)) - - updated, err := configmap.SerializeConfig(*config) - if err != nil { - r.logger.Error("Unable to serialize config", zap.Error(err), zap.Any("config", config)) - return err - } - - cm := &corev1.ConfigMap{} - err = r.client.Get(ctx, r.configMapKey, cm) - if errors.IsNotFound(err) { - cm = r.createNewConfigMap(updated) - err = r.client.Create(ctx, cm) - } - if err != nil { - logger.Info("Unable to get/create ConfigMap", zap.Error(err)) - return err - } - - if equality.Semantic.DeepEqual(cm.Data, updated) { - // Nothing to update. - return nil - } - - cm.Data = updated - return r.client.Update(ctx, cm) -} - -func (r *reconciler) createNewConfigMap(data map[string]string) *corev1.ConfigMap { - return &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: r.configMapKey.Namespace, - Name: r.configMapKey.Name, - }, - Data: data, - } -} - func multiChannelFanoutConfig(channels []eventingv1alpha1.Channel) *multichannelfanout.Config { cc := make([]multichannelfanout.ChannelConfig, 0) for _, c := range channels { diff --git a/pkg/provisioners/inmemory/controller/main.go b/pkg/provisioners/inmemory/controller/main.go index d8da2d062b4..99ee64f885e 100644 --- a/pkg/provisioners/inmemory/controller/main.go +++ b/pkg/provisioners/inmemory/controller/main.go @@ -29,6 +29,9 @@ import ( "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" + + // uncomment this line to debug in GKE from local machine + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) func main() { diff --git a/pkg/reconciler/v1alpha1/broker/resources/ingress.go b/pkg/reconciler/v1alpha1/broker/resources/ingress.go index f83a991a7b1..f4eb40cd85c 100644 --- a/pkg/reconciler/v1alpha1/broker/resources/ingress.go +++ b/pkg/reconciler/v1alpha1/broker/resources/ingress.go @@ -58,9 +58,9 @@ func MakeIngress(args *IngressArgs) *appsv1.Deployment { Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: ingressLabels(args.Broker), - Annotations: map[string]string{ - "sidecar.istio.io/inject": "true", - }, + // Annotations: map[string]string{ + // "sidecar.istio.io/inject": "true", + // }, }, Spec: corev1.PodSpec{ ServiceAccountName: args.ServiceAccountName, diff --git a/pkg/sidecar/swappable/swappable.go b/pkg/sidecar/swappable/swappable.go index 70de3edab2c..3cff72630df 100644 --- a/pkg/sidecar/swappable/swappable.go +++ b/pkg/sidecar/swappable/swappable.go @@ -24,6 +24,7 @@ package swappable import ( "errors" + "fmt" "net/http" "sync" "sync/atomic" @@ -102,6 +103,9 @@ func (h *Handler) UpdateConfig(config *multichannelfanout.Config) error { // ServeHTTP delegates all HTTP requests to the current multichannelfanout.Handler. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // TODO: delete this debugging code + fmt.Sprintf("Request: %+v", r) + // Hand work off to the current multi channel fanout handler. h.logger.Debug("ServeHTTP request received") h.getMultiChannelFanoutHandler().ServeHTTP(w, r) diff --git a/test/crd.go b/test/crd.go index e4a000b2c59..139b33de079 100644 --- a/test/crd.go +++ b/test/crd.go @@ -164,9 +164,8 @@ func EventSenderPod(name string, namespace string, sink string, event CloudEvent return &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: map[string]string{"sidecar.istio.io/inject": "true"}, + Name: name, + Namespace: namespace, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ From 2a4faae5e4969868b1d2f63b3dd87992b816c784 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Tue, 9 Apr 2019 10:33:56 -0700 Subject: [PATCH 03/13] WIP - remove istio dependency from in-memroy channel --- cmd/broker/ingress/main.go | 17 + .../in-memory-channel/in-memory-channel.yaml | 19 +- .../pkg/controller/channel/reconcile.go | 14 +- pkg/provisioners/channel_util.go | 39 +- .../inmemory/channel/controller.go | 10 - .../inmemory/channel/reconcile.go | 44 +- .../inmemory/channel/reconcile_test.go | 375 +---------------- .../clusterchannelprovisioner/reconcile.go | 12 +- .../reconcile_test.go | 65 ++- pkg/provisioners/provisioner_util.go | 24 +- .../filesystem/filesystem_watcher.go | 126 ++++++ .../filesystem/filesystem_watcher_test.go | 379 ++++++++++++++++++ pkg/sidecar/configmap/parse.go | 54 +++ pkg/sidecar/configmap/parse_test.go | 213 ++++++++++ pkg/sidecar/configmap/watcher/watcher.go | 49 +++ pkg/sidecar/configmap/watcher/watcher_test.go | 125 ++++++ .../multi_channel_fanout_handler_test.go | 49 +-- pkg/sidecar/swappable/swappable_test.go | 24 +- 18 files changed, 1127 insertions(+), 511 deletions(-) create mode 100644 pkg/sidecar/configmap/filesystem/filesystem_watcher.go create mode 100644 pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go create mode 100644 pkg/sidecar/configmap/parse.go create mode 100644 pkg/sidecar/configmap/parse_test.go create mode 100644 pkg/sidecar/configmap/watcher/watcher.go create mode 100644 pkg/sidecar/configmap/watcher/watcher_test.go diff --git a/cmd/broker/ingress/main.go b/cmd/broker/ingress/main.go index ea0094fba73..efafda14aae 100644 --- a/cmd/broker/ingress/main.go +++ b/cmd/broker/ingress/main.go @@ -17,10 +17,12 @@ package main import ( + "bytes" "context" "errors" "flag" "fmt" + "io/ioutil" "log" "net/http" "net/url" @@ -221,6 +223,20 @@ func (h *handler) serveHTTP(ctx context.Context, event cloudevents.Event, resp * } func (h *handler) sendEvent(ctx context.Context, tctx cehttp.TransportContext, event cloudevents.Event) error { + + //url := "http://external-service.knative-eventing.svc.cluster.local" + resp, err1 := http.Post(h.channelURI.String(), "application/json", bytes.NewBuffer([]byte{})) + if err1 != nil { + log.Println("Error:", err1) + } + body, err1 := ioutil.ReadAll(resp.Body) + if err1 != nil { + log.Fatalln(err1) + } + log.Println(fmt.Sprintf("Reponse: %+v", resp)) + log.Println(fmt.Sprintf("ReponseBody from server: %v", string(body))) + + fmt.Println("ChannelURI: ", h.channelURI) sendingCTX := broker.SendingContext(ctx, tctx, h.channelURI) startTS := time.Now() @@ -232,6 +248,7 @@ func (h *handler) sendEvent(ctx context.Context, tctx cehttp.TransportContext, e _, err := h.ceClient.Send(sendingCTX, event) if err != nil { sendingCTX, _ = tag.New(sendingCTX, tag.Insert(TagResult, "error")) + fmt.Println("Error: ", err) } else { sendingCTX, _ = tag.New(sendingCTX, tag.Insert(TagResult, "ok")) } diff --git a/config/provisioners/in-memory-channel/in-memory-channel.yaml b/config/provisioners/in-memory-channel/in-memory-channel.yaml index cec26e323e3..3f466ff5e41 100644 --- a/config/provisioners/in-memory-channel/in-memory-channel.yaml +++ b/config/provisioners/in-memory-channel/in-memory-channel.yaml @@ -62,7 +62,6 @@ rules: - apiGroups: - "" # Core API group. resources: - - configmaps - services verbs: - get @@ -83,22 +82,6 @@ rules: - services verbs: - update - - apiGroups: - - "" # Core API Group. - resources: - - configmaps - verbs: - - update - - apiGroups: - - networking.istio.io - resources: - - virtualservices - verbs: - - get - - list - - watch - - create - - update - apiGroups: - "" # Core API Group. resources: @@ -205,6 +188,8 @@ spec: role: dispatcher template: metadata: + annotations: + sidecar.istio.io/inject: "true" labels: *labels spec: serviceAccountName: in-memory-channel-dispatcher diff --git a/contrib/gcppubsub/pkg/controller/channel/reconcile.go b/contrib/gcppubsub/pkg/controller/channel/reconcile.go index a1836d83e8a..b446f11de3c 100644 --- a/contrib/gcppubsub/pkg/controller/channel/reconcile.go +++ b/contrib/gcppubsub/pkg/controller/channel/reconcile.go @@ -121,6 +121,9 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err } logging.FromContext(ctx).Info("Reconciling Channel") + // Modify a copy, not the original. + c = c.DeepCopy() + ctx = logging.WithLogger(ctx, logging.FromContext(ctx).With(zap.Any("channel", c))) requeue, reconcileErr := r.reconcile(ctx, c) if reconcileErr != nil { @@ -160,8 +163,9 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) // We are syncing four things: // 1. The K8s Service to talk to this Channel. - // 2. The GCP PubSub Topic (one for the Channel). - // 3. The GCP PubSub Subscriptions (one for each Subscriber of the Channel). + // 2. The Istio VirtualService to talk to this Channel. + // 3. The GCP PubSub Topic (one for the Channel). + // 4. The GCP PubSub Subscriptions (one for each Subscriber of the Channel). // First we will plan all the names out for steps 3 and 4 persist them to status.internal. Then, on a // subsequent reconcile, we manipulate all the GCP resources in steps 3 and 4. @@ -233,6 +237,12 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) return false, err } + err = r.createVirtualService(ctx, c, svc) + if err != nil { + r.recorder.Eventf(c, v1.EventTypeWarning, virtualServiceCreateFailed, "Failed to reconcile Virtual Service for the Channel: %v", err) + return false, err + } + topic, err := r.createTopic(ctx, plannedPCS, gcpCreds) if err != nil { r.recorder.Eventf(c, v1.EventTypeWarning, topicCreateFailed, "Failed to reconcile Topic for the Channel: %v", err) diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index 7df9356cc5e..d52581c8116 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -63,11 +63,28 @@ func RemoveFinalizer(o metav1.Object, finalizerName string) { o.SetFinalizers(finalizers.List()) } -func CreateK8sService(ctx context.Context, client runtimeClient.Client, c *eventingv1alpha1.Channel) (*corev1.Service, error) { +type k8sServiceOption func(*corev1.Service) error + +// ExternalService is a functional option for CreateK8sService to create a K8s service of type ExternalName +func ExternalService(c *eventingv1alpha1.Channel) k8sServiceOption { + return func(svc *corev1.Service) error { + svc.Spec = corev1.ServiceSpec{ + Type: "ExternalName", + ExternalName: names.ServiceHostName(channelDispatcherServiceName(c.Spec.Provisioner.Name), system.Namespace()), + } + return nil + } +} + +func CreateK8sService(ctx context.Context, client runtimeClient.Client, c *eventingv1alpha1.Channel, opts ...k8sServiceOption) (*corev1.Service, error) { getSvc := func() (*corev1.Service, error) { return getK8sService(ctx, client, c) } - return createK8sService(ctx, client, getSvc, newK8sService(c)) + svc, err := newK8sService(c, opts...) + if err != nil { + return nil, err + } + return createK8sService(ctx, client, getSvc, svc) } func getK8sService(ctx context.Context, client runtimeClient.Client, c *eventingv1alpha1.Channel) (*corev1.Service, error) { @@ -247,10 +264,10 @@ func UpdateChannel(ctx context.Context, client runtimeClient.Client, u *eventing // newK8sService creates a new Service for a Channel resource. It also sets the appropriate // OwnerReferences on the resource so handleObject can discover the Channel resource that 'owns' it. // As well as being garbage collected when the Channel is deleted. -func newK8sService(c *eventingv1alpha1.Channel) *corev1.Service { +func newK8sService(c *eventingv1alpha1.Channel, opts ...k8sServiceOption) (*corev1.Service, error) { // TODO: Need to check if generated name truncates the channel name in case channel name is tool long // Add annotations - return &corev1.Service{ + svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ GenerateName: channelServiceName(c.ObjectMeta.Name), Namespace: c.Namespace, @@ -264,10 +281,20 @@ func newK8sService(c *eventingv1alpha1.Channel) *corev1.Service { }, }, Spec: corev1.ServiceSpec{ - Type: "ExternalName", - ExternalName: names.ServiceHostName(channelDispatcherServiceName(c.Spec.Provisioner.Name), system.Namespace()), + Ports: []corev1.ServicePort{ + { + Name: PortName, + Port: PortNumber, + }, + }, }, } + for _, opt := range opts { + if err := opt(svc); err != nil { + return nil, err + } + } + return svc, nil } // k8sOldServiceLabels returns a map with only old eventing channel and provisioner labels diff --git a/pkg/provisioners/inmemory/channel/controller.go b/pkg/provisioners/inmemory/channel/controller.go index 6edf558e411..88f0e96233f 100644 --- a/pkg/provisioners/inmemory/channel/controller.go +++ b/pkg/provisioners/inmemory/channel/controller.go @@ -18,7 +18,6 @@ package channel import ( eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -67,14 +66,5 @@ func ProvideController(mgr manager.Manager, logger *zap.Logger) (controller.Cont return nil, err } - // Watch the VirtualServices that are owned by Channels. - err = c.Watch(&source.Kind{ - Type: &istiov1alpha3.VirtualService{}, - }, &handler.EnqueueRequestForOwner{OwnerType: &eventingv1alpha1.Channel{}, IsController: true}) - if err != nil { - logger.Error("Unable to watch VirtualServices.", zap.Error(err)) - return nil, err - } - return c, nil } diff --git a/pkg/provisioners/inmemory/channel/reconcile.go b/pkg/provisioners/inmemory/channel/reconcile.go index 5db47a21063..fffbdc34c64 100644 --- a/pkg/provisioners/inmemory/channel/reconcile.go +++ b/pkg/provisioners/inmemory/channel/reconcile.go @@ -31,18 +31,14 @@ import ( util "github.com/knative/eventing/pkg/provisioners" ccpcontroller "github.com/knative/eventing/pkg/provisioners/inmemory/clusterchannelprovisioner" "github.com/knative/eventing/pkg/reconciler/names" - "github.com/knative/eventing/pkg/sidecar/fanout" - "github.com/knative/eventing/pkg/sidecar/multichannelfanout" ) const ( finalizerName = controllerAgentName // Name of the corev1.Events emitted from the reconciliation process - channelReconciled = "ChannelReconciled" - channelUpdateStatusFailed = "ChannelUpdateStatusFailed" - channelConfigSyncFailed = "ChannelConfigSyncFailed" - k8sServiceCreateFailed = "K8sServiceCreateFailed" - virtualServiceCreateFailed = "VirtualServiceCreateFailed" + channelReconciled = "ChannelReconciled" + channelUpdateStatusFailed = "ChannelUpdateStatusFailed" + k8sServiceCreateFailed = "K8sServiceCreateFailed" // TODO after in-memory-channel is retired, asyncProvisionerName should be removed defaultProvisionerName = "in-memory-channel" ) @@ -126,7 +122,7 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) // 3. The configuration of all Channel subscriptions. if c.DeletionTimestamp != nil { - // K8s garbage collection will delete the K8s service and VirtualService for this channel. + // K8s garbage collection will delete the K8s service for this channel. // We use a finalizer to ensure the channel config has been synced. util.RemoveFinalizer(c, finalizerName) return nil @@ -134,17 +130,12 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) util.AddFinalizer(c, finalizerName) - // We use a single dispatcher for both in-memory and in-memory-channel provisioners. - // - originalProvisionerName := c.Spec.Provisioner.Name - c.Spec.Provisioner.Name = defaultProvisionerName - svc, err := util.CreateK8sService(ctx, r.client, c) + svc, err := util.CreateK8sService(ctx, r.client, c, util.ExternalService(c)) if err != nil { logger.Info("Error creating the Channel's K8s Service", zap.Error(err)) r.recorder.Eventf(c, corev1.EventTypeWarning, k8sServiceCreateFailed, "Failed to reconcile Channel's K8s Service: %v", err) return err } - c.Spec.Provisioner.Name = originalProvisionerName c.Status.SetAddress(names.ServiceHostName(svc.Name, svc.Namespace)) @@ -152,31 +143,6 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) return nil } -func multiChannelFanoutConfig(channels []eventingv1alpha1.Channel) *multichannelfanout.Config { - cc := make([]multichannelfanout.ChannelConfig, 0) - for _, c := range channels { - channelConfig := multichannelfanout.ChannelConfig{ - Namespace: c.Namespace, - Name: c.Name, - } - if c.Spec.Subscribable != nil { - // TODO After in-memory-channel is retired, this logic must be refactored. - asyncHandler := false - if c.Spec.Provisioner.Name != defaultProvisionerName { - asyncHandler = true - } - channelConfig.FanoutConfig = fanout.Config{ - Subscriptions: c.Spec.Subscribable.Subscribers, - AsyncHandler: asyncHandler, - } - } - cc = append(cc, channelConfig) - } - return &multichannelfanout.Config{ - ChannelConfigs: cc, - } -} - func (r *reconciler) listAllChannels(ctx context.Context) ([]eventingv1alpha1.Channel, error) { channels := make([]eventingv1alpha1.Channel, 0) diff --git a/pkg/provisioners/inmemory/channel/reconcile_test.go b/pkg/provisioners/inmemory/channel/reconcile_test.go index 30b9b2ac27b..211f3fc5a03 100644 --- a/pkg/provisioners/inmemory/channel/reconcile_test.go +++ b/pkg/provisioners/inmemory/channel/reconcile_test.go @@ -18,27 +18,25 @@ package channel import ( "context" - "encoding/json" "errors" "fmt" "testing" - "github.com/google/go-cmp/cmp" eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" util "github.com/knative/eventing/pkg/provisioners" + "github.com/knative/eventing/pkg/reconciler/names" controllertesting "github.com/knative/eventing/pkg/reconciler/testing" - "github.com/knative/eventing/pkg/sidecar/configmap" "github.com/knative/eventing/pkg/sidecar/fanout" "github.com/knative/eventing/pkg/sidecar/multichannelfanout" "github.com/knative/eventing/pkg/utils" istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3" + "github.com/knative/pkg/system" _ "github.com/knative/pkg/system/testing" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -56,8 +54,6 @@ const ( cmName = "test-config-map" testErrorMessage = "test induced error" - - insertedByVerifyConfigMapData = "data inserted by verifyConfigMapData so that it can be WantPresent" ) var ( @@ -183,11 +179,9 @@ var ( // map of events to set test cases' expectations easier events = map[string]corev1.Event{ - channelReconciled: {Reason: channelReconciled, Type: corev1.EventTypeNormal}, - channelUpdateStatusFailed: {Reason: channelUpdateStatusFailed, Type: corev1.EventTypeWarning}, - channelConfigSyncFailed: {Reason: channelConfigSyncFailed, Type: corev1.EventTypeWarning}, - k8sServiceCreateFailed: {Reason: k8sServiceCreateFailed, Type: corev1.EventTypeWarning}, - virtualServiceCreateFailed: {Reason: virtualServiceCreateFailed, Type: corev1.EventTypeWarning}, + channelReconciled: {Reason: channelReconciled, Type: corev1.EventTypeNormal}, + channelUpdateStatusFailed: {Reason: channelUpdateStatusFailed, Type: corev1.EventTypeWarning}, + k8sServiceCreateFailed: {Reason: k8sServiceCreateFailed, Type: corev1.EventTypeWarning}, } ) @@ -250,23 +244,6 @@ func TestReconcile(t *testing.T) { makeChannelWithWrongProvisionerName(), }, }, - { - Name: "Channel deleted - Channel config sync fails", - InitialState: []runtime.Object{ - makeDeletingChannel(), - }, - Mocks: controllertesting.Mocks{ - MockLists: errorListingChannels(), - }, - WantPresent: []runtime.Object{ - // Finalizer has not been removed. - makeDeletingChannel(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[channelConfigSyncFailed], - }, - }, { Name: "Channel deleted - finalizer removed", InitialState: []runtime.Object{ @@ -279,64 +256,10 @@ func TestReconcile(t *testing.T) { events[channelReconciled], }, }, - { - Name: "Channel config sync fails - can't list Channels", - InitialState: []runtime.Object{ - makeChannel(), - }, - Mocks: controllertesting.Mocks{ - MockLists: errorListingChannels(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[channelConfigSyncFailed], - }, - }, - { - Name: "Channel config sync fails - can't get ConfigMap", - InitialState: []runtime.Object{ - makeChannel(), - }, - Mocks: controllertesting.Mocks{ - MockGets: errorGettingConfigMap(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[channelConfigSyncFailed], - }, - }, - { - Name: "Channel config sync fails - can't create ConfigMap", - InitialState: []runtime.Object{ - makeChannel(), - }, - Mocks: controllertesting.Mocks{ - MockCreates: errorCreatingConfigMap(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[channelConfigSyncFailed], - }, - }, - { - Name: "Channel config sync fails - can't update ConfigMap", - InitialState: []runtime.Object{ - makeChannel(), - makeConfigMap(), - }, - Mocks: controllertesting.Mocks{ - MockUpdates: errorUpdatingConfigMap(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[channelConfigSyncFailed], - }, - }, { Name: "K8s service get fails", InitialState: []runtime.Object{ makeChannel(), - makeConfigMap(), }, Mocks: controllertesting.Mocks{ MockLists: errorListingK8sService(), @@ -353,7 +276,6 @@ func TestReconcile(t *testing.T) { Name: "K8s service creation fails", InitialState: []runtime.Object{ makeChannel(), - makeConfigMap(), }, Mocks: controllertesting.Mocks{ MockCreates: errorCreatingK8sService(), @@ -367,54 +289,11 @@ func TestReconcile(t *testing.T) { events[k8sServiceCreateFailed], }, }, - { - Name: "Virtual service get fails", - InitialState: []runtime.Object{ - makeChannel(), - makeConfigMap(), - makeK8sService(), - makeVirtualService(), - }, - Mocks: controllertesting.Mocks{ - MockLists: errorListingVirtualService(), - }, - WantPresent: []runtime.Object{ - // TODO: This should have a useful error message saying that the VirtualService - // failed. - makeChannelWithFinalizerAndAddress(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[virtualServiceCreateFailed], - }, - }, - { - Name: "Virtual service creation fails", - InitialState: []runtime.Object{ - makeChannel(), - makeConfigMap(), - makeK8sService(), - }, - Mocks: controllertesting.Mocks{ - MockCreates: errorCreatingVirtualService(), - }, - WantPresent: []runtime.Object{ - // TODO: This should have a useful error message saying that the VirtualService - // failed. - makeChannelWithFinalizerAndAddress(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[virtualServiceCreateFailed], - }, - }, { Name: "Channel get for update fails", InitialState: []runtime.Object{ makeChannel(), - makeConfigMap(), makeK8sService(), - makeVirtualService(), }, Mocks: controllertesting.Mocks{ MockGets: errorOnSecondChannelGet(), @@ -428,9 +307,7 @@ func TestReconcile(t *testing.T) { Name: "Channel update fails", InitialState: []runtime.Object{ makeChannel(), - makeConfigMap(), makeK8sService(), - makeVirtualService(), }, Mocks: controllertesting.Mocks{ MockUpdates: errorUpdatingChannel(), @@ -443,9 +320,7 @@ func TestReconcile(t *testing.T) { Name: "Channel status update fails", InitialState: []runtime.Object{ makeChannel(), - makeConfigMap(), makeK8sService(), - makeVirtualService(), }, Mocks: controllertesting.Mocks{ MockStatusUpdates: errorUpdatingChannelStatus(), @@ -454,83 +329,14 @@ func TestReconcile(t *testing.T) { WantEvent: []corev1.Event{ events[channelReconciled], events[channelUpdateStatusFailed], }, - }, { - Name: "Channel reconcile successful - Channel list follows pagination", - InitialState: []runtime.Object{ - makeChannel(), - makeConfigMap(), - }, - Mocks: controllertesting.Mocks{ - MockLists: (&paginatedChannelsListStruct{channels: channels}).MockLists(), - // This is more accurate to be in WantPresent, but we need to check JSON equality, - // not string equality, so it can't be done in WantPresent. Instead, we verify - // during the update call, swapping out the data and WantPresent with that inserted - // data. - MockUpdates: verifyConfigMapData(channelsConfig), - }, - WantPresent: []runtime.Object{ - makeReadyChannel(), - makeK8sService(), - makeVirtualService(), - makeConfigMapWithVerifyConfigMapData(), - }, - WantEvent: []corev1.Event{ - events[channelReconciled], - }, - }, - { - Name: "Channel reconcile successful - Channel has no subscribers", - InitialState: []runtime.Object{ - makeChannel(), - makeConfigMap(), - }, - Mocks: controllertesting.Mocks{ - MockLists: (&paginatedChannelsListStruct{channels: []eventingv1alpha1.Channel{ - { - ObjectMeta: metav1.ObjectMeta{ - Namespace: "high-consul", - Name: "duarte", - }, - Spec: eventingv1alpha1.ChannelSpec{ - Provisioner: &corev1.ObjectReference{ - Name: ccpName, - }, - }, - }, - }}).MockLists(), - // This is more accurate to be in WantPresent, but we need to check JSON equality, - // not string equality, so it can't be done in WantPresent. Instead, we verify - // during the update call, swapping out the data and WantPresent with that inserted - // data. - MockUpdates: verifyConfigMapData(multichannelfanout.Config{ - ChannelConfigs: []multichannelfanout.ChannelConfig{ - { - Namespace: "high-consul", - Name: "duarte", - }, - }, - }), - }, - WantPresent: []runtime.Object{ - makeReadyChannel(), - makeK8sService(), - makeVirtualService(), - makeConfigMapWithVerifyConfigMapData(), - }, - WantEvent: []corev1.Event{ - events[channelReconciled], - }, }, { Name: "Channel reconcile successful - Async channel", - // VirtualService should have channel provisioner name - // defaults to in-memory-channel but the service should match provisioner's service name InitialState: []runtime.Object{ makeChannel("in-memory"), }, Mocks: controllertesting.Mocks{}, WantPresent: []runtime.Object{ - makeVirtualService(), makeK8sService("in-memory"), }, WantEvent: []corev1.Event{ @@ -539,14 +345,11 @@ func TestReconcile(t *testing.T) { }, { Name: "Channel reconcile successful - Non Async channel", - // VirtualService should have channel provisioner name - // defaults to in-memory-channel InitialState: []runtime.Object{ makeChannel(), }, Mocks: controllertesting.Mocks{}, WantPresent: []runtime.Object{ - makeVirtualService(), makeK8sService(), }, WantEvent: []corev1.Event{ @@ -556,17 +359,12 @@ func TestReconcile(t *testing.T) { } for _, tc := range testCases { - configMapKey := types.NamespacedName{ - Namespace: cmNamespace, - Name: cmName, - } c := tc.GetClient() recorder := tc.GetEventRecorder() r := &reconciler{ - client: c, - recorder: recorder, - logger: zap.NewNop(), - configMapKey: configMapKey, + client: c, + recorder: recorder, + logger: zap.NewNop(), } if tc.ReconcileKey == "" { tc.ReconcileKey = fmt.Sprintf("/%s", cName) @@ -656,26 +454,6 @@ func makeDeletingChannelWithoutFinalizer() *eventingv1alpha1.Channel { return c } -func makeConfigMap() *corev1.ConfigMap { - return &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ConfigMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: cmNamespace, - Name: cmName, - }, - } -} - -func makeConfigMapWithVerifyConfigMapData() *corev1.ConfigMap { - cm := makeConfigMap() - cm.Data = map[string]string{} - cm.Data[configmap.MultiChannelFanoutConfigKey] = insertedByVerifyConfigMapData - return cm -} - func makeK8sService(pn ...string) *corev1.Service { return &corev1.Service{ TypeMeta: metav1.TypeMeta{ @@ -703,60 +481,8 @@ func makeK8sService(pn ...string) *corev1.Service { }, }, Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: util.PortName, - Port: util.PortNumber, - }, - }, - }, - } -} - -func makeVirtualService() *istiov1alpha3.VirtualService { - return &istiov1alpha3.VirtualService{ - TypeMeta: metav1.TypeMeta{ - APIVersion: istiov1alpha3.SchemeGroupVersion.String(), - Kind: "VirtualService", - }, - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("%s-channel-", cName), - Namespace: cNamespace, - Labels: map[string]string{ - util.EventingChannelLabel: cName, - util.OldEventingChannelLabel: cName, - util.EventingProvisionerLabel: ccpName, - util.OldEventingProvisionerLabel: ccpName, - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), - Kind: "Channel", - Name: cName, - UID: cUID, - Controller: &truePointer, - BlockOwnerDeletion: &truePointer, - }, - }, - }, - Spec: istiov1alpha3.VirtualServiceSpec{ - Hosts: []string{ - serviceAddress, - fmt.Sprintf("%s.%s.channels.%s", cName, cNamespace, utils.GetClusterDomainName()), - }, - HTTP: []istiov1alpha3.HTTPRoute{{ - Rewrite: &istiov1alpha3.HTTPRewrite{ - Authority: fmt.Sprintf("%s.%s.channels.%s", cName, cNamespace, utils.GetClusterDomainName()), - }, - Route: []istiov1alpha3.DestinationWeight{{ - Destination: istiov1alpha3.Destination{ - Host: "in-memory-channel-dispatcher.knative-testing.svc." + utils.GetClusterDomainName(), - Port: istiov1alpha3.PortSelector{ - Number: util.PortNumber, - }, - }}, - }}, - }, + ExternalName: names.ServiceHostName(fmt.Sprintf("%s-dispatcher", getProvisionerName(pn)), system.Namespace()), + Type: "ExternalName", }, } } @@ -780,18 +506,6 @@ func errorGettingChannel() []controllertesting.MockGet { }, } } - -func errorGettingConfigMap() []controllertesting.MockGet { - return []controllertesting.MockGet{ - func(_ client.Client, _ context.Context, _ client.ObjectKey, obj runtime.Object) (controllertesting.MockHandled, error) { - if _, ok := obj.(*corev1.ConfigMap); ok { - return controllertesting.Handled, errors.New(testErrorMessage) - } - return controllertesting.Unhandled, nil - }, - } -} - func errorListingK8sService() []controllertesting.MockList { return []controllertesting.MockList{ func(_ client.Client, _ context.Context, _ *client.ListOptions, obj runtime.Object) (controllertesting.MockHandled, error) { @@ -803,17 +517,6 @@ func errorListingK8sService() []controllertesting.MockList { } } -func errorListingVirtualService() []controllertesting.MockList { - return []controllertesting.MockList{ - func(_ client.Client, _ context.Context, _ *client.ListOptions, obj runtime.Object) (controllertesting.MockHandled, error) { - if _, ok := obj.(*istiov1alpha3.VirtualServiceList); ok { - return controllertesting.Handled, errors.New(testErrorMessage) - } - return controllertesting.Unhandled, nil - }, - } -} - func errorListingChannels() []controllertesting.MockList { return []controllertesting.MockList{ func(client.Client, context.Context, *client.ListOptions, runtime.Object) (controllertesting.MockHandled, error) { @@ -822,17 +525,6 @@ func errorListingChannels() []controllertesting.MockList { } } -func errorCreatingConfigMap() []controllertesting.MockCreate { - return []controllertesting.MockCreate{ - func(_ client.Client, _ context.Context, obj runtime.Object) (controllertesting.MockHandled, error) { - if _, ok := obj.(*corev1.ConfigMap); ok { - return controllertesting.Handled, errors.New(testErrorMessage) - } - return controllertesting.Unhandled, nil - }, - } -} - func errorCreatingK8sService() []controllertesting.MockCreate { return []controllertesting.MockCreate{ func(_ client.Client, _ context.Context, obj runtime.Object) (controllertesting.MockHandled, error) { @@ -844,17 +536,6 @@ func errorCreatingK8sService() []controllertesting.MockCreate { } } -func errorCreatingVirtualService() []controllertesting.MockCreate { - return []controllertesting.MockCreate{ - func(_ client.Client, _ context.Context, obj runtime.Object) (controllertesting.MockHandled, error) { - if _, ok := obj.(*istiov1alpha3.VirtualService); ok { - return controllertesting.Handled, errors.New(testErrorMessage) - } - return controllertesting.Unhandled, nil - }, - } -} - func errorUpdatingChannel() []controllertesting.MockUpdate { return []controllertesting.MockUpdate{ func(_ client.Client, _ context.Context, obj runtime.Object) (controllertesting.MockHandled, error) { @@ -877,17 +558,6 @@ func errorUpdatingChannelStatus() []controllertesting.MockStatusUpdate { } } -func errorUpdatingConfigMap() []controllertesting.MockUpdate { - return []controllertesting.MockUpdate{ - func(_ client.Client, _ context.Context, obj runtime.Object) (controllertesting.MockHandled, error) { - if _, ok := obj.(*corev1.ConfigMap); ok { - return controllertesting.Handled, errors.New(testErrorMessage) - } - return controllertesting.Unhandled, nil - }, - } -} - type paginatedChannelsListStruct struct { channels []eventingv1alpha1.Channel } @@ -911,28 +581,3 @@ func (p *paginatedChannelsListStruct) MockLists() []controllertesting.MockList { }, } } - -func verifyConfigMapData(expected multichannelfanout.Config) []controllertesting.MockUpdate { - return []controllertesting.MockUpdate{ - func(innerClient client.Client, ctx context.Context, obj runtime.Object) (controllertesting.MockHandled, error) { - if cm, ok := obj.(*corev1.ConfigMap); ok { - s := cm.Data[configmap.MultiChannelFanoutConfigKey] - c := multichannelfanout.Config{} - err := json.Unmarshal([]byte(s), &c) - if err != nil { - return controllertesting.Handled, - fmt.Errorf("test is unable to unmarshal ConfigMap data: %v", err) - } - if diff := cmp.Diff(c, expected); diff != "" { - return controllertesting.Handled, - fmt.Errorf("test got unwanted ChannelsConfig (-want +got) %s", diff) - } - // Verified it is correct, now so that we can verify this actually occurred, swap - // out the data with a known value for later comparison. - cm.Data[configmap.MultiChannelFanoutConfigKey] = insertedByVerifyConfigMapData - return controllertesting.Handled, innerClient.Update(ctx, obj) - } - return controllertesting.Unhandled, nil - }, - } -} diff --git a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go index 5e79fc3c802..5794b2dbafc 100644 --- a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go +++ b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go @@ -22,6 +22,7 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -153,7 +154,7 @@ func (r *reconciler) reconcile(ctx context.Context, ccp *eventingv1alpha1.Cluste return nil } - svc, err := util.CreateDispatcherService(ctx, r.client, ccp) + svc, err := util.CreateDispatcherService(ctx, r.client, ccp, setDispatcherServiceSelector()) if err != nil { logger.Info("Error creating the ClusterChannelProvisioner's K8s Service", zap.Error(err)) @@ -179,6 +180,15 @@ func (r *reconciler) reconcile(ctx context.Context, ccp *eventingv1alpha1.Cluste return nil } +// Since there are two provisioners "in-memry" and "in-memory-channel" but one single dispatcher service deployment, +// update the label of the K8s service to always point at the same dispatcher service deployment +func setDispatcherServiceSelector() util.ServiceOption { + return func(svc *v1.Service) error { + svc.Spec.Selector = util.DispatcherLabels("in-memory-channel") + return nil + } +} + func (r *reconciler) deleteOldDispatcherService(ctx context.Context, ccp *eventingv1alpha1.ClusterChannelProvisioner) error { svcName := fmt.Sprintf("%s-clusterbus", ccp.Name) svcKey := types.NamespacedName{ diff --git a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go index e4ff44abb9b..036e1424235 100644 --- a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go +++ b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go @@ -40,10 +40,11 @@ import ( ) const ( - ccpUID = "test-uid" - testErrorMessage = "test-induced-error" - testNS = "test-ns" - Name = "in-memory-channel" + ccpUID = "test-uid" + testErrorMessage = "test-induced-error" + testNS = "test-ns" + inMemoryChannelName = "in-memory-channel" + inMemoryName = "in-memory" ) var ( @@ -96,7 +97,7 @@ func TestIsControlled(t *testing.T) { "wrong namespace": { ref: &corev1.ObjectReference{ Namespace: "other", - Name: Name, + Name: inMemoryName, }, isControlled: false, }, @@ -108,7 +109,7 @@ func TestIsControlled(t *testing.T) { }, "is controlled": { ref: &corev1.ObjectReference{ - Name: Name, + Name: inMemoryName, }, isControlled: true, }, @@ -143,7 +144,7 @@ func TestReconcile(t *testing.T) { &eventingv1alpha1.ClusterChannelProvisioner{ ObjectMeta: metav1.ObjectMeta{ Namespace: "not empty string", - Name: Name, + Name: inMemoryName, }, }, }, @@ -240,6 +241,20 @@ func TestReconcile(t *testing.T) { events[ccpReconciled], }, }, + { + Name: "Create dispatcher succeeds - in-memory-Channel", + ReconcileKey: inMemoryChannelName, + InitialState: []runtime.Object{ + makeClusterChannelProvisionerOld(), + }, + WantPresent: []runtime.Object{ + makeReadyClusterChannelProvisionerOld(), + makeK8sServiceOld(), + }, + WantEvent: []corev1.Event{ + events[ccpReconciled], + }, + }, { Name: "Create dispatcher succeeds - request is namespace-scoped", InitialState: []runtime.Object{ @@ -249,7 +264,7 @@ func TestReconcile(t *testing.T) { makeReadyClusterChannelProvisioner(), makeK8sService(), }, - ReconcileKey: fmt.Sprintf("%s/%s", testNS, Name), + ReconcileKey: fmt.Sprintf("%s/%s", testNS, inMemoryName), WantEvent: []corev1.Event{ events[ccpReconciled], }, @@ -297,13 +312,19 @@ func TestReconcile(t *testing.T) { logger: zap.NewNop(), } if tc.ReconcileKey == "" { - tc.ReconcileKey = fmt.Sprintf("/%s", Name) + tc.ReconcileKey = fmt.Sprintf("/%s", inMemoryName) } tc.IgnoreTimes = true t.Run(tc.Name, tc.Runner(t, r, c, recorder)) } } +func makeClusterChannelProvisionerOld() *eventingv1alpha1.ClusterChannelProvisioner { + ccp := makeClusterChannelProvisioner() + ccp.SetName(inMemoryChannelName) + return ccp +} + func makeClusterChannelProvisioner() *eventingv1alpha1.ClusterChannelProvisioner { return &eventingv1alpha1.ClusterChannelProvisioner{ TypeMeta: metav1.TypeMeta{ @@ -311,7 +332,7 @@ func makeClusterChannelProvisioner() *eventingv1alpha1.ClusterChannelProvisioner Kind: "ClusterChannelProvisioner", }, ObjectMeta: metav1.ObjectMeta{ - Name: Name, + Name: inMemoryName, UID: ccpUID, }, Spec: eventingv1alpha1.ClusterChannelProvisionerSpec{}, @@ -328,6 +349,12 @@ func makeReadyClusterChannelProvisioner() *eventingv1alpha1.ClusterChannelProvis return ccp } +func makeReadyClusterChannelProvisionerOld() *eventingv1alpha1.ClusterChannelProvisioner { + ccp := makeReadyClusterChannelProvisioner() + ccp.Name = inMemoryChannelName + return ccp +} + func makeDeletingClusterChannelProvisioner() *eventingv1alpha1.ClusterChannelProvisioner { ccp := makeClusterChannelProvisioner() ccp.DeletionTimestamp = &deletionTime @@ -342,21 +369,21 @@ func makeK8sService() *corev1.Service { }, ObjectMeta: metav1.ObjectMeta{ Namespace: system.Namespace(), - Name: fmt.Sprintf("%s-dispatcher", Name), + Name: fmt.Sprintf("%s-dispatcher", inMemoryName), OwnerReferences: []metav1.OwnerReference{ { APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), Kind: "ClusterChannelProvisioner", - Name: Name, + Name: inMemoryName, UID: ccpUID, Controller: &truePointer, BlockOwnerDeletion: &truePointer, }, }, - Labels: util.DispatcherLabels(Name), + Labels: util.DispatcherLabels(inMemoryName), }, Spec: corev1.ServiceSpec{ - Selector: util.DispatcherLabels(Name), + Selector: util.DispatcherLabels(inMemoryChannelName), Ports: []corev1.ServicePort{ { Name: "http", @@ -368,9 +395,17 @@ func makeK8sService() *corev1.Service { } } +func makeK8sServiceOld() *corev1.Service { + svc := makeK8sService() + svc.SetName(fmt.Sprintf("%s-dispatcher", inMemoryChannelName)) + svc.GetOwnerReferences()[0].Name = inMemoryChannelName + svc.SetLabels(util.DispatcherLabels(inMemoryChannelName)) + return svc +} + func makeOldK8sService() *corev1.Service { svc := makeK8sService() - svc.ObjectMeta.Name = fmt.Sprintf("%s-clusterbus", Name) + svc.ObjectMeta.Name = fmt.Sprintf("%s-clusterbus", inMemoryName) return svc } diff --git a/pkg/provisioners/provisioner_util.go b/pkg/provisioners/provisioner_util.go index 4afe9d4aea0..7003250345c 100644 --- a/pkg/provisioners/provisioner_util.go +++ b/pkg/provisioners/provisioner_util.go @@ -5,6 +5,7 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -19,7 +20,10 @@ import ( "github.com/knative/pkg/system" ) -func CreateDispatcherService(ctx context.Context, client runtimeClient.Client, ccp *eventingv1alpha1.ClusterChannelProvisioner) (*corev1.Service, error) { +// ServiceOption can be used to optionally modify the K8s default that gets created for the Dispatcher in CreateDispatcherService +type ServiceOption func(*v1.Service) error + +func CreateDispatcherService(ctx context.Context, client runtimeClient.Client, ccp *eventingv1alpha1.ClusterChannelProvisioner, opts ...ServiceOption) (*corev1.Service, error) { svcKey := types.NamespacedName{ Namespace: system.Namespace(), Name: channelDispatcherServiceName(ccp.Name), @@ -29,7 +33,12 @@ func CreateDispatcherService(ctx context.Context, client runtimeClient.Client, c err := client.Get(ctx, svcKey, svc) return svc, err } - return createK8sService(ctx, client, getSvc, newDispatcherService(ccp)) + svc, err := newDispatcherService(ccp, opts...) + if err != nil { + return nil, err + } + + return createK8sService(ctx, client, getSvc, svc) } func UpdateClusterChannelProvisionerStatus(ctx context.Context, client runtimeClient.Client, u *eventingv1alpha1.ClusterChannelProvisioner) error { @@ -50,9 +59,9 @@ func UpdateClusterChannelProvisionerStatus(ctx context.Context, client runtimeCl // newDispatcherService creates a new Service for a ClusterChannelProvisioner resource. It also sets // the appropriate OwnerReferences on the resource so handleObject can discover // the ClusterChannelProvisioner resource that 'owns' it. -func newDispatcherService(ccp *eventingv1alpha1.ClusterChannelProvisioner) *corev1.Service { +func newDispatcherService(ccp *eventingv1alpha1.ClusterChannelProvisioner, opts ...ServiceOption) (*corev1.Service, error) { labels := DispatcherLabels(ccp.Name) - return &corev1.Service{ + svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: channelDispatcherServiceName(ccp.Name), Namespace: system.Namespace(), @@ -76,6 +85,13 @@ func newDispatcherService(ccp *eventingv1alpha1.ClusterChannelProvisioner) *core }, }, } + + for _, opt := range opts { + if err := opt(svc); err != nil { + return svc, err + } + } + return svc, nil } func DispatcherLabels(ccpName string) map[string]string { diff --git a/pkg/sidecar/configmap/filesystem/filesystem_watcher.go b/pkg/sidecar/configmap/filesystem/filesystem_watcher.go new file mode 100644 index 00000000000..12f5042d51e --- /dev/null +++ b/pkg/sidecar/configmap/filesystem/filesystem_watcher.go @@ -0,0 +1,126 @@ +/* +Copyright 2018 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 filesystem + +import ( + "errors" + + "github.com/fsnotify/fsnotify" + sidecarconfigmap "github.com/knative/eventing/pkg/sidecar/configmap" + "github.com/knative/eventing/pkg/sidecar/multichannelfanout" + "github.com/knative/eventing/pkg/sidecar/swappable" + "github.com/knative/pkg/configmap" + "go.uber.org/zap" +) + +const ( + // ConfigDir is the mount path of the configMap volume. + ConfigDir = "/etc/config/fanout_sidecar" +) + +// Monitors an attached ConfigMap volume for updated configuration and calls `configUpdated` when +// the value changes. +type ConfigMapWatcher struct { + logger *zap.Logger + // The directory to read the configMap from. + dir string + // Stop the watcher by closing this channel. + watcherStopCh chan<- bool + + // The function to call when the configuration is updated. + configUpdated swappable.UpdateConfig +} + +// NewConfigMapWatcher creates a new filesystem.ConfigMapWatcher. The caller is responsible for +// calling Start(<-chan), likely via a controller-runtime Manager. +func NewConfigMapWatcher(logger *zap.Logger, dir string, updateConfig swappable.UpdateConfig) (*ConfigMapWatcher, error) { + conf, err := readConfigMap(logger, dir) + if err != nil { + logger.Error("Unable to read configMap", zap.Error(err)) + return nil, err + } + + logger.Info("Read initial configMap", zap.Any("conf", conf)) + + err = updateConfig(conf) + if err != nil { + logger.Error("Unable to use the initial configMap: %v", zap.Error(err)) + return nil, err + } + + cmw := &ConfigMapWatcher{ + logger: logger, + dir: dir, + configUpdated: updateConfig, + } + return cmw, nil +} + +// readConfigMap attempts to read the configMap from the attached volume. +func readConfigMap(logger *zap.Logger, dir string) (*multichannelfanout.Config, error) { + cm, err := configmap.Load(dir) + if err != nil { + return nil, err + } + return sidecarconfigmap.NewFanoutConfig(logger, cm) +} + +// updateConfig reads the configMap data and calls `configUpdated` with the updated value. +func (cmw *ConfigMapWatcher) updateConfig() { + conf, err := readConfigMap(cmw.logger, cmw.dir) + if err != nil { + cmw.logger.Error("Unable to read the configMap", zap.Error(err)) + return + } + err = cmw.configUpdated(conf) + if err != nil { + cmw.logger.Error("Unable to update config", zap.Error(err)) + return + } +} + +// Start implements controller runtime's manager.Runnable. +func (cmw *ConfigMapWatcher) Start(stopCh <-chan struct{}) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + + err = watcher.Add(cmw.dir) + if err != nil { + return err + } + + for { + select { + case _, ok := <-watcher.Events: + if !ok { + // Channel closed. + return errors.New("watcher.Events channel closed") + } + cmw.updateConfig() + case e, ok := <-watcher.Errors: + if !ok { + // Channel closed. + return errors.New("watcher.Errors channel closed") + } + cmw.logger.Error("watcher.Errors", zap.Error(e)) + case <-stopCh: + return watcher.Close() + } + } +} diff --git a/pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go b/pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go new file mode 100644 index 00000000000..84a0ac83912 --- /dev/null +++ b/pkg/sidecar/configmap/filesystem/filesystem_watcher_test.go @@ -0,0 +1,379 @@ +/* +Copyright 2018 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 filesystem + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "sync" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" + "github.com/knative/eventing/pkg/sidecar/configmap" + "github.com/knative/eventing/pkg/sidecar/fanout" + "github.com/knative/eventing/pkg/sidecar/multichannelfanout" + "github.com/knative/eventing/pkg/utils" + "go.uber.org/zap" + yaml "gopkg.in/yaml.v2" +) + +func TestReadConfigMap(t *testing.T) { + testCases := []struct { + name string + createDir bool + config string + expected *multichannelfanout.Config + expectedErr bool + }{ + { + name: "dir does not exist", + createDir: false, + }, + { + name: "no data", + createDir: true, + expectedErr: true, + }, + { + name: "invalid YAML", + createDir: true, + config: ` + key: + - value + - different indent level + `, + expectedErr: true, + }, + { + name: "valid YAML -- invalid JSON", + config: "{ nil: Key }", + createDir: true, + expectedErr: true, + }, + { + name: "unknown field", + config: "{ channelConfigs: [ { not: a-defined-field } ] }", + createDir: true, + expectedErr: true, + }, + { + name: "valid", + createDir: true, + config: ` + channelConfigs: + - namespace: default + name: c1 + fanoutConfig: + subscriptions: + - subscriberURI: event-changer.default.svc.` + utils.GetClusterDomainName() + ` + replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` + - subscriberURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` + - replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` + - namespace: default + name: c2 + fanoutConfig: + subscriptions: + - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` + - namespace: other + name: c3 + fanoutConfig: + subscriptions: + - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName(), + expected: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "c1", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + SubscriberURI: "event-changer.default.svc." + utils.GetClusterDomainName(), + ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), + }, + { + SubscriberURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), + }, + { + ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), + }, + }, + }, + }, + { + Namespace: "default", + Name: "c2", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), + }, + }, + }, + }, + { + Namespace: "other", + Name: "c3", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), + }, + }, + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var dir string + if tc.createDir { + var cleanup func() + dir, cleanup = createTempDir(t) + defer cleanup() + } else { + dir = "/tmp/doesNotExist" + } + writeConfigString(t, dir, tc.config) + c, e := readConfigMap(zap.NewNop(), dir) + if tc.expectedErr { + if e == nil { + t.Errorf("Expected an error, actual nil") + } + return + } + if !cmp.Equal(c, tc.expected) { + t.Errorf("Unexpected config. Expected '%v'. Actual '%v'.", tc.expected, c) + } + }) + } +} + +func TestWatch(t *testing.T) { + testCases := map[string]struct { + initialConfigErr error + initialConfig *multichannelfanout.Config + updateConfigErr error + updateConfig *multichannelfanout.Config + }{ + "error applying initial config": { + initialConfig: &multichannelfanout.Config{}, + initialConfigErr: errors.New("test-induced error"), + }, + "read initial config": { + initialConfig: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "c1", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "foo.bar", + }, + }, + }, + }, + }, + }, + }, + "error apply updated config": { + initialConfig: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "c1", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "foo.bar", + }, + }, + }, + }, + }, + }, + updateConfigErr: errors.New("test-induced error"), + }, + "update config": { + initialConfig: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "c1", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "foo.bar", + }, + }, + }, + }, + }, + }, + updateConfig: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "new-channel", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + SubscriberURI: "baz.qux", + }, + }, + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + dir, cleanup := createTempDir(t) + defer cleanup() + writeConfig(t, dir, tc.initialConfig) + + cuc := &configUpdatedChecker{ + updateConfigErr: tc.initialConfigErr, + } + cmw, err := NewConfigMapWatcher(zap.NewNop(), dir, cuc.updateConfig) + if err != nil { + if tc.initialConfigErr != err { + t.Errorf("Unexpected error making ConfigMapWatcher. Expected: '%v'. Actual '%v'", tc.initialConfigErr, err) + } + return + } + ac := cuc.getConfig() + if !cmp.Equal(tc.initialConfig, ac) { + t.Errorf("Unexpected initial config. Expected '%v'. Actual '%v'", tc.initialConfig, ac) + } + + stopCh := make(chan struct{}) + go func() { + _ = cmw.Start(stopCh) + }() + defer func() { + close(stopCh) + }() + // Sadly, the test is flaky unless we sleep here, waiting for the file system + // watcher to truly start. + time.Sleep(100 * time.Millisecond) + + if tc.updateConfigErr != nil { + cuc.updateConfigErr = tc.updateConfigErr + } + + expected := tc.initialConfig + if tc.updateConfig != nil { + expected = tc.updateConfig + } + + cuc.updateCalled = make(chan struct{}, 1) + writeConfig(t, dir, expected) + // The watcher is running in another goroutine, give it some time to notice the + // change. + select { + case <-cuc.updateCalled: + break + case <-time.After(5 * time.Second): + t.Errorf("Time out waiting for watcher to notice change.") + } + + ac = cuc.getConfig() + if !cmp.Equal(ac, expected) { + t.Errorf("Unexpected update config. Expected '%v'. Actual '%v'", expected, ac) + } + }) + } +} + +type configUpdatedChecker struct { + configLock sync.Mutex + config *multichannelfanout.Config + updateCalled chan struct{} + updateConfigErr error +} + +func (cuc *configUpdatedChecker) updateConfig(config *multichannelfanout.Config) error { + cuc.configLock.Lock() + defer cuc.configLock.Unlock() + cuc.config = config + if cuc.updateCalled != nil { + cuc.updateCalled <- struct{}{} + } + return cuc.updateConfigErr +} + +func (cuc *configUpdatedChecker) getConfig() *multichannelfanout.Config { + cuc.configLock.Lock() + defer cuc.configLock.Unlock() + return cuc.config +} + +func createTempDir(t *testing.T) (string, func()) { + dir, err := ioutil.TempDir("", "configMapHandlerTest") + if err != nil { + t.Errorf("Unable to make temp directory: %v", err) + } + return dir, func() { + _ = os.RemoveAll(dir) + } +} + +func writeConfig(t *testing.T, dir string, config *multichannelfanout.Config) { + if config != nil { + yb, err := yaml.Marshal(config) + if err != nil { + t.Errorf("Unable to marshal the config") + } + writeConfigString(t, dir, string(yb)) + } +} + +func writeConfigString(t *testing.T, dir, config string) { + if config != "" { + // Golang editors tend to replace leading spaces with tabs. YAML is left whitespace + // sensitive, so let's replace the tabs with spaces. + leftSpaceConfig := strings.Replace(config, "\t", " ", -1) + err := atomicWriteFile(t, fmt.Sprintf("%s/%s", dir, configmap.MultiChannelFanoutConfigKey), []byte(leftSpaceConfig), 0700) + if err != nil { + t.Errorf("Problem writing the config file: %v", err) + } + } +} + +func atomicWriteFile(t *testing.T, file string, bytes []byte, perm os.FileMode) error { + // In order to more closely replicate how K8s writes ConfigMaps to the file system, we will + // atomically swap out the file by writing it to a temp directory, then renaming it into the + // directory we are watching. + tempDir, cleanup := createTempDir(t) + defer cleanup() + + tempFile := fmt.Sprintf("%s/%s", tempDir, "temp") + err := ioutil.WriteFile(tempFile, bytes, perm) + if err != nil { + return err + } + return os.Rename(tempFile, file) +} diff --git a/pkg/sidecar/configmap/parse.go b/pkg/sidecar/configmap/parse.go new file mode 100644 index 00000000000..ba6da64f12c --- /dev/null +++ b/pkg/sidecar/configmap/parse.go @@ -0,0 +1,54 @@ +/* +Copyright 2018 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 configmap + +import ( + "encoding/json" + "fmt" + + "github.com/knative/eventing/pkg/sidecar/multichannelfanout" + "go.uber.org/zap" +) + +const ( + // MultiChannelFanoutConfigKey is the key in the ConfigMap that contains all the configuration + // data. + MultiChannelFanoutConfigKey = "multiChannelFanoutConfig" +) + +// NewFanoutConfig attempts to parse the config map's data into a multichannelfanout.Config. +// orig == NewFanoutConfig(SerializeConfig(orig)) +func NewFanoutConfig(logger *zap.Logger, data map[string]string) (*multichannelfanout.Config, error) { + str, present := data[MultiChannelFanoutConfigKey] + if !present { + logger.Error("Expected key not found", zap.String("key", MultiChannelFanoutConfigKey)) + return nil, fmt.Errorf("expected key not found: %v", MultiChannelFanoutConfigKey) + } + return multichannelfanout.Parse(logger, str) +} + +// SerializeConfig takes in a multichannelfanout.Config and generates the ConfigMap equivalent. +// orig == NewFanoutConfig(SerializeConfig(orig)) +func SerializeConfig(config multichannelfanout.Config) (map[string]string, error) { + jb, err := json.Marshal(config) + if err != nil { + return nil, err + } + return map[string]string{ + MultiChannelFanoutConfigKey: string(jb), + }, nil +} diff --git a/pkg/sidecar/configmap/parse_test.go b/pkg/sidecar/configmap/parse_test.go new file mode 100644 index 00000000000..cee271ce090 --- /dev/null +++ b/pkg/sidecar/configmap/parse_test.go @@ -0,0 +1,213 @@ +/* +Copyright 2018 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 configmap + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" + "github.com/knative/eventing/pkg/sidecar/fanout" + "github.com/knative/eventing/pkg/sidecar/multichannelfanout" + "github.com/knative/eventing/pkg/utils" + "go.uber.org/zap" +) + +func TestNewFanoutConfig(t *testing.T) { + testCases := []struct { + name string + config string + expected *multichannelfanout.Config + expectedErr bool + }{ + { + name: "no data", + expectedErr: true, + }, + { + name: "invalid YAML", + config: ` + key: + - value + - different indent level + `, + expectedErr: true, + }, + { + name: "valid YAML -- invalid JSON", + config: "{ nil: Key }", + expectedErr: true, + }, + { + name: "unknown field", + config: "{ channelConfigs: [ { not: a-defined-field } ] }", + expectedErr: true, + }, + { + name: "valid", + config: ` + channelConfigs: + - namespace: default + name: c1 + fanoutConfig: + subscriptions: + - subscriberURI: event-changer.default.svc.` + utils.GetClusterDomainName() + ` + replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` + - subscriberURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` + - replyURI: message-dumper-bar.default.svc.` + utils.GetClusterDomainName() + ` + - namespace: default + name: c2 + fanoutConfig: + subscriptions: + - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName() + ` + - namespace: other + name: c3 + fanoutConfig: + subscriptions: + - replyURI: message-dumper-foo.default.svc.` + utils.GetClusterDomainName(), + expected: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "c1", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + SubscriberURI: "event-changer.default.svc." + utils.GetClusterDomainName(), + ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), + }, + { + SubscriberURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), + }, + { + ReplyURI: "message-dumper-bar.default.svc." + utils.GetClusterDomainName(), + }, + }, + }, + }, + { + Namespace: "default", + Name: "c2", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), + }, + }, + }, + }, + { + Namespace: "other", + Name: "c3", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + ReplyURI: "message-dumper-foo.default.svc." + utils.GetClusterDomainName(), + }, + }, + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + data := formatData(tc.config) + c, e := NewFanoutConfig(zap.NewNop(), data) + if tc.expectedErr { + if e == nil { + t.Errorf("Expected an error, actual nil") + } + return + } + if !cmp.Equal(c, tc.expected) { + t.Errorf("Unexpected config. Expected '%v'. Actual '%v'.", tc.expected, c) + } + }) + } +} + +func TestSerializeConfig(t *testing.T) { + testCases := map[string]struct { + config *multichannelfanout.Config + }{ + "empty config": { + config: &multichannelfanout.Config{}, + }, + "full config": { + config: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Namespace: "default", + Name: "c1", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + SubscriberURI: "foo.example.com", + ReplyURI: "bar.example.com", + }, + { + ReplyURI: "qux.example.com", + }, + { + SubscriberURI: "baz.example.com", + }, + {}, + }, + }, + }, + { + Namespace: "other", + Name: "no-subs", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{}, + }, + }, + }, + }, + }, + } + + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + s, err := SerializeConfig(*tc.config) + if err != nil { + t.Errorf("Unexpected error serializing config: %v", err) + } + rt, err := NewFanoutConfig(zap.NewNop(), s) + if err != nil { + t.Errorf("Unexpected error deserializing: %v", err) + } + if diff := cmp.Diff(tc.config, rt); diff != "" { + t.Errorf("Unexpected error roundtripping the config (-want, +got): %v", diff) + } + }) + } +} + +func formatData(config string) map[string]string { + data := make(map[string]string) + if config != "" { + // Golang editors tend to replace leading spaces with tabs. YAML is left whitespace + // sensitive and disallows tabs, so let's replace the tabs with four spaces. + leftSpaceConfig := strings.Replace(config, "\t", " ", -1) + data[MultiChannelFanoutConfigKey] = leftSpaceConfig + } + return data +} diff --git a/pkg/sidecar/configmap/watcher/watcher.go b/pkg/sidecar/configmap/watcher/watcher.go new file mode 100644 index 00000000000..01dc5d7af9a --- /dev/null +++ b/pkg/sidecar/configmap/watcher/watcher.go @@ -0,0 +1,49 @@ +/* +Copyright 2018 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 watcher + +import ( + sidecarconfigmap "github.com/knative/eventing/pkg/sidecar/configmap" + "github.com/knative/eventing/pkg/sidecar/swappable" + "github.com/knative/pkg/configmap" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// NewWatcher creates a new InformedWatcher that watches the specified ConfigMap and on any change +// that results in a valid multichannelfanout.Config calls configUpdated. +func NewWatcher(logger *zap.Logger, kc kubernetes.Interface, cmNamespace, cmName string, configUpdated swappable.UpdateConfig) (manager.Runnable, error) { + iw := configmap.NewInformedWatcher(kc, cmNamespace) + iw.Watch(cmName, func(cm *corev1.ConfigMap) { + config, err := sidecarconfigmap.NewFanoutConfig(logger, cm.Data) + if err != nil { + logger.Error("Could not parse ConfigMap", zap.Error(err), + zap.Any("configMap.Data", cm.Data)) + return + } + + err = configUpdated(config) + if err != nil { + logger.Error("Unable to update config", zap.Error(err)) + return + } + }) + + return iw, nil +} diff --git a/pkg/sidecar/configmap/watcher/watcher_test.go b/pkg/sidecar/configmap/watcher/watcher_test.go new file mode 100644 index 00000000000..6164c38cd63 --- /dev/null +++ b/pkg/sidecar/configmap/watcher/watcher_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2018 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 watcher + +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" + eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" + sidecarconfigmap "github.com/knative/eventing/pkg/sidecar/configmap" + "github.com/knative/eventing/pkg/sidecar/fanout" + "github.com/knative/eventing/pkg/sidecar/multichannelfanout" + "github.com/knative/pkg/configmap" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + namespace = "test-namespace" + name = "test-name" +) + +func TestReconcile(t *testing.T) { + testCases := map[string]struct { + config map[string]string + updateConfigErr error + expectedConfig *multichannelfanout.Config + }{ + "missing key": { + config: map[string]string{}, + expectedConfig: nil, + }, + "cannot parse cm": { + config: map[string]string{ + sidecarconfigmap.MultiChannelFanoutConfigKey: "invalid config", + }, + expectedConfig: nil, + }, + "configUpdated fails": { + config: map[string]string{ + sidecarconfigmap.MultiChannelFanoutConfigKey: "", + }, + updateConfigErr: errors.New("test-error"), + expectedConfig: &multichannelfanout.Config{}, + }, + "success": { + config: map[string]string{ + sidecarconfigmap.MultiChannelFanoutConfigKey: ` + channelConfigs: + - name: foo + namespace: bar + fanoutConfig: + subscriptions: + - subscriberURI: subscriber + replyURI: reply`, + }, + expectedConfig: &multichannelfanout.Config{ + ChannelConfigs: []multichannelfanout.ChannelConfig{ + { + Name: "foo", + Namespace: "bar", + FanoutConfig: fanout.Config{ + Subscriptions: []eventingduck.ChannelSubscriberSpec{ + { + SubscriberURI: "subscriber", + ReplyURI: "reply", + }, + }, + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + cuc := &configUpdatedChecker{ + updateConfigErr: tc.updateConfigErr, + } + + r, err := NewWatcher(zap.NewNop(), nil, namespace, name, cuc.updateConfig) + if err != nil { + t.Errorf("Error creating watcher: %v", err) + } + iw := r.(*configmap.InformedWatcher) + iw.OnChange(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Data: tc.config, + }) + + if diff := cmp.Diff(tc.expectedConfig, cuc.config); diff != "" { + t.Errorf("Unexpected config (-want +got): %v", diff) + } + }) + } +} + +type configUpdatedChecker struct { + config *multichannelfanout.Config + updateConfigErr error +} + +func (cuc *configUpdatedChecker) updateConfig(config *multichannelfanout.Config) error { + cuc.config = config + return cuc.updateConfigErr +} diff --git a/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler_test.go b/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler_test.go index e6c9c30d048..32b86bdc84a 100644 --- a/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler_test.go +++ b/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler_test.go @@ -34,33 +34,6 @@ const ( replaceDomain = "replaceDomain" ) -func TestMakeChannelKey(t *testing.T) { - testCases := []struct { - namespace string - name string - key string - }{ - { - namespace: "default", - name: "channel", - key: "default/channel", - }, - { - namespace: "foo", - name: "bar", - key: "foo/bar", - }, - } - for _, tc := range testCases { - name := fmt.Sprintf("%s, %s -> %s", tc.namespace, tc.name, tc.key) - t.Run(name, func(t *testing.T) { - if key := makeChannelKey(tc.namespace, tc.name); key != tc.key { - t.Errorf("Unexpected ChannelKey. Expected '%v'. Actual '%v'", tc.key, key) - } - }) - } -} - func TestNewHandler(t *testing.T) { testCases := []struct { name string @@ -72,16 +45,14 @@ func TestNewHandler(t *testing.T) { config: Config{ ChannelConfigs: []ChannelConfig{ { - Namespace: "default", - Name: "duplicate", + HostName: "duplicatekey", }, { - Namespace: "default", - Name: "duplicate", + HostName: "duplicatekey", }, }, }, - createErr: "duplicate channel key: default/duplicate", + createErr: "duplicate channel key: duplicatekey", }, } @@ -241,8 +212,9 @@ func TestServeHTTP(t *testing.T) { config: Config{ ChannelConfigs: []ChannelConfig{ { - Namespace: "default", - Name: "first-channel", + Namespace: "ns", + Name: "name", + HostName: "first-channel.default", FanoutConfig: fanout.Config{ Subscriptions: []eventingduck.ChannelSubscriberSpec{ { @@ -261,8 +233,10 @@ func TestServeHTTP(t *testing.T) { config: Config{ ChannelConfigs: []ChannelConfig{ { - Namespace: "default", - Name: "first-channel", + + Namespace: "ns", + Name: "name", + HostName: "first-channel.default", FanoutConfig: fanout.Config{ Subscriptions: []eventingduck.ChannelSubscriberSpec{ { @@ -274,6 +248,7 @@ func TestServeHTTP(t *testing.T) { { Namespace: "default", Name: "second-channel", + HostName: "second-channel.default", FanoutConfig: fanout.Config{ Subscriptions: []eventingduck.ChannelSubscriberSpec{ { @@ -303,7 +278,7 @@ func TestServeHTTP(t *testing.T) { h, err := NewHandler(zap.NewNop(), tc.config) if err != nil { - t.Errorf("Unexpected NewHandler error: '%v'", err) + t.Fatalf("Unexpected NewHandler error: '%v'", err) } r := requestWithChannelKey(tc.key) diff --git a/pkg/sidecar/swappable/swappable_test.go b/pkg/sidecar/swappable/swappable_test.go index 7ee97d00955..b4cc0daa872 100644 --- a/pkg/sidecar/swappable/swappable_test.go +++ b/pkg/sidecar/swappable/swappable_test.go @@ -30,9 +30,8 @@ import ( ) const ( - namespace = "default" - name = "channel1" replaceDomain = "replaceDomain" + hostName = "a.b.c.d" ) func TestHandler(t *testing.T) { @@ -44,8 +43,7 @@ func TestHandler(t *testing.T) { { ChannelConfigs: []multichannelfanout.ChannelConfig{ { - Namespace: namespace, - Name: name, + HostName: hostName, FanoutConfig: fanout.Config{ Subscriptions: []eventingduck.ChannelSubscriberSpec{ { @@ -59,8 +57,7 @@ func TestHandler(t *testing.T) { { ChannelConfigs: []multichannelfanout.ChannelConfig{ { - Namespace: namespace, - Name: name, + HostName: hostName, FanoutConfig: fanout.Config{ Subscriptions: []eventingduck.ChannelSubscriberSpec{ { @@ -96,8 +93,7 @@ func TestHandler_InvalidConfigChange(t *testing.T) { initialConfig: multichannelfanout.Config{ ChannelConfigs: []multichannelfanout.ChannelConfig{ { - Namespace: namespace, - Name: name, + HostName: hostName, FanoutConfig: fanout.Config{ Subscriptions: []eventingduck.ChannelSubscriberSpec{ { @@ -112,12 +108,10 @@ func TestHandler_InvalidConfigChange(t *testing.T) { // Duplicate (namespace, name). ChannelConfigs: []multichannelfanout.ChannelConfig{ { - Namespace: namespace, - Name: name, + HostName: hostName, }, { - Namespace: namespace, - Name: name, + HostName: hostName, }, }, }, @@ -183,7 +177,7 @@ func updateConfigAndTest(t *testing.T, h *Handler, config multichannelfanout.Con func assertRequestAccepted(t *testing.T, h *Handler) { w := httptest.NewRecorder() - h.ServeHTTP(w, makeRequest(namespace, name)) + h.ServeHTTP(w, makeRequest(hostName)) if w.Code != http.StatusAccepted { t.Errorf("Unexpected response code. Expected 202. Actual %v", w.Code) } @@ -196,8 +190,8 @@ func (*successHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _ = r.Body.Close() } -func makeRequest(namespace, name string) *http.Request { - r := httptest.NewRequest("POST", fmt.Sprintf("http://%s.%s/", name, namespace), strings.NewReader("")) +func makeRequest(hostName string) *http.Request { + r := httptest.NewRequest("POST", fmt.Sprintf("http://%s/", hostName), strings.NewReader("")) return r } From bd7ae6832f033bea79a57224b6a004e4d74e5d52 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Tue, 9 Apr 2019 17:28:41 -0700 Subject: [PATCH 04/13] UTs pass, E2E tests pass with in-memory as well as kafka --- cmd/broker/ingress/main.go | 17 ----------------- cmd/fanoutsidecar/main.go | 3 +-- .../{channelwatcher.go => channel_watcher.go} | 0 pkg/provisioners/channel_util.go | 12 +++++++----- .../clusterchannelprovisioner/reconcile.go | 2 +- pkg/provisioners/provisioner_util.go | 5 ++++- .../v1alpha1/broker/resources/ingress.go | 7 ++++--- .../multi_channel_fanout_handler.go | 6 +++--- pkg/sidecar/swappable/swappable.go | 4 ---- 9 files changed, 20 insertions(+), 36 deletions(-) rename pkg/channelwatcher/{channelwatcher.go => channel_watcher.go} (100%) diff --git a/cmd/broker/ingress/main.go b/cmd/broker/ingress/main.go index efafda14aae..ea0094fba73 100644 --- a/cmd/broker/ingress/main.go +++ b/cmd/broker/ingress/main.go @@ -17,12 +17,10 @@ package main import ( - "bytes" "context" "errors" "flag" "fmt" - "io/ioutil" "log" "net/http" "net/url" @@ -223,20 +221,6 @@ func (h *handler) serveHTTP(ctx context.Context, event cloudevents.Event, resp * } func (h *handler) sendEvent(ctx context.Context, tctx cehttp.TransportContext, event cloudevents.Event) error { - - //url := "http://external-service.knative-eventing.svc.cluster.local" - resp, err1 := http.Post(h.channelURI.String(), "application/json", bytes.NewBuffer([]byte{})) - if err1 != nil { - log.Println("Error:", err1) - } - body, err1 := ioutil.ReadAll(resp.Body) - if err1 != nil { - log.Fatalln(err1) - } - log.Println(fmt.Sprintf("Reponse: %+v", resp)) - log.Println(fmt.Sprintf("ReponseBody from server: %v", string(body))) - - fmt.Println("ChannelURI: ", h.channelURI) sendingCTX := broker.SendingContext(ctx, tctx, h.channelURI) startTS := time.Now() @@ -248,7 +232,6 @@ func (h *handler) sendEvent(ctx context.Context, tctx cehttp.TransportContext, e _, err := h.ceClient.Send(sendingCTX, event) if err != nil { sendingCTX, _ = tag.New(sendingCTX, tag.Insert(TagResult, "error")) - fmt.Println("Error: ", err) } else { sendingCTX, _ = tag.New(sendingCTX, tag.Insert(TagResult, "ok")) } diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 9787fdfaecb..6392dd91e57 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -40,9 +40,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" - // uncomment this line to debug in GKE from local machine - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) var ( diff --git a/pkg/channelwatcher/channelwatcher.go b/pkg/channelwatcher/channel_watcher.go similarity index 100% rename from pkg/channelwatcher/channelwatcher.go rename to pkg/channelwatcher/channel_watcher.go diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index d52581c8116..1fc8026dd27 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -123,12 +123,14 @@ func createK8sService(ctx context.Context, client runtimeClient.Client, getSvc g } else if err != nil { return nil, err } - // spec.clusterIP is immutable and is set on existing services. If we don't set this // to the same value, we will encounter an error while updating. svc.Spec.ClusterIP = current.Spec.ClusterIP if !equality.Semantic.DeepDerivative(svc.Spec, current.Spec) || - !expectedLabelsPresent(current.ObjectMeta.Labels, svc.ObjectMeta.Labels) { + !expectedLabelsPresent(current.ObjectMeta.Labels, svc.ObjectMeta.Labels) || + // This DeepEqual is necessary to force update dispatcher services when upgrading from 0.5 to 0.6. + // Above DeepDerivative will not work because we have removed an optional field (name) from ports + !equality.Semantic.DeepEqual(svc.Spec.Ports, current.Spec.Ports) { current.Spec = svc.Spec current.ObjectMeta.Labels = addExpectedLabels(current.ObjectMeta.Labels, svc.ObjectMeta.Labels) err = client.Update(ctx, current) @@ -265,7 +267,6 @@ func UpdateChannel(ctx context.Context, client runtimeClient.Client, u *eventing // OwnerReferences on the resource so handleObject can discover the Channel resource that 'owns' it. // As well as being garbage collected when the Channel is deleted. func newK8sService(c *eventingv1alpha1.Channel, opts ...k8sServiceOption) (*corev1.Service, error) { - // TODO: Need to check if generated name truncates the channel name in case channel name is tool long // Add annotations svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -283,8 +284,9 @@ func newK8sService(c *eventingv1alpha1.Channel, opts ...k8sServiceOption) (*core Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ { - Name: PortName, - Port: PortNumber, + Name: PortName, + Protocol: corev1.ProtocolTCP, + Port: PortNumber, }, }, }, diff --git a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go index 5794b2dbafc..678c544d46a 100644 --- a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go +++ b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile.go @@ -180,7 +180,7 @@ func (r *reconciler) reconcile(ctx context.Context, ccp *eventingv1alpha1.Cluste return nil } -// Since there are two provisioners "in-memry" and "in-memory-channel" but one single dispatcher service deployment, +// Since there are two provisioners "in-memory" and "in-memory-channel" but one single dispatcher service deployment, // update the label of the K8s service to always point at the same dispatcher service deployment func setDispatcherServiceSelector() util.ServiceOption { return func(svc *v1.Service) error { diff --git a/pkg/provisioners/provisioner_util.go b/pkg/provisioners/provisioner_util.go index 7003250345c..a65551fcc3f 100644 --- a/pkg/provisioners/provisioner_util.go +++ b/pkg/provisioners/provisioner_util.go @@ -78,8 +78,11 @@ func newDispatcherService(ccp *eventingv1alpha1.ClusterChannelProvisioner, opts Selector: labels, Ports: []corev1.ServicePort{ { - Name: "http", + // There is a bug in Istio where named port doesn't work when connecting using an ExternalName service + // Refer to https://github.com/istio/istio/issues/13193 for more details. + // TODO: Revert this when ISTIO fixes the issue Port: 80, + Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(8080), }, }, diff --git a/pkg/reconciler/v1alpha1/broker/resources/ingress.go b/pkg/reconciler/v1alpha1/broker/resources/ingress.go index f4eb40cd85c..8df5a57f841 100644 --- a/pkg/reconciler/v1alpha1/broker/resources/ingress.go +++ b/pkg/reconciler/v1alpha1/broker/resources/ingress.go @@ -58,9 +58,10 @@ func MakeIngress(args *IngressArgs) *appsv1.Deployment { Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: ingressLabels(args.Broker), - // Annotations: map[string]string{ - // "sidecar.istio.io/inject": "true", - // }, + // TODO: Remove this annotation once all channels stop using istio virtual service + Annotations: map[string]string{ + "sidecar.istio.io/inject": "true", + }, }, Spec: corev1.PodSpec{ ServiceAccountName: args.ServiceAccountName, diff --git a/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go b/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go index a2f24cbc6d8..282a1c0985d 100644 --- a/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go +++ b/pkg/sidecar/multichannelfanout/multi_channel_fanout_handler.go @@ -42,9 +42,9 @@ type Config struct { // ChannelConfig is the configuration for a single Channel. type ChannelConfig struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - HostName string + Namespace string `json:"namespace"` + Name string `json:"name"` + HostName string `json:"hostname"` FanoutConfig fanout.Config `json:"fanoutConfig"` } diff --git a/pkg/sidecar/swappable/swappable.go b/pkg/sidecar/swappable/swappable.go index 3cff72630df..70de3edab2c 100644 --- a/pkg/sidecar/swappable/swappable.go +++ b/pkg/sidecar/swappable/swappable.go @@ -24,7 +24,6 @@ package swappable import ( "errors" - "fmt" "net/http" "sync" "sync/atomic" @@ -103,9 +102,6 @@ func (h *Handler) UpdateConfig(config *multichannelfanout.Config) error { // ServeHTTP delegates all HTTP requests to the current multichannelfanout.Handler. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // TODO: delete this debugging code - fmt.Sprintf("Request: %+v", r) - // Hand work off to the current multi channel fanout handler. h.logger.Debug("ServeHTTP request received") h.getMultiChannelFanoutHandler().ServeHTTP(w, r) From df4487f10c86dd6b35e5ab47a516aa039db8829d Mon Sep 17 00:00:00 2001 From: akashrv Date: Wed, 10 Apr 2019 06:51:24 -0700 Subject: [PATCH 05/13] fixed uts that failed due to last K8s service change --- .../controller/clusterchannelprovisioner/reconcile_test.go | 2 +- pkg/provisioners/channel_util_test.go | 5 +++-- .../inmemory/clusterchannelprovisioner/reconcile_test.go | 2 +- pkg/provisioners/provisioner_util_test.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/contrib/natss/pkg/controller/clusterchannelprovisioner/reconcile_test.go b/contrib/natss/pkg/controller/clusterchannelprovisioner/reconcile_test.go index e2f043d9231..57a70ade635 100644 --- a/contrib/natss/pkg/controller/clusterchannelprovisioner/reconcile_test.go +++ b/contrib/natss/pkg/controller/clusterchannelprovisioner/reconcile_test.go @@ -254,7 +254,7 @@ func makeK8sService() *corev1.Service { Selector: provisioners.DispatcherLabels(Name), Ports: []corev1.ServicePort{ { - Name: "http", + Protocol: corev1.ProtocolTCP, Port: 80, TargetPort: intstr.FromInt(8080), }, diff --git a/pkg/provisioners/channel_util_test.go b/pkg/provisioners/channel_util_test.go index 6aded3735d3..4f89ffb7b56 100644 --- a/pkg/provisioners/channel_util_test.go +++ b/pkg/provisioners/channel_util_test.go @@ -597,8 +597,9 @@ func makeK8sService() *corev1.Service { Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ { - Name: PortName, - Port: PortNumber, + Name: PortName, + Port: PortNumber, + Protocol: corev1.ProtocolTCP, }, }, }, diff --git a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go index 036e1424235..9d8934d0c61 100644 --- a/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go +++ b/pkg/provisioners/inmemory/clusterchannelprovisioner/reconcile_test.go @@ -386,9 +386,9 @@ func makeK8sService() *corev1.Service { Selector: util.DispatcherLabels(inMemoryChannelName), Ports: []corev1.ServicePort{ { - Name: "http", Port: 80, TargetPort: intstr.FromInt(8080), + Protocol: corev1.ProtocolTCP, }, }, }, diff --git a/pkg/provisioners/provisioner_util_test.go b/pkg/provisioners/provisioner_util_test.go index cdf2eb724e6..fcd6a9dafe1 100644 --- a/pkg/provisioners/provisioner_util_test.go +++ b/pkg/provisioners/provisioner_util_test.go @@ -180,9 +180,9 @@ func makeDispatcherService() *corev1.Service { Selector: DispatcherLabels(clusterChannelProvisionerName), Ports: []corev1.ServicePort{ { - Name: "http", Port: 80, TargetPort: intstr.FromInt(8080), + Protocol: corev1.ProtocolTCP, }, }, }, From 23ae8b4457032e08f4f5cb5422ce5135f1b46fb2 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Wed, 10 Apr 2019 16:14:55 -0700 Subject: [PATCH 06/13] Removed unnecessary space from a line --- config/provisioners/in-memory-channel/in-memory-channel.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/provisioners/in-memory-channel/in-memory-channel.yaml b/config/provisioners/in-memory-channel/in-memory-channel.yaml index 3f466ff5e41..d1b30298273 100644 --- a/config/provisioners/in-memory-channel/in-memory-channel.yaml +++ b/config/provisioners/in-memory-channel/in-memory-channel.yaml @@ -188,7 +188,7 @@ spec: role: dispatcher template: metadata: - annotations: + annotations: sidecar.istio.io/inject: "true" labels: *labels spec: From bb7ab3e84fbbee2e7b6144e5df9d199512840e04 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Wed, 10 Apr 2019 16:19:26 -0700 Subject: [PATCH 07/13] dding istio annotation to test POD. This will ve needed when running E2E tests against channels other than in-memory --- test/crd.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/crd.go b/test/crd.go index 139b33de079..dd29cd53534 100644 --- a/test/crd.go +++ b/test/crd.go @@ -74,6 +74,9 @@ func Configuration(name string, namespace string, imagePath string) *servingv1al // ClusterChannelProvisioner returns a ClusterChannelProvisioner for a given name func ClusterChannelProvisioner(name string) *corev1.ObjectReference { + if name == "" { + return nil + } return pkgTest.CoreV1ObjectReference("ClusterChannelProvisioner", eventsApiVersion, name) } @@ -164,8 +167,9 @@ func EventSenderPod(name string, namespace string, sink string, event CloudEvent return &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, + Name: name, + Namespace: namespace, + Annotations: map[string]string{"sidecar.istio.io/inject": "true"}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ From c646dcd49ab85272d84a605abfb1dd124e24a832 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Thu, 11 Apr 2019 10:36:16 -0700 Subject: [PATCH 08/13] Bug fix to set clusterIp of K8s service only when it is not of type ExternalName --- pkg/provisioners/channel_util.go | 6 ++++-- pkg/provisioners/inmemory/channel/reconcile_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index 1fc8026dd27..3ca480a6582 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -69,7 +69,7 @@ type k8sServiceOption func(*corev1.Service) error func ExternalService(c *eventingv1alpha1.Channel) k8sServiceOption { return func(svc *corev1.Service) error { svc.Spec = corev1.ServiceSpec{ - Type: "ExternalName", + Type: corev1.ServiceTypeExternalName, ExternalName: names.ServiceHostName(channelDispatcherServiceName(c.Spec.Provisioner.Name), system.Namespace()), } return nil @@ -125,7 +125,9 @@ func createK8sService(ctx context.Context, client runtimeClient.Client, getSvc g } // spec.clusterIP is immutable and is set on existing services. If we don't set this // to the same value, we will encounter an error while updating. - svc.Spec.ClusterIP = current.Spec.ClusterIP + if svc.Spec.Type != corev1.ServiceTypeExternalName { + svc.Spec.ClusterIP = current.Spec.ClusterIP + } if !equality.Semantic.DeepDerivative(svc.Spec, current.Spec) || !expectedLabelsPresent(current.ObjectMeta.Labels, svc.ObjectMeta.Labels) || // This DeepEqual is necessary to force update dispatcher services when upgrading from 0.5 to 0.6. diff --git a/pkg/provisioners/inmemory/channel/reconcile_test.go b/pkg/provisioners/inmemory/channel/reconcile_test.go index 211f3fc5a03..76aa9e06f95 100644 --- a/pkg/provisioners/inmemory/channel/reconcile_test.go +++ b/pkg/provisioners/inmemory/channel/reconcile_test.go @@ -482,7 +482,7 @@ func makeK8sService(pn ...string) *corev1.Service { }, Spec: corev1.ServiceSpec{ ExternalName: names.ServiceHostName(fmt.Sprintf("%s-dispatcher", getProvisionerName(pn)), system.Namespace()), - Type: "ExternalName", + Type: corev1.ServiceTypeExternalName, }, } } From feb5e64feb4e7a837eb7a553a14d91b6d47517d7 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Fri, 12 Apr 2019 12:01:55 -0700 Subject: [PATCH 09/13] Updated code based on PR comments --- cmd/fanoutsidecar/main.go | 7 ++- pkg/channelwatcher/channel_watcher.go | 4 +- pkg/provisioners/channel_util.go | 3 +- .../inmemory/channel/reconcile.go | 29 ++++++------ .../inmemory/channel/reconcile_test.go | 44 +------------------ pkg/provisioners/inmemory/controller/main.go | 3 +- pkg/provisioners/provisioner_util.go | 5 ++- .../v1alpha1/broker/resources/ingress.go | 1 + 8 files changed, 31 insertions(+), 65 deletions(-) diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 6392dd91e57..54fa882d9a6 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -82,7 +82,7 @@ func main() { } if len(channelProvisioners) < 1 { - logger.Fatal("--channel_provisioners must be specified") + logger.Fatal("--channel_provisioner must be specified") } sh, err := swappable.NewEmptyHandler(logger) @@ -132,7 +132,10 @@ func setupChannelWatcher(logger *zap.Logger, configUpdated swappable.UpdateConfi logger.Error("Error creating new maanger.", zap.Error(err)) return nil, err } - v1alpha1.AddToScheme(mgr.GetScheme()) + if err = v1alpha1.AddToScheme(mgr.GetScheme()); err != nil { + logger.Error("Error while adding eventing scheme to manager.", zap.Error(err)) + return nil, err + } channelwatcher.New(mgr, logger, updateChannelConfig(configUpdated)) return mgr, nil diff --git a/pkg/channelwatcher/channel_watcher.go b/pkg/channelwatcher/channel_watcher.go index 1b9f7dcbb2a..b9a77670ce8 100644 --- a/pkg/channelwatcher/channel_watcher.go +++ b/pkg/channelwatcher/channel_watcher.go @@ -26,9 +26,9 @@ type reconciler struct { func (r *reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) { ctx := logging.WithLogger(context.TODO(), r.logger.With(zap.Any("request", req))) - r.logger.Info("New update for channel.") + logging.FromContext(ctx).Info("New update for channel.") if err := r.handler(ctx, r.client, req.NamespacedName); err != nil { - r.logger.Error("WatchHandlerFunc returned error", zap.Error(err)) + logging.FromContext(ctx).Error("WatchHandlerFunc returned error", zap.Error(err)) return reconcile.Result{}, err } return reconcile.Result{}, nil diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index 3ca480a6582..f0705452d44 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -65,7 +65,7 @@ func RemoveFinalizer(o metav1.Object, finalizerName string) { type k8sServiceOption func(*corev1.Service) error -// ExternalService is a functional option for CreateK8sService to create a K8s service of type ExternalName +// ExternalService is a functional option for CreateK8sService to create a K8s service of type ExternalName. func ExternalService(c *eventingv1alpha1.Channel) k8sServiceOption { return func(svc *corev1.Service) error { svc.Spec = corev1.ServiceSpec{ @@ -132,6 +132,7 @@ func createK8sService(ctx context.Context, client runtimeClient.Client, getSvc g !expectedLabelsPresent(current.ObjectMeta.Labels, svc.ObjectMeta.Labels) || // This DeepEqual is necessary to force update dispatcher services when upgrading from 0.5 to 0.6. // Above DeepDerivative will not work because we have removed an optional field (name) from ports + // TODO: Remove this check in 0.7+ !equality.Semantic.DeepEqual(svc.Spec.Ports, current.Spec.Ports) { current.Spec = svc.Spec current.ObjectMeta.Labels = addExpectedLabels(current.ObjectMeta.Labels, svc.ObjectMeta.Labels) diff --git a/pkg/provisioners/inmemory/channel/reconcile.go b/pkg/provisioners/inmemory/channel/reconcile.go index fffbdc34c64..630f22b4d6b 100644 --- a/pkg/provisioners/inmemory/channel/reconcile.go +++ b/pkg/provisioners/inmemory/channel/reconcile.go @@ -85,6 +85,19 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err } logger.Info("Reconciling Channel") + // Finalizer needs to be removed (even though no finalizers are added) main back compat + // with v0.5 in which a finalzier was added. Or else channels will not get deleted after upgrading to 0.6 + // TODO: Remove this entire if block in v0.7+ + if c.DeletionTimestamp != nil { + // K8s garbage collection will delete the K8s service and VirtualService for this channel. + // We use a finalizer to ensure the channel config has been synced. + util.RemoveFinalizer(c, finalizerName) + r.client.Update(ctx, c) + logger.Info("Channel reconciled") + r.recorder.Eventf(c, corev1.EventTypeNormal, channelReconciled, "Channel reconciled: %q", c.Name) + return reconcile.Result{}, nil + } + err = r.reconcile(ctx, c) if err != nil { logger.Info("Error reconciling Channel", zap.Error(err)) @@ -95,7 +108,7 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err r.recorder.Eventf(c, corev1.EventTypeNormal, channelReconciled, "Channel reconciled: %q", c.Name) } - if updateStatusErr := util.UpdateChannel(ctx, r.client, c); updateStatusErr != nil { + if updateStatusErr := r.client.Status().Update(ctx, c); updateStatusErr != nil { logger.Info("Error updating Channel Status", zap.Error(updateStatusErr)) r.recorder.Eventf(c, corev1.EventTypeWarning, channelUpdateStatusFailed, "Failed to update Channel's status: %v", err) return reconcile.Result{}, updateStatusErr @@ -117,19 +130,7 @@ func (r *reconciler) reconcile(ctx context.Context, c *eventingv1alpha1.Channel) c.Status.InitializeConditions() - // We are syncing three things: - // 1. The K8s Service to talk to this Channel. - // 3. The configuration of all Channel subscriptions. - - if c.DeletionTimestamp != nil { - // K8s garbage collection will delete the K8s service for this channel. - // We use a finalizer to ensure the channel config has been synced. - util.RemoveFinalizer(c, finalizerName) - return nil - } - - util.AddFinalizer(c, finalizerName) - + // We are syncing K8s Service to talk to this Channel. svc, err := util.CreateK8sService(ctx, r.client, c, util.ExternalService(c)) if err != nil { logger.Info("Error creating the Channel's K8s Service", zap.Error(err)) diff --git a/pkg/provisioners/inmemory/channel/reconcile_test.go b/pkg/provisioners/inmemory/channel/reconcile_test.go index 76aa9e06f95..2978419df42 100644 --- a/pkg/provisioners/inmemory/channel/reconcile_test.go +++ b/pkg/provisioners/inmemory/channel/reconcile_test.go @@ -265,7 +265,7 @@ func TestReconcile(t *testing.T) { MockLists: errorListingK8sService(), }, WantPresent: []runtime.Object{ - makeChannelWithFinalizer(), + makeChannel(), }, WantErrMsg: testErrorMessage, WantEvent: []corev1.Event{ @@ -282,41 +282,14 @@ func TestReconcile(t *testing.T) { }, WantPresent: []runtime.Object{ // TODO: This should have a useful error message saying that the K8s Service failed. - makeChannelWithFinalizer(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[k8sServiceCreateFailed], - }, - }, - { - Name: "Channel get for update fails", - InitialState: []runtime.Object{ makeChannel(), - makeK8sService(), - }, - Mocks: controllertesting.Mocks{ - MockGets: errorOnSecondChannelGet(), }, WantErrMsg: testErrorMessage, WantEvent: []corev1.Event{ - events[channelReconciled], events[channelUpdateStatusFailed], + events[k8sServiceCreateFailed], }, }, { - Name: "Channel update fails", - InitialState: []runtime.Object{ - makeChannel(), - makeK8sService(), - }, - Mocks: controllertesting.Mocks{ - MockUpdates: errorUpdatingChannel(), - }, - WantErrMsg: testErrorMessage, - WantEvent: []corev1.Event{ - events[channelReconciled], events[channelUpdateStatusFailed], - }, - }, { Name: "Channel status update fails", InitialState: []runtime.Object{ makeChannel(), @@ -405,19 +378,6 @@ func getProvisionerName(pn []string) string { return provisionerName } -func makeChannelWithFinalizerAndAddress() *eventingv1alpha1.Channel { - c := makeChannelWithFinalizer() - c.Status.SetAddress(serviceAddress) - return c -} - -func makeReadyChannel() *eventingv1alpha1.Channel { - // Ready channels have the finalizer and are Addressable. - c := makeChannelWithFinalizerAndAddress() - c.Status.MarkProvisioned() - return c -} - func makeChannelNilProvisioner() *eventingv1alpha1.Channel { c := makeChannel() c.Spec.Provisioner = nil diff --git a/pkg/provisioners/inmemory/controller/main.go b/pkg/provisioners/inmemory/controller/main.go index 99ee64f885e..fbbdd262dff 100644 --- a/pkg/provisioners/inmemory/controller/main.go +++ b/pkg/provisioners/inmemory/controller/main.go @@ -29,9 +29,8 @@ import ( "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" - // uncomment this line to debug in GKE from local machine - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) func main() { diff --git a/pkg/provisioners/provisioner_util.go b/pkg/provisioners/provisioner_util.go index a65551fcc3f..0a3653df75f 100644 --- a/pkg/provisioners/provisioner_util.go +++ b/pkg/provisioners/provisioner_util.go @@ -80,7 +80,8 @@ func newDispatcherService(ccp *eventingv1alpha1.ClusterChannelProvisioner, opts { // There is a bug in Istio where named port doesn't work when connecting using an ExternalName service // Refer to https://github.com/istio/istio/issues/13193 for more details. - // TODO: Revert this when ISTIO fixes the issue + // TODO: Uncomment Name:"http" when ISTIO fixes the issue + // Name: "http", Port: 80, Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(8080), @@ -91,7 +92,7 @@ func newDispatcherService(ccp *eventingv1alpha1.ClusterChannelProvisioner, opts for _, opt := range opts { if err := opt(svc); err != nil { - return svc, err + return nil, err } } return svc, nil diff --git a/pkg/reconciler/v1alpha1/broker/resources/ingress.go b/pkg/reconciler/v1alpha1/broker/resources/ingress.go index 8df5a57f841..3bde11755e9 100644 --- a/pkg/reconciler/v1alpha1/broker/resources/ingress.go +++ b/pkg/reconciler/v1alpha1/broker/resources/ingress.go @@ -59,6 +59,7 @@ func MakeIngress(args *IngressArgs) *appsv1.Deployment { ObjectMeta: metav1.ObjectMeta{ Labels: ingressLabels(args.Broker), // TODO: Remove this annotation once all channels stop using istio virtual service + // https://github.com/knative/eventing/issues/294 Annotations: map[string]string{ "sidecar.istio.io/inject": "true", }, From d2c831f7985e315a981ac41f31c48c5aa8717c3f Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Mon, 15 Apr 2019 14:11:12 -0700 Subject: [PATCH 10/13] Updates based on PR comments --- cmd/broker/ingress/main.go | 3 +- cmd/controller/main.go | 16 +++++----- cmd/fanoutsidecar/main.go | 16 ++++++++-- contrib/kafka/cmd/controller/main.go | 12 ++++---- contrib/kafka/main.go | 12 ++++---- pkg/provisioners/channel_util.go | 29 +++++++++++++------ pkg/provisioners/channel_util_test.go | 29 +++++++++++++++++++ .../inmemory/channel/reconcile.go | 16 ++++------ .../inmemory/channel/reconcile_test.go | 21 ++++---------- pkg/provisioners/inmemory/controller/main.go | 2 +- 10 files changed, 96 insertions(+), 60 deletions(-) diff --git a/cmd/broker/ingress/main.go b/cmd/broker/ingress/main.go index ea0094fba73..4e1ec764f55 100644 --- a/cmd/broker/ingress/main.go +++ b/cmd/broker/ingress/main.go @@ -42,10 +42,11 @@ import ( "go.opencensus.io/stats/view" "go.opencensus.io/tag" "go.uber.org/zap" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" crlog "sigs.k8s.io/controller-runtime/pkg/runtime/log" + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). + //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) var ( diff --git a/cmd/controller/main.go b/cmd/controller/main.go index a508d7092a9..36d1653a0da 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -24,20 +24,13 @@ import ( "os" "time" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/logconfig" "github.com/knative/eventing/pkg/reconciler/v1alpha1/broker" "github.com/knative/eventing/pkg/reconciler/v1alpha1/channel" "github.com/knative/eventing/pkg/reconciler/v1alpha1/namespace" "github.com/knative/eventing/pkg/reconciler/v1alpha1/subscription" "github.com/knative/eventing/pkg/reconciler/v1alpha1/trigger" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/manager" - - // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - - eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - "github.com/knative/eventing/pkg/logconfig" istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3" "github.com/knative/pkg/configmap" "github.com/knative/pkg/logging" @@ -46,9 +39,14 @@ import ( "github.com/knative/pkg/system" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" controllerruntime "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). + //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) const ( diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 54fa882d9a6..5e2fd40c6c6 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -40,9 +40,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" - // uncomment this line to debug in GKE from local machine + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) +) var ( readTimeout = 1 * time.Minute @@ -156,7 +157,12 @@ func updateChannelConfig(updateConfig swappable.UpdateConfig) channelwatcher.Wat func listAllChannels(ctx context.Context, c client.Client) ([]v1alpha1.Channel, error) { channels := make([]v1alpha1.Channel, 0) cl := &v1alpha1.ChannelList{} - if err := c.List(ctx, &client.ListOptions{}, cl); err != nil { + opts := &client.ListOptions{ + // Set Raw because if we need to get more than one page, then we will put the continue token + // into opts.Raw.Continue. + Raw: &metav1.ListOptions{}, + } + if err := c.List(ctx, opts, cl); err != nil { return nil, err } for _, c := range cl.Items { @@ -164,7 +170,11 @@ func listAllChannels(ctx context.Context, c client.Client) ([]v1alpha1.Channel, channels = append(channels, c) } } - return channels, nil + if cl.Continue != "" { + opts.Raw.Continue = cl.Continue + } else { + return channels, nil + } } func shouldWatch(ch *v1alpha1.Channel) bool { diff --git a/contrib/kafka/cmd/controller/main.go b/contrib/kafka/cmd/controller/main.go index 37e45a43349..5f57b0165de 100644 --- a/contrib/kafka/cmd/controller/main.go +++ b/contrib/kafka/cmd/controller/main.go @@ -4,20 +4,20 @@ import ( "flag" "os" + provisionerController "github.com/knative/eventing/contrib/kafka/pkg/controller" + "github.com/knative/eventing/contrib/kafka/pkg/controller/channel" + eventingv1alpha "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/provisioners" istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3" "go.uber.org/zap" "k8s.io/apimachinery/pkg/runtime" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" - - provisionerController "github.com/knative/eventing/contrib/kafka/pkg/controller" - "github.com/knative/eventing/contrib/kafka/pkg/controller/channel" - eventingv1alpha "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - "github.com/knative/eventing/pkg/provisioners" + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). + //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) // SchemeFunc adds types to a Scheme. diff --git a/contrib/kafka/main.go b/contrib/kafka/main.go index 316f2dbd521..ec0a9067282 100644 --- a/contrib/kafka/main.go +++ b/contrib/kafka/main.go @@ -4,20 +4,20 @@ import ( "flag" "os" + provisionerController "github.com/knative/eventing/contrib/kafka/pkg/controller" + "github.com/knative/eventing/contrib/kafka/pkg/controller/channel" + eventingv1alpha "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/provisioners" istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3" "go.uber.org/zap" "k8s.io/apimachinery/pkg/runtime" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" - - provisionerController "github.com/knative/eventing/contrib/kafka/pkg/controller" - "github.com/knative/eventing/contrib/kafka/pkg/controller/channel" - eventingv1alpha "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" - "github.com/knative/eventing/pkg/provisioners" + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). + //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) const ( diff --git a/pkg/provisioners/channel_util.go b/pkg/provisioners/channel_util.go index f0705452d44..497dae5adc8 100644 --- a/pkg/provisioners/channel_util.go +++ b/pkg/provisioners/channel_util.go @@ -41,9 +41,14 @@ const ( // AddFinalizerResult is used indicate whether a finalizer was added or already present. type AddFinalizerResult bool +// RemoveFinalizerResult is used to indicate whether a finalizer was found and removed (FinalizerRemoved), or finalizer not found (FinalizerNotFound). +type RemoveFinalizerResult bool + const ( - FinalizerAlreadyPresent AddFinalizerResult = false - FinalizerAdded AddFinalizerResult = true + FinalizerAlreadyPresent AddFinalizerResult = false + FinalizerAdded AddFinalizerResult = true + FinalizerRemoved RemoveFinalizerResult = true + FinalizerNotFound RemoveFinalizerResult = false ) // AddFinalizer adds finalizerName to the Object. @@ -57,16 +62,22 @@ func AddFinalizer(o metav1.Object, finalizerName string) AddFinalizerResult { return FinalizerAdded } -func RemoveFinalizer(o metav1.Object, finalizerName string) { +func RemoveFinalizer(o metav1.Object, finalizerName string) RemoveFinalizerResult { + result := FinalizerNotFound finalizers := sets.NewString(o.GetFinalizers()...) - finalizers.Delete(finalizerName) - o.SetFinalizers(finalizers.List()) + if finalizers.Has(finalizerName) { + result = FinalizerRemoved + finalizers.Delete(finalizerName) + o.SetFinalizers(finalizers.List()) + } + return result } -type k8sServiceOption func(*corev1.Service) error +// K8sServiceOption is a functional option that can modify the K8s Service in CreateK8sService +type K8sServiceOption func(*corev1.Service) error // ExternalService is a functional option for CreateK8sService to create a K8s service of type ExternalName. -func ExternalService(c *eventingv1alpha1.Channel) k8sServiceOption { +func ExternalService(c *eventingv1alpha1.Channel) K8sServiceOption { return func(svc *corev1.Service) error { svc.Spec = corev1.ServiceSpec{ Type: corev1.ServiceTypeExternalName, @@ -76,7 +87,7 @@ func ExternalService(c *eventingv1alpha1.Channel) k8sServiceOption { } } -func CreateK8sService(ctx context.Context, client runtimeClient.Client, c *eventingv1alpha1.Channel, opts ...k8sServiceOption) (*corev1.Service, error) { +func CreateK8sService(ctx context.Context, client runtimeClient.Client, c *eventingv1alpha1.Channel, opts ...K8sServiceOption) (*corev1.Service, error) { getSvc := func() (*corev1.Service, error) { return getK8sService(ctx, client, c) } @@ -269,7 +280,7 @@ func UpdateChannel(ctx context.Context, client runtimeClient.Client, u *eventing // newK8sService creates a new Service for a Channel resource. It also sets the appropriate // OwnerReferences on the resource so handleObject can discover the Channel resource that 'owns' it. // As well as being garbage collected when the Channel is deleted. -func newK8sService(c *eventingv1alpha1.Channel, opts ...k8sServiceOption) (*corev1.Service, error) { +func newK8sService(c *eventingv1alpha1.Channel, opts ...K8sServiceOption) (*corev1.Service, error) { // Add annotations svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/provisioners/channel_util_test.go b/pkg/provisioners/channel_util_test.go index 4f89ffb7b56..848dda5d8f3 100644 --- a/pkg/provisioners/channel_util_test.go +++ b/pkg/provisioners/channel_util_test.go @@ -404,6 +404,35 @@ func TestAddFinalizer(t *testing.T) { } } +func TestRemoveFinalizer(t *testing.T) { + testCases := map[string]struct { + expected RemoveFinalizerResult + }{ + "Finalizer not found": { + expected: false, + }, + "Finalizer removed successfully": { + expected: true, + }, + } + finalizer := "test-finalizer" + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + c := getNewChannel() + if tc.expected { + c.Finalizers = []string{finalizer} + } else { + c.Finalizers = []string{} + } + actual := RemoveFinalizer(c, finalizer) + + if diff := cmp.Diff(actual, tc.expected); diff != "" { + t.Errorf("unexpected error (-want, +got) = %v", diff) + } + }) + } +} + func TestChannelNames(t *testing.T) { testCases := []struct { Name string diff --git a/pkg/provisioners/inmemory/channel/reconcile.go b/pkg/provisioners/inmemory/channel/reconcile.go index 630f22b4d6b..5d5f8392a82 100644 --- a/pkg/provisioners/inmemory/channel/reconcile.go +++ b/pkg/provisioners/inmemory/channel/reconcile.go @@ -85,17 +85,13 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err } logger.Info("Reconciling Channel") - // Finalizer needs to be removed (even though no finalizers are added) main back compat - // with v0.5 in which a finalzier was added. Or else channels will not get deleted after upgrading to 0.6 - // TODO: Remove this entire if block in v0.7+ - if c.DeletionTimestamp != nil { - // K8s garbage collection will delete the K8s service and VirtualService for this channel. - // We use a finalizer to ensure the channel config has been synced. - util.RemoveFinalizer(c, finalizerName) + // Finalizer needs to be removed (even though no finalizers are added) to maintain backwards compatibility + // with v0.5 in which a finalzier was added. Or else channels will not get deleted after upgrading to 0.6+ + if result := util.RemoveFinalizer(c, finalizerName); result == util.FinalizerRemoved { r.client.Update(ctx, c) - logger.Info("Channel reconciled") - r.recorder.Eventf(c, corev1.EventTypeNormal, channelReconciled, "Channel reconciled: %q", c.Name) - return reconcile.Result{}, nil + logger.Info("Channel reconciled. Finalizer Removed") + r.recorder.Eventf(c, corev1.EventTypeNormal, channelReconciled, "Channel reconciled: %q. Finalizer removed.", c.Name) + return reconcile.Result{Requeue: true}, nil } err = r.reconcile(ctx, c) diff --git a/pkg/provisioners/inmemory/channel/reconcile_test.go b/pkg/provisioners/inmemory/channel/reconcile_test.go index 2978419df42..0b221854a41 100644 --- a/pkg/provisioners/inmemory/channel/reconcile_test.go +++ b/pkg/provisioners/inmemory/channel/reconcile_test.go @@ -22,6 +22,8 @@ import ( "fmt" "testing" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + eventingduck "github.com/knative/eventing/pkg/apis/duck/v1alpha1" eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" util "github.com/knative/eventing/pkg/provisioners" @@ -245,16 +247,17 @@ func TestReconcile(t *testing.T) { }, }, { - Name: "Channel deleted - finalizer removed", + Name: "Channel has finalizer (to test back compat with version <= 0.5, when finalizers were added", InitialState: []runtime.Object{ - makeDeletingChannel(), + makeChannelWithFinalizer(), }, WantPresent: []runtime.Object{ - makeDeletingChannelWithoutFinalizer(), + makeChannel(), }, WantEvent: []corev1.Event{ events[channelReconciled], }, + WantResult: reconcile.Result{Requeue: true}, }, { Name: "K8s service get fails", @@ -402,18 +405,6 @@ func makeChannelWithFinalizer() *eventingv1alpha1.Channel { return c } -func makeDeletingChannel() *eventingv1alpha1.Channel { - c := makeChannelWithFinalizer() - c.DeletionTimestamp = &deletionTime - return c -} - -func makeDeletingChannelWithoutFinalizer() *eventingv1alpha1.Channel { - c := makeDeletingChannel() - c.Finalizers = nil - return c -} - func makeK8sService(pn ...string) *corev1.Service { return &corev1.Service{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/provisioners/inmemory/controller/main.go b/pkg/provisioners/inmemory/controller/main.go index fbbdd262dff..577bdc948ce 100644 --- a/pkg/provisioners/inmemory/controller/main.go +++ b/pkg/provisioners/inmemory/controller/main.go @@ -29,7 +29,7 @@ import ( "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" - // uncomment this line to debug in GKE from local machine + // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) From 16a6ffc29389e54f1f80c7ff89b9a23fb32bd6d6 Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Mon, 15 Apr 2019 14:13:34 -0700 Subject: [PATCH 11/13] Updates based on PR comments --- cmd/broker/ingress/main.go | 2 +- cmd/controller/main.go | 2 +- cmd/fanoutsidecar/main.go | 3 +-- contrib/kafka/cmd/controller/main.go | 2 +- contrib/kafka/main.go | 2 +- pkg/provisioners/inmemory/controller/main.go | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/broker/ingress/main.go b/cmd/broker/ingress/main.go index 4e1ec764f55..5317d44320c 100644 --- a/cmd/broker/ingress/main.go +++ b/cmd/broker/ingress/main.go @@ -46,7 +46,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" crlog "sigs.k8s.io/controller-runtime/pkg/runtime/log" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) var ( diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 36d1653a0da..82851fc1bce 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -46,7 +46,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) const ( diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 5e2fd40c6c6..24d793e07eb 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -41,8 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" -) + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) var ( diff --git a/contrib/kafka/cmd/controller/main.go b/contrib/kafka/cmd/controller/main.go index 5f57b0165de..375361f4af3 100644 --- a/contrib/kafka/cmd/controller/main.go +++ b/contrib/kafka/cmd/controller/main.go @@ -17,7 +17,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) // SchemeFunc adds types to a Scheme. diff --git a/contrib/kafka/main.go b/contrib/kafka/main.go index ec0a9067282..ed98481c20b 100644 --- a/contrib/kafka/main.go +++ b/contrib/kafka/main.go @@ -17,7 +17,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) const ( diff --git a/pkg/provisioners/inmemory/controller/main.go b/pkg/provisioners/inmemory/controller/main.go index 577bdc948ce..2b09c992b4f 100644 --- a/pkg/provisioners/inmemory/controller/main.go +++ b/pkg/provisioners/inmemory/controller/main.go @@ -30,7 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). - //_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) func main() { From 67611dc715f72e199330b2c76c5a4d10a7ed920f Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Mon, 15 Apr 2019 14:25:56 -0700 Subject: [PATCH 12/13] Fixed UTs --- cmd/fanoutsidecar/main.go | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/cmd/fanoutsidecar/main.go b/cmd/fanoutsidecar/main.go index 24d793e07eb..370289ffb5e 100644 --- a/cmd/fanoutsidecar/main.go +++ b/cmd/fanoutsidecar/main.go @@ -35,6 +35,7 @@ import ( "github.com/knative/eventing/pkg/sidecar/swappable" "go.uber.org/zap" "go.uber.org/zap/zapcore" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -155,24 +156,26 @@ func updateChannelConfig(updateConfig swappable.UpdateConfig) channelwatcher.Wat func listAllChannels(ctx context.Context, c client.Client) ([]v1alpha1.Channel, error) { channels := make([]v1alpha1.Channel, 0) - cl := &v1alpha1.ChannelList{} - opts := &client.ListOptions{ - // Set Raw because if we need to get more than one page, then we will put the continue token - // into opts.Raw.Continue. - Raw: &metav1.ListOptions{}, - } - if err := c.List(ctx, opts, cl); err != nil { - return nil, err - } - for _, c := range cl.Items { - if c.Status.IsReady() && shouldWatch(&c) { - channels = append(channels, c) + for { + cl := &v1alpha1.ChannelList{} + opts := &client.ListOptions{ + // Set Raw because if we need to get more than one page, then we will put the continue token + // into opts.Raw.Continue. + Raw: &metav1.ListOptions{}, + } + if err := c.List(ctx, opts, cl); err != nil { + return nil, err + } + for _, c := range cl.Items { + if c.Status.IsReady() && shouldWatch(&c) { + channels = append(channels, c) + } + } + if cl.Continue != "" { + opts.Raw.Continue = cl.Continue + } else { + return channels, nil } - } - if cl.Continue != "" { - opts.Raw.Continue = cl.Continue - } else { - return channels, nil } } From 2cc8525d17a627f2fde061564420cc1320b6e7cc Mon Sep 17 00:00:00 2001 From: Akash Verenkar Date: Mon, 15 Apr 2019 14:50:51 -0700 Subject: [PATCH 13/13] Updated VENDOR_LICENSE --- third_party/VENDOR-LICENSE | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/third_party/VENDOR-LICENSE b/third_party/VENDOR-LICENSE index e8d7037e247..697031fbfb7 100644 --- a/third_party/VENDOR-LICENSE +++ b/third_party/VENDOR-LICENSE @@ -627,40 +627,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -=========================================================== -Import: github.com/knative/eventing/vendor/github.com/fsnotify/fsnotify - -Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012 fsnotify Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - =========================================================== Import: github.com/knative/eventing/vendor/github.com/ghodss/yaml