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

[v2.x] Add requests timeouts option #246

Merged
merged 3 commits into from
Apr 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Metrics/AbcSize:
Metrics/LineLength:
Max: 100
Metrics/ParameterLists:
Max: 8
Max: 5
CountKeywordArgs: false
Metrics/CyclomaticComplexity:
Max: 8
Style/FileName:
Exclude: [Gemfile*]
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ rvm:
- "2.1"
- "2.2"
- "2.3.0"
gemfile:
- Gemfile
- Gemfile-rest-client-1.8.rb
sudo: false
cache: bundler
script: bundle exec rake $TASK
env:
- TASK=test
- TASK=rubocop
matrix:
exclude:
# No point running rubocop with old rest-client
- gemfile: Gemfile-rest-client-1.8.rb
env: TASK=rubocop
11 changes: 11 additions & 0 deletions Gemfile-rest-client-1.8.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# For travis to additionally test rest-client 1.x.

source 'https://rubygems.org'

# Specify your gem's dependencies in kubeclient.gemspec
gemspec

if dependencies.any? # needed for overriding with recent bundler (1.13 ?)
dependencies.delete('rest-client')
gem 'rest-client', '= 1.8.0'
end
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ uri = URI::HTTP.build(host: "somehostname", port: 8080)
client = Kubeclient::Client.new uri
```

### SSL

It is also possible to use https and configure ssl with:

```ruby
Expand Down Expand Up @@ -85,6 +87,8 @@ client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1',
ssl_options: ssl_options
```

### Authentication

If you are using basic authentication or bearer tokens as described
[here](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/authentication.md) then you can specify one
of the following:
Expand Down Expand Up @@ -135,6 +139,8 @@ client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1',

You can find information about token in [this guide](http://kubernetes.io/docs/user-guide/accessing-the-cluster/) and in [this reference](http://kubernetes.io/docs/admin/authentication/).

### Non-blocking IO

You can also use kubeclient with non-blocking sockets such as Celluloid::IO, see [here](https://github.com/httprb/http/wiki/Parallel-requests-with-Celluloid%3A%3AIO)
for details. For example:

Expand All @@ -148,14 +154,40 @@ client = Kubeclient::Client.new 'https://localhost:8443/api/' , 'v1',
socket_options: socket_options
```

You can also use kubeclient with an http proxy server such as tinyproxy. It can be entered as a string or a URI object
This affects only `.watch_*` sockets, not one-off actions like `.get_*`, `.delete_*` etc.

### Proxies

You can also use kubeclient with an http proxy server such as tinyproxy. It can be entered as a string or a URI object.
For example:
```ruby
proxy_uri = URI::HTTP.build(host: "myproxyhost", port: 8443)
client = Kubeclient::Client.new('https://localhost:8443/api/',
:http_proxy_uri => proxy_uri)
```


### Timeouts

Watching never times out.

One-off actions like `.get_*`, `.delete_*` have a configurable timeout:
```ruby
timeouts = {
open: 10, # unit is seconds
read: nil # nil means never time out
}
client = Kubeclient::Client.new(
'https://localhost:8443/api/', timeouts: timeouts
)
```

Default timeouts match `Net::HTTP` and `RestClient`, which unfortunately depends on ruby version:
- open was infinite up to ruby 2.2, 60 seconds in 2.3+.
- read is 60 seconds.

If you want ruby-independent behavior, always specify `:open`.

### Discovery

Discovery from the kube-apiserver is done lazily on method calls so it would not change behavior.
Expand Down
26 changes: 25 additions & 1 deletion lib/kubeclient/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ module ClientMixin
ssl_socket_class: nil
}.freeze

DEFAULT_TIMEOUTS = {
# These do NOT affect watch, watching never times out.
open: Net::HTTP.new('127.0.0.1').open_timeout, # depends on ruby version
read: Net::HTTP.new('127.0.0.1').read_timeout
}.freeze

DEFAULT_HTTP_PROXY_URI = nil

SEARCH_ARGUMENTS = {
Expand All @@ -50,6 +56,7 @@ def initialize_client(
ssl_options: DEFAULT_SSL_OPTIONS,
auth_options: DEFAULT_AUTH_OPTIONS,
socket_options: DEFAULT_SOCKET_OPTIONS,
timeouts: DEFAULT_TIMEOUTS,
http_proxy_uri: DEFAULT_HTTP_PROXY_URI
)
validate_auth_options(auth_options)
Expand All @@ -63,6 +70,9 @@ def initialize_client(
@ssl_options = ssl_options
@auth_options = auth_options
@socket_options = socket_options
# Allow passing partial timeouts hash, without unspecified
# @timeouts[:foo] == nil resulting in infinite timeout.
@timeouts = DEFAULT_TIMEOUTS.merge(timeouts)
@http_proxy_uri = http_proxy_uri.to_s if http_proxy_uri

if auth_options[:bearer_token]
Expand Down Expand Up @@ -215,7 +225,9 @@ def create_rest_client(path = nil)
ssl_client_key: @ssl_options[:client_key],
proxy: @http_proxy_uri,
user: @auth_options[:username],
password: @auth_options[:password]
password: @auth_options[:password],
open_timeout: @timeouts[:open],
ClientMixin.restclient_read_timeout_option => @timeouts[:read]
}
RestClient::Resource.new(@api_endpoint.merge(path).to_s, options)
end
Expand Down Expand Up @@ -420,6 +432,18 @@ def api
JSON.parse(response)
end

def self.restclient_read_timeout_option
@restclient_read_timeout_option ||=
# RestClient silently accepts unknown options, so check accessors instead.
if RestClient::Resource.instance_methods.include?(:read_timeout) # rest-client 2.0
:read_timeout
elsif RestClient::Resource.instance_methods.include?(:timeout) # rest-client 1.x
:timeout
else
fail ArgumentError("RestClient doesn't support neither :read_timeout nor :timeout")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@cben I changed raise to fail.

end
end

private

def load_entities
Expand Down
72 changes: 72 additions & 0 deletions test/test_kubeclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ def test_api_ssl_failure
assert_equal(error_message, exception.message)
end

def test_api_timeout
stub_request(:get, 'http://localhost:8080/api').to_timeout

client = Kubeclient::Client.new('http://localhost:8080/api/')

exception = assert_raises(KubeException) { client.api }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@cben note that I changed this to KubeException from Kubeclient::HttpError

assert_match(/(timed out|timeout)/i, exception.message)
end

def test_api_valid
stub_request(:get, 'http://localhost:8080/api')
.to_return(status: 200, body: open_test_file('versions_list.json'))
Expand Down Expand Up @@ -627,6 +636,69 @@ def test_nil_items
client.get_persistent_volume_claims
end

# Timeouts

def test_timeouts_defaults
client = Kubeclient::Client.new(
'http://localhost:8080/api/'
)
rest_client = client.rest_client
assert_default_open_timeout(rest_client.open_timeout)
assert_equal(60, read_timeout(rest_client))
end

def test_timeouts_open
client = Kubeclient::Client.new(
'http://localhost:8080/api/',
timeouts: { open: 10 }
)
rest_client = client.rest_client
assert_equal(10, rest_client.open_timeout)
assert_equal(60, read_timeout(rest_client))
end

def test_timeouts_read
client = Kubeclient::Client.new(
'http://localhost:8080/api/',
timeouts: { read: 300 }
)
rest_client = client.rest_client
assert_default_open_timeout(rest_client.open_timeout)
assert_equal(300, read_timeout(rest_client))
end

def test_timeouts_both
client = Kubeclient::Client.new(
'http://localhost:8080/api/',
timeouts: { open: 10, read: 300 }
)
rest_client = client.rest_client
assert_equal(10, rest_client.open_timeout)
assert_equal(300, read_timeout(rest_client))
end

def test_timeouts_infinite
client = Kubeclient::Client.new(
'http://localhost:8080/api/',
timeouts: { open: nil, read: nil }
)
rest_client = client.rest_client
assert_nil(rest_client.open_timeout)
assert_nil(read_timeout(rest_client))
end

def read_timeout(rest_client)
rest_client.send(Kubeclient::ClientMixin.restclient_read_timeout_option)
end

def assert_default_open_timeout(actual)
if RUBY_VERSION >= '2.3'
assert_equal(60, actual)
else
assert_nil(actual)
end
end

private

# dup method creates a shallow copy which is not good in this case
Expand Down