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

Fixes #971 Adds initial capability to add a gRPC Proxy to Tyk using bundler or direct API Def config #1111

Closed
wants to merge 42 commits into from

Conversation

lonelycode
Copy link
Member

@lonelycode lonelycode commented Sep 12, 2017

Fixes #971

lonelycode and others added 13 commits September 4, 2017 19:11
…or grpc and trace as grpc plugins fail hard because of type mismatches. Needs more investigation.
…conflicts *except* golang.org/x/net/trace, which keeps generating duplicate paths on /debug/trace in the muxer. The only solution is to compile tyk with that vendored lib removed - then plugins work.
…we can retain vendoring and plugin functionality
…vendor by renaming and make the vendor dir the GOPATH to guarantee the same build path and versions for plugins
Copy link
Contributor

@mvdan mvdan left a comment

Choose a reason for hiding this comment

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

first pass before bed, will take a more careful look another day.

.travis.yml Outdated
@@ -25,6 +25,9 @@ services:
- redis-server

install:
# gRPC Proxy as plugins means we can't have these vendored
- go get "github.com/grpc-ecosystem/grpc-gateway/runtime"
- go get "golang.org/x/net/trace"
Copy link
Contributor

Choose a reason for hiding this comment

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

This is now fixed and they're vendored - remove?

api_loader.go Outdated
if spec.CustomMiddlewareBundle != "" {
pathPrefix = path.Join(getTykBundlePath(), spec.APIID+"-"+spec.CustomMiddlewareBundle)
}
filePath := path.Join(pathPrefix, pluginPath)
Copy link
Contributor

Choose a reason for hiding this comment

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

filepath.Join

api_loader.go Outdated
pluginPath := spec.CustomMiddleware.GRPCProxy.Path
pathPrefix := globalConf.MiddlewarePath
if spec.CustomMiddlewareBundle != "" {
pathPrefix = path.Join(getTykBundlePath(), spec.APIID+"-"+spec.CustomMiddlewareBundle)
Copy link
Contributor

Choose a reason for hiding this comment

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

filepath.Join

"google.golang.org/grpc"
)

type conf struct{}
Copy link
Contributor

Choose a reason for hiding this comment

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

this global name throws me off a bit - reading its declaration, I have no idea what it's for.

var p *plugin.Plugin

// Load our plugin
if p, err = plugin.Open(path); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace these three lines with

p, err := ...
if err != nil {

tmapObj, err := p.Lookup("Types")
if err != nil {
fmt.Fprintf(os.Stderr, "error: failed to lookup type map: %v\n", err)
os.Exit(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

A func that errors but may also exit? (here and below)

modGo := lib.NewModule("register")

ctx := context.Background()
ctx, _ = context.WithCancel(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the point of this line if you discard the cancel func?

type v2Config struct {
ctx context.Context
mux *runtime.ServeMux
e string
Copy link
Contributor

Choose a reason for hiding this comment

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

e is a bit too short to know what it is

```sh
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we vendored them?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure my last comment saved - if building the pb.go files manually you need to do this otherwise you can't actually generate the templates

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we would rely on our vendored copies - otherwise plenty of things can go wrong, e.g. if those libs ever have any breaking changes

For example, we could tell people to use our vendor/ as their GOPATH

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't affect these instructions, this command generates the plugins needed by protoc in the previous step to do the generating, so the stuff in our vendor dir won;t be part of the go bin path.

They would need to change their GOPATH after when they actually build the plugin.

Copy link
Contributor

@mvdan mvdan left a comment

Choose a reason for hiding this comment

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

A better review, now that I'm awake.

// register the plug-in's modules
tmapObj, err := p.Lookup("Types")
if err != nil {
return fmt.Errorf("error: failed to lookup type map: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

All the errors have an error: prefix, that can be removed


// assert that the type map pointer is not nil
if tmapPtr == nil {
return fmt.Errorf("error: nil type map: type=%[1]T val=%[1]v", tmapPtr)
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like bad formatting, even github highlights the % chars in red

}

// Get returns the value for the specified key
func (c *v2Config) Get(ctx context.Context, key string) interface{} {
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you come up with the Get/Set interface? If so, why not make it type-safe, like:

func (c *v2Config) Ctx() *context.Context { return c.ctx }

(or just exporting the fields, which is even easier)

Copy link
Member Author

Choose a reason for hiding this comment

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

See the last comment, this mechanism solves a whole bunch of type conversion headaches. I'm not confident enough to redesign it, so if you want to take a shot, be my guest.

```sh
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we would rely on our vendored copies - otherwise plenty of things can go wrong, e.g. if those libs ever have any breaking changes

For example, we could tell people to use our vendor/ as their GOPATH

}

// Do not edit this function
func changeMe(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (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.

changeMe that says do not edit? :)


Copy these to the same directory as your stubs.

Open the *.pb.gw.go and *.pb.go files, you must change the package name from whatever was generated, to `main`, so the
Copy link
Contributor

Choose a reason for hiding this comment

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

whenever you use * you should quote it, otherwise the format gets fucked up


It will generate a reverse proxy `path/to/your_service.pb.gw.go`.

Note: After generating the code for each of the stubs, in order to build the code, you will want to run ```go get .``` from the directory containing the stubs.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think for a single line, a single ` is enough?

// Instantiate a copy of the module registered by the plug-in.
modGo := lib.NewModule("register")

ctx := context.Background()
Copy link
Contributor

Choose a reason for hiding this comment

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

Related to the question below on the Get/Set interface - why is a context in the mix? We don't seem to make any use of it. And it seems unnecessary for getter/setters.

Copy link
Member Author

Choose a reason for hiding this comment

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

I really don't want to mess with these because it's part of the plugin fix for type passing - it's literally a lift and shift from the github.com/akutz/gpds/lib code.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've had a look and it seems like context isn't used there at all. Are we going to make use of it? If not, I'd leave it out of the API.

@@ -0,0 +1,162 @@
# GRPC Proxy How To
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 plugin_build is just a guide. Is this going to move to the docs?

Copy link
Member Author

Choose a reason for hiding this comment

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

Eventually, this is alpha, so keeping a readme here is a good idea until we formalize

docker image that will make this process more automated**

*Edit* To make this easier, use the `tykio/bakery` container, it will generate a plugin file from your proto
files without any additional steps.
Copy link
Contributor

Choose a reason for hiding this comment

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

These two notes seem to contradict each other a bit.

@lonelycode
Copy link
Member Author

@mvdan If you can suggest a good alternative to httptest.NewRecorder() would be happy to change it.

log.Debug("====> Calling: ", outreq.URL.String())
if breakerEnforced {
if breakerConf.CB.Ready() {
rec := httptest.NewRecorder()
Copy link
Contributor

Choose a reason for hiding this comment

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

So if we're using the grpc proxy, we're buffering the entire response? That seems pretty bad to me. If someone wishes to use a grpc API that writes tons of data or writes data incrementally or over time, this won't work at all.

I assume you did this because you only have ServeHTTP to use from grpc. We should be calling that directly with the user's ResponseWriter, instead of reading the whole response into memory first like this.

Note that this is different from what RoundTrip does - that one does the request, but it doesn't buffer the entire response body into memory.


Now you must edit the `opts.go` file:

1. For specific gRPC options, edit the `getOpts()`` return value with the gRPC Options you need for your gateway
Copy link
Contributor

Choose a reason for hiding this comment

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

one too many ` this time I think

1. For specific gRPC options, edit the `getOpts()`` return value with the gRPC Options you need for your gateway
2. Change the name of `var doRegister = changeMe` to whatever is in the `*.pb.gw.go` file, it will be something like:

```RegisterYourServiceHandlerFromEndpoint```
Copy link
Contributor

Choose a reason for hiding this comment

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

If you indent something with four spaces or more, it's already a blockquote - no need for the quotes

@mvdan
Copy link
Contributor

mvdan commented Sep 14, 2017

The httptest thing seems like a release blocker to me, but otherwise this looks okay to me - you probably want another set of eyes as well, like Leon's.

@mvdan
Copy link
Contributor

mvdan commented Sep 14, 2017

Also, I just noticed no tests? I realise that a plugin test would be a nightmare, but you could decouple the plugin loading stuff from the actual important code and test that part.

@lonelycode
Copy link
Member Author

Which ones in particular? Some I won't fix (like the context).

@mvdan
Copy link
Contributor

mvdan commented Sep 15, 2017

Which ones in particular? Some I won't fix (like the context).

The markdown formatting ones :)

@lonelycode
Copy link
Member Author

Bump

Copy link
Contributor

@mvdan mvdan left a comment

Choose a reason for hiding this comment

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

LGTM now, but I'd still like @buger's eyes on this before it's merged.

Also, conflicts - feel free to just merge master into this, and we can squash-merge at the end.

@@ -6,9 +6,7 @@
"checksumSHA1": "9BfDgevpBgdiP4B6DT9SvcuVyLs=",
"path": "github.com/Jeffail/gabs",
Copy link
Contributor

Choose a reason for hiding this comment

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

you moved the vendor copy from Jeffail to jeffail, so update this too

pName := fmt.Sprintf("%v.so", uuid.NewV4().String())
fileArg := fmt.Sprintf("-o=%v", pName)

cmd := exec.Command("go", "build", "--tags='dummy'", "--buildmode=plugin", fileArg)
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't run in a shell, so remove the quotes around dummy (otherwise you really are using 'dummy' with quotes as the build tag)

@buger
Copy link
Member

buger commented Sep 20, 2017

Looks good!

However, I'm not sure what "Step 3" with adding custom option does, and why it is needed https://github.com/TykTechnologies/tyk/blob/f07bd7ab0c7312b52258a3755875ef5712d1698e/plugin_build/README.md#step-3-add-a-custom-option-to-the-proto-file
It defines mapping between gRPC method and proxied URL, right?

If so, seems like bakery don't do it, and readme should mention that this step needed even with bakery.

@lonelycode
Copy link
Member Author

That's the gRPC proxy specific code annotations to have the proto file generate a proxy in the first place.

You're right though the readme should be clear that yu need to do this with the bakery.

@lonelycode
Copy link
Member Author

Bump

@buger
Copy link
Member

buger commented Sep 22, 2017

@matiasinsaurralde pls do the QA of this task. Once you confirm that everything works, can be merged.

@mvdan
Copy link
Contributor

mvdan commented Sep 29, 2017

bump @matiasinsaurralde

@lonelycode
Copy link
Member Author

We may need to drop this for v2.4, happy to do so, it's hyper experimental.

@stale
Copy link

stale bot commented Mar 25, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs, please add comments to this ticket if you would like it to stay open. Thank you for your contributions.

@stale stale bot added the wontfix label Mar 25, 2020
@stale stale bot closed this Apr 24, 2020
@furkansenharputlu furkansenharputlu deleted the grpc-proxy branch May 25, 2021 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable initial REST to gRPC proxying
3 participants