OpenStack クライアントのタイムアウト

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

<!-- more -->

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

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

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


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

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


OpenStackのクライアントライブラリのソースを追ってみた。OpenStackのクライアントライブラリのtimeoutパラメータはPythonのHTTPライブラリ[requests](http://docs.python-requests.org/en/master/)にそのまま渡される。


`requests`のマニュアルには以下のような[但し書き](http://docs.python-requests.org/en/master/user/quickstart/#timeouts)がある。


> 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秒おきに'.'を出力するサーバをつくる

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

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

```
$ 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秒ごとに'.'を出力するサーバをつくる。

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

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

```
$ 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値を設定忘れるとそこで処理が停止したまま・・・なんてこともあるので同じく注意。