Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start of event-delivery service #40

Merged
merged 6 commits into from
Apr 25, 2018
Merged

Start of event-delivery service #40

merged 6 commits into from
Apr 25, 2018

Conversation

inlined
Copy link
Contributor

@inlined inlined commented Apr 16, 2018

First pass at creating the skeleton for a delivery service. Since this needs to run at data-plane scale, I've spliced it off from the bind-controller pod. Eventually this will become more complex (e.g. when the backing store is Kafka or some other durable queue). I want to experiment with the in-memory queue for a while so we can derive our requirements before we choose what backing store to endorse.

Future PRs will make the action dynamic once Bind has been refactored. May also add a firehose "sendEvent" method, which would be a good fishbowl implementation of a public event provider SDK.

* Changes to "triggers":
  * Renamed the "triggers" pacakge to "sources" to avoid confusion.
  * EventTriggers (FKA Sources) now take EventTriggers instead of
    just params.
  * source.EventTrigger is different from v1alpha1.EventTrigger because
    the latter has embedded Raw types that would be hard to use.
* Updated GitHub sample:
  * Renamed the EventSource to "github.com" rather than just "github"
    to better align with GCF. I think this revealed something about
    the control plane model that I want to discuss.
  * Updated pullrequest.yaml to match new format.
* As part of this I made a minor change to update-deps to work on mac
(gnu-sed still required).
@google-prow-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: inlined

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-prow-robot google-prow-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Apr 16, 2018
@inlined
Copy link
Contributor Author

inlined commented Apr 16, 2018

/assign @vaikas-google

Sorry this didn't break into smaller PRs well =/

@inlined inlined changed the base branch from master to v0.1 April 18, 2018 16:36
@inlined
Copy link
Contributor Author

inlined commented Apr 18, 2018

Changing base to branch v0.1 so it is appropriate to /assign @eobrain while Ville is out of office.

@inlined
Copy link
Contributor Author

inlined commented Apr 18, 2018

/assign @eobrain

@inlined
Copy link
Contributor Author

inlined commented Apr 18, 2018

/unassign @vaikas-google

Copy link
Contributor

@eobrain eobrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not done with the review, but here is what I have so far. Mostly nits and comments.

@@ -0,0 +1,139 @@
/*
Copyright 2017 The Kubernetes Authors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/2017/2018/

@@ -0,0 +1,139 @@
/*
Copyright 2017 The Kubernetes Authors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "The Kubernetes Authors" the correct copyright owner? Or should it be Google like in the other licence files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like I copied a bad copy from cmd/controller.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I seemed to have copied bad prose from cmd/controller/main.go

// but if it does, propagate it back.
glog.Info("Staring event sender")
if err := sender.Run(senderThreads, stopCh); err != nil {
glog.Fatalf("Error running controller: %s", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Suggest:
glog.Fatalf("Error running controller: %v", err)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never "%v" an error. "%s" has an overload for the error interface to call err.Error() whereas "%v" will inspect the structure (and fails to follow pointers)

embed = [":go_default_library"],
importpath = "github.com/elafros/eventing/cmd/delivery",
pure = "on",
visibility = ["//visibility:public"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary, given the default_visibility.

defer cancel()
srv.Shutdown(ctx)

glog.Flush()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or alternatively this could be defer glog.Flush() at the beginning of the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// InMemoryQueue implements the queue interface with a memory buffer.
// Note: this isn't just a simple typedef for a queue because we will soon start
// experimenting with other features, such as fetching an event separate from acking,
// transactional ack + enqueue, cursors, etc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that these other features will only apply to InMemoryQueue and not to the Queue interface? Is that why InMemoryQueue is exported (rather than being inMemoryQueue, with NewInMemoryQueue returning the interface?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They'd apply to the Queue interface; I want to use silly shims to help me decide what I need/want before I go shopping for software

// Queue implements basic features to allow asynchronous buffering of events.
type Queue interface {
Push(event QueuedEvent) error
Pull(stopCh <-chan struct{}) (event QueuedEvent, ok bool)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does stopCh really belong here? It seems like instead it might be better to be passed into the NewInMemoryQueue function (or equivalent for other implementation), and stored in the struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I don't fully understand how to use stopCh. Until working with K8S I've been told that a public interface with a channel is a strict anti-pattern in Golang. Most of the code I see in bind/controller.go uses the stopCh similarly to how a context.Context is used (which would dictate that it is not used in a constructor). Otoh, wait.Until with an infinite loop callback means there can never be a graceful shutdown of the Bind controller. I'll need to ask Ville for help understanding the right usage when he's back in the office.


// TODO(vaikas): Remove this once Bind's Action has been migrated
// to be generic.
const alwaysUseProcessor = "eventing.elafros.dev/EventLogger"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this constant make it sound like it is a Boolean. Maybe rename it to something like onlySupportedProcessorForNow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

bindsLister listers.BindLister
}

// NewReceiver creates a new Reciever object to enqueue events.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: spelling

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

// SendEvent enqueues an event data and Context for delivery to a particular action.
func (r *Receiver) SendEvent(action queue.ActionType, data interface{}, context *event.Context) error {
Copy link
Contributor

@eobrain eobrain Apr 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason this cannot be the exact same signature as Action.SendEvent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still very much TBD in my mind which half of the queue (if not both) will look up and determine the action. This is totally agnostic of whether the webhooks are the same.

Copy link
Contributor

@eobrain eobrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the rest of my review.

w.WriteHeader(http.StatusNotFound)
return
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for clarity, it seems like it might be worth doing

namespace, flowName := matches[1], matches[2]

and using those below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

)

// If an event source knows the exact flow it is targeting it can bypass the work involved with
// processing event triggers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not understand how this comment relates to the regexp below. Where is this bypassing of work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expensive version is not yet implemented. I can change the comment for now.

bind, err := r.bindsLister.Binds(string(matches[1])).Get(string(matches[2]))
if err != nil {
if errors.IsNotFound(err) {
fmt.Printf("Could not find Bind %s in namespace %s\n", matches[2], matches[1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this left over from debugging? Suggest removing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return
}

w.WriteHeader(http.StatusOK)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about putting something in the response body to make debugging easier. For example something like

w.Write([]byte("event sent"))

And if you do this you don't need the w.WriteHeader(http.StatusOK).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove the StatusOK. I don't want to start sending willy nilly response bodies, especially prose bodies.

if err := r.SendEvent(actionFromBind(bind), data, context); err != nil {
glog.Error("Failed to enqueue event", err)
w.WriteHeader(http.StatusInternalServerError)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why you don't want to expose the err caused by internal error, but maybe print something in the response body to make debugging easier. For example

w.Write([]byte(fmt.Sprintf("internal error attempting to send event to %s", bind.Spec.Action.RouteName)))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be easier in the next PR when we remove the shim actionFromBind

go func() {
sender.Run(20, stopCh)
done = true
wait.Done()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, all that has happened is that 20 goroutines have been spawned inside sender.Run, but they have not finished yet. Is that what you intended, or did you intend to wait until all the RunOnces are finished?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed so that sender.Run blocks like the equivalent code in controller/bind

done = true
wait.Done()
}()
if done {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there a race here? Is it not possible (if unlikely) the go-routine above could execute done = true before we get here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if sender.Run can exit before closing the stopCh (per the above mentioned fix)

"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add unit tests in this package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel confident that I know enough of Elafros to write a good unit test except one that literally mocks & exercises my code. The Logging action isn't really testable unless glog has hooks.

Can look at the right way to add tests after Copenhagen

"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add unit tests in this package.

// Set up HTTP endpoints
glog.Infof("Set up metrics scrape path %s", metricsScrapePath)
glog.Infof("Set up debug queue path %s", debugQueuePath)
glog.Infof("Hosting sendEvent API at prefix %s", sendEventPrefix)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems these logging statements would be better split up an put before their respective mux.Handle statements. Otherwise keeping these up-to-date will be error-prone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. These were for my own debugging and they stuck around. Will reformat.

Copy link
Contributor

@eobrain eobrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few extra godoc suggestions.

limitations under the License.
*/

package main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding package comment explaining what this command is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is really necessary, but done.

limitations under the License.
*/

package delivery
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding package comment for godoc, perhaps in separately file doc.go containing just

/* package delivery blah blah blah
...
 */
package delivery

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

limitations under the License.
*/

package action
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding package comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(see doc.go)

Copy link
Contributor Author

@inlined inlined left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with a pass at incorporating feedback.

I think that the queue package may merit tests, especially in the near future. I'm on the fence about whether it's worth building now rather than doing more demo work since the interface is so trivial.

WRT the Action package, the logger is untestable and the Elafros implementation would only literally make sure the code I wrote is exercised, not that any of the input is meaningfully acted upon. This can probably get exercised better in an integration test once we have a better foundation to work with.

@@ -0,0 +1,139 @@
/*
Copyright 2017 The Kubernetes Authors.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I seemed to have copied bad prose from cmd/controller/main.go

limitations under the License.
*/

package main
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is really necessary, but done.

go informerFactory.Start(stopCh)

// Start sender:
go func() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. It should have been blocking like the bind controller. Have fixed.

// Set up HTTP endpoints
glog.Infof("Set up metrics scrape path %s", metricsScrapePath)
glog.Infof("Set up debug queue path %s", debugQueuePath)
glog.Infof("Hosting sendEvent API at prefix %s", sendEventPrefix)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. These were for my own debugging and they stuck around. Will reformat.

defer cancel()
srv.Shutdown(ctx)

glog.Flush()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

t.Fatal("Failed to pull queued event")
}
if !reflect.DeepEqual(context, event.Context) {
t.Fatalf("Event context was not marshalled correctly; expected=%+v got=%+v", context, event.Context)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cascading failures in a test almost never help debugging. The only good reason for t.Errorf in the past was table-driven tests, and now there's a better interface that properly encapsulates t.Fatalf.

}
}

// RunOnce processes a single event from the queue.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OK bool idiom is pretty idiomatic. LMK if the comment is sufficient.

}

// Run runs Sender until stopCh is closed.
func (s *Sender) Run(threadiness int, stopCh <-chan struct{}) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to mimic the surrounding codebase. I don't feel comfortable changing until/unless Ville clarifies.

go func() {
sender.Run(20, stopCh)
done = true
wait.Done()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed so that sender.Run blocks like the equivalent code in controller/bind

done = true
wait.Done()
}()
if done {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if sender.Run can exit before closing the stopCh (per the above mentioned fix)

Action ActionType `json:"action"`
Data interface{} `json:"data"`
Context *event.Context `json:"context"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Suggest TODO saying that.

t.Fatal("Failed to pull queued event")
}
if !reflect.DeepEqual(context, event.Context) {
t.Fatalf("Event context was not marshalled correctly; expected=%+v got=%+v", context, event.Context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I agree, but OK -- fine as is.

}
}

// RunOnce processes a single event from the queue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I naively expected the Boolean would mean "SendEvent succeeded". But actually the Boolean is also true in two cases that look like error conditions. It might be worth explaining how this is used to implement retry.

Copy link
Contributor

@eobrain eobrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM

@eobrain
Copy link
Contributor

eobrain commented Apr 25, 2018

/lgtm

@google-prow-robot google-prow-robot added the lgtm Indicates that a PR is ready to be merged. label Apr 25, 2018
@google-prow-robot google-prow-robot merged commit 397222e into knative:v0.1 Apr 25, 2018
akashrv pushed a commit to akashrv/eventing that referenced this pull request Mar 14, 2019
Accept v0.1 and v0.2 cloud events. Adding UTs.
matzew added a commit to matzew/eventing that referenced this pull request Apr 8, 2019
Olm manifest and catalogsource
matzew added a commit to matzew/eventing that referenced this pull request Apr 11, 2019
Olm manifest and catalogsource
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants