OpenStackのCLI(コマンドラインインターフェース)の動作が予想と違っていたので調査した。

OpenStackのCLIにはタイムアウトの引数がある。例えばkeystoneの場合:

1
2
$ keystone --help
--timeout <seconds> Set request timeout (in seconds).

デフォルトはタイムアウトしない設定になっている。

このtimeoutはCLIコマンド自体のタイムアウトではないし、もっと厳密に言うとリクエストのタイムアウトでもない。(Set request timeoutって書いてあるのに!)

結論から言うとOpenStackクライアントのtimeout設定はAPIコールの無通信時間のタイムアウトである。

OpenStackのクライアントライブラリのソースを追ってみた。OpenStackのクライアントライブラリのtimeoutパラメータはPythonのHTTPライブラリrequestsにそのまま渡される。

requestsのマニュアルには以下のような但し書きがある。

timeout is not a time limit on the entire response download; rather, an exception is raised if the server has not issued a response for timeout seconds (more precisely, if no bytes have been received on the underlying socket for timeout seconds). If no timeout is specified explicitly, requests do not time out.

実際に試してみるために、20秒おきに’.’を出力するサーバをつくる

1
$ ( echo "HTTP/1.0 200 OK"; echo; while true;do sleep 20;echo ".";done ) | nc -l 8080

タイムアウト10秒でリクエストすると正しくタイムアウトする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python
>>> requests.request('GET', 'http://localhost:8080',timeout=10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/api.py", line 53, in request
return session.request(method=method, url=url, **kwargs)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/sessions.py", line 468, in request
resp = self.send(prep, **send_kwargs)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/sessions.py", line 608, in send
r.content
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/models.py", line 737, in content
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/models.py", line 667, in generate
raise ConnectionError(e)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=8080): Read timed out.

問題となるのは、タイムアウト値以下で間欠的なレスポンスを返すケースだろう

実際に試すために1秒ごとに’.’を出力するサーバをつくる。

1
$ ( echo "HTTP/1.0 200 OK"; echo; while true;do sleep 1;echo ".";done ) | nc -l 8080

以下のように、タイムアウト10秒でHTTPリクエストをしてもタイムアウトしない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ python
>>> import requests
>>> requests.request('GET', 'http://localhost:8080',timeout=10)
(ここでCtrl-Cまで停止)
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/api.py", line 53, in request
return session.request(method=method, url=url, **kwargs)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/sessions.py", line 468, in request
resp = self.send(prep, **send_kwargs)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/sessions.py", line 608, in send
r.content
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/models.py", line 737, in content
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/models.py", line 660, in generate
for chunk in self.raw.stream(chunk_size, decode_content=True):
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/packages/urllib3/response.py", line 344, in stream
data = self.read(amt=amt, decode_content=decode_content)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/site-packages/requests/packages/urllib3/response.py", line 301, in read
data = self._fp.read(amt)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/httplib.py", line 612, in read
s = self.fp.read(amt)
File "/Users/tumf/.pyenv/versions/2.7.10/lib/python2.7/socket.py", line 384, in read
data = self._sock.recv(left)
KeyboardInterrupt

OpenStackクライアントのtimeout設定は期待した時間にタイムアウトしないことがあることは頭に入れておきたい。

また、OpenStackのクライアントライブラリはタイムアウトはしないがデフォルトの挙動なので、CLIをバッチプロセス中に組み込むときにtimeout値を設定忘れるとそこで処理が停止したまま・・・なんてこともあるので同じく注意。