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

Add flag for Oauth request timeout #707

Merged
merged 8 commits into from
Jul 27, 2018
Merged

Add flag for Oauth request timeout #707

merged 8 commits into from
Jul 27, 2018

Conversation

addityasingh
Copy link
Contributor

@addityasingh addityasingh commented Jul 16, 2018

Close #633

  • Add flag for Oauth request timeout
  • Fix test for correct HTTP status code
  • Revisit the HTTPClient used

@lmineiro
Copy link
Contributor

Thanks for the improvements. I added a couple of comments.

oauthCredentialsDirUsage = "directory where oauth credentials are stored: client.json and user.json"
oauthScopeUsage = "the whitespace separated list of oauth scopes"
oauth2TokeninfoURLUsage = "sets the default tokeninfo URL to query information about an incoming OAuth2 token in oauth2Tokeninfo filters"
oauth2TokeninfoTimeoutUsage = "sets the default tokeninfo request timeout duration"
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add some info what a value of 0 means - no timeout, stdlib default (how much?), etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it enough to add the timeout value of 1500ms from defaultOAuthTokeninfoTimeout ?

oauthCredentialsDirUsage = "directory where oauth credentials are stored: client.json and user.json"
oauthScopeUsage = "the whitespace separated list of oauth scopes"
oauth2TokeninfoURLUsage = "sets the default tokeninfo URL to query information about an incoming OAuth2 token in oauth2Tokeninfo filters"
defaultOAuthTokeninfoTimeout = 100 * time.Millisecond // Not sure if this makes sense
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems too short as a default to me. It should be more relaxed by default and be able to accommodate, let's say, the redirects the default client will follow - which count towards the timeout. I propose something more in the range of 1~2 sec as default. End users should customize that for their own use case. If your use case considers more appropriate 100ms, that's the value you should use.

Copy link
Member

Choose a reason for hiding this comment

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

+1 and please move the defaults to the defaults section, thanks

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 have updated this to 1500ms

skipper.go Outdated
@@ -433,6 +433,9 @@ type Options struct {
// OAuthTokeninfoURL sets the OAuthTokeninfoURL similar to https://godoc.org/golang.org/x/oauth2#Endpoint
OAuthTokeninfoURL string

// OAuthTokeninfoTimeout sets timwout duration while calling oauth token service
Copy link
Contributor

Choose a reason for hiding this comment

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

typo s/timwout/timeout

@@ -251,7 +261,9 @@ func (s *tokeninfoSpec) CreateFilter(args []interface{}) (filters.Filter, error)
return nil, filters.ErrInvalidFilterParameters
}

ac, err := newAuthClient(s.tokeninfoURL)
log.Info("Log the timeout ", s.tokenInfoTimeout)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this log line

rsp, err := http.DefaultClient.Do(req)
var client *http.Client
if timeout != 0 {
client = &http.Client{Timeout: timeout}
Copy link
Member

Choose a reason for hiding this comment

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

This will not failover DNS if someone will change the traffic to another loadbalancer for example.
We need to have the workaround as in the kubernetes dataclient or the proxy.
See https://github.com/zalando/skipper/blob/master/dataclients/kubernetes/kube.go#L310-L337.

Copy link
Contributor

Choose a reason for hiding this comment

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

@szuecs Is this still true since the introduction of Transport.IdleConnTimeout in go 1.7? I think that new setting which defaults to 90secs is good enough to mitigate that problem.

Copy link
Member

Choose a reason for hiding this comment

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

No, right now it’s the same problem, but we have an open issue golang/go#23427

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@szuecs do I need to create the same Transport dialer in the auth filter?

Copy link
Member

Choose a reason for hiding this comment

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

@addityasingh you don't need the same, but you need the for {} loop below the transport to close idle connections to make the http client to do a DNS call to make DNS based failover happen.

Copy link
Member

@szuecs szuecs left a comment

Choose a reason for hiding this comment

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

the DNS problem should be fixed and the default as commented from @lmineiro and me cheanged.

@@ -655,10 +658,10 @@ func Run(o Options) error {
}

if o.OAuthTokeninfoURL != "" {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@szuecs I am not sure if I should check for the o.OAuthTokeninfoTimeout field here, since we always will have a default. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

No you should not check timeout here

readme.md Outdated
@@ -100,15 +100,20 @@ logging. See the documentation at:

Getting the code with the test dependencies (`-t` switch):

1. Get skipper using `go get`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aryszka Can you check if this makes sense?

Copy link
Contributor

Choose a reason for hiding this comment

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

@addityasingh have you checked the 'Working with the code' section below? The reason for this 'Installation' section is left more open, is that 1/ 'go get' actually installs the skipper command in GOPATH/bin, 2/ it leaves the choice between using the latest or the bound version of the dependencies to the user.

This doesn't mean I am not open to improve the readme, but I think it should be a discussion in a separate PR. Can you please move the changes to the readme into a new, separate PR?

@@ -145,9 +149,35 @@ func intersect(left []string, right []string) bool {
return false
}

func createHttpClient(timeout time.Duration) *http.Client {
var client *http.Client
if timeout != 0 {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@szuecs IMO, since we will always have a default value for the OAuthTokeninfoTimeout , it doesn't make sense to have a default client and always use the one with timeout. Thoughts ?

Copy link
Member

Choose a reason for hiding this comment

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

Yes use always timeouts

@addityasingh addityasingh changed the title [WIP]: Add flag for Oauth request timeout Add flag for Oauth request timeout Jul 19, 2018

client = &http.Client{Transport: transport}

go func() {
Copy link
Member

Choose a reason for hiding this comment

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

We need to have a quit channel to not leak this goroutine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At which part of the lifecycle should the quit channel be closed?

Copy link
Contributor Author

@addityasingh addityasingh Jul 22, 2018

Choose a reason for hiding this comment

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

@szuecs I did not find any lifecycle method in oauth filter similar to Close() method in the kubernetes ingress controller. I have added the quit channel to return from the goroutine but still not sure at which point to send to the quit channel. I checked the Explicit Cancellation in go pipleines concurrency patterns but could not find the point where to trigger the quit channel

Copy link
Member

@szuecs szuecs Jul 22, 2018

Choose a reason for hiding this comment

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

We have a sigterm handler and use quit channels right now. Please check the kubernetes dataclient or the client connection pool in proxy/proxy.go

Copy link
Contributor Author

@addityasingh addityasingh Jul 23, 2018

Choose a reason for hiding this comment

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

I checked the quit channel implementations in those 2 but the kubernetes/dataclients and proxy/proxy.go have Close() methods, but a filter doesn't. Should I implement a custom dataclient with Close() method for the oauth filter?

readme.md Outdated
make deps
make install
```
2. Install all dependencies using `glide`
Copy link
Contributor

Choose a reason for hiding this comment

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

we have a make task, 'deps', that installs the dependencies

readme.md Outdated
```sh
glide install
```
3. To build the packages and skipper cli, run `make`
Copy link
Contributor

Choose a reason for hiding this comment

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

i'd prefer not using the term 'cli'.


client = &http.Client{Transport: transport}

go func() {
Copy link
Contributor

Choose a reason for hiding this comment

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

i wonder if this is required. If it is required, then we should make it possible to return from this goroutine.

@@ -159,7 +189,8 @@ func jsonGet(url *url.URL, auth string, doc interface{}) error {
return err
}

rsp, err := http.DefaultClient.Do(req)
client := createHttpClient(timeout)
Copy link
Contributor

Choose a reason for hiding this comment

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

we should not create a new client on every request.

@@ -57,6 +57,9 @@ const (
defaultTLSHandshakeTimeoutBackend = 60 * time.Second
defaultMaxIdleConnsBackend = 0

// OAuth2:
defaultOAuthTokeninfoTimeout = 1500 * time.Millisecond // Not sure if this makes sense
Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense, but still a bit opinionated in my view 😄 . How about time.Minute?

Copy link
Contributor Author

@addityasingh addityasingh Jul 21, 2018

Choose a reason for hiding this comment

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

I have changed it to 2 * time.Second

u, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
return &authClient{url: u}, nil

quit := make(chan struct{})
Copy link
Contributor

Choose a reason for hiding this comment

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

to call close(quit), I would the following:

  1. add a Close() method to the filterspec
  2. call close(spec.authClient.quit) from this Close() method
  3. for normal skipper operation do nothing, do not call close, because there's going to be only a single instance of the spec
  4. for tests, always call Close() on the spec
  5. document that in case someone uses filter spec as a library in a custom build, they need to call Close() if they may create an arbitrary number of spec instances

@addityasingh @szuecs what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This approach looks clean to me, since we wouldn't need to pass down quit channel all the way down from skipper instance creation. And since the Close() is on filterspec, we wouldn't need to bother about the leaking goroutine as well. @szuecs Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the FilterSpec interface doesn't have the Close method, this approach wouldn't work. Check this https://play.golang.org/p/LkpQRoatvvn

Copy link
Member

@szuecs szuecs Jul 23, 2018

Choose a reason for hiding this comment

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

You might be able to type cast it. Keep in mind if you need access from another package you have to export the type.

See: https://play.golang.org/p/BK0kM25jnjb

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 works in the playground, but not in the skipper test

Copy link
Member

Choose a reason for hiding this comment

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

I see you found out. An interface can only be casted to, if you use the pointer IIRC.

@@ -280,6 +314,13 @@ func (s *tokeninfoSpec) CreateFilter(args []interface{}) (filters.Filter, error)
return f, nil
}

// Close cleans-up the quit channel used for this spec
func (s *tokeninfoSpec) Close() {
if s.authClient.quit != nil {
Copy link
Member

Choose a reason for hiding this comment

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

A spec can create filters.
I think you have to close it from the filter, that's why you get a nil ptr panic.
See https://github.com/zalando/skipper/pull/707/files#diff-82513d1c973748c3c6b7fa0677fd3fd4L259, which shows that you store it in the filter and not in the spec.

@lmineiro
Copy link
Contributor

👍

1 similar comment
@aryszka
Copy link
Contributor

aryszka commented Jul 27, 2018

👍

@aryszka
Copy link
Contributor

aryszka commented Jul 27, 2018

Thanks a lot for this PR! 😄

@aryszka aryszka merged commit e6d8e2d into zalando:master Jul 27, 2018
@addityasingh addityasingh deleted the add-oauth-timeout-flag branch July 27, 2018 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants