我正在爲SaltStack編寫一些etcd模塊,並遇到了這個奇怪的問題,它在某種程度上阻止了我捕獲一個異常,我對它是如何做到這一點感興趣。它似乎特別圍繞着urllib3。爲什麼我無法捕捉到這個python異常?
一個小腳本(不加鹽):
import etcd
c = etcd.Client('127.0.0.1', 4001)
print c.read('/test1', wait=True, timeout=2)
當我們運行:
[[email protected] utils]# /tmp/etcd_watch.py
Traceback (most recent call last):
File "/tmp/etcd_watch.py", line 5, in <module>
print c.read('/test1', wait=True, timeout=2)
File "/usr/lib/python2.6/site-packages/etcd/client.py", line 481, in read
timeout=timeout)
File "/usr/lib/python2.6/site-packages/etcd/client.py", line 788, in api_execute
cause=e
etcd.EtcdConnectionFailed: Connection to etcd failed due to ReadTimeoutError("HTTPConnectionPool(host='127.0.0.1', port=4001): Read timed out.",)
好吧,讓我們趕上開溜:
#!/usr/bin/python
import etcd
c = etcd.Client('127.0.0.1', 4001)
try:
print c.read('/test1', wait=True, timeout=2)
except etcd.EtcdConnectionFailed:
print 'connect failed'
運行:
[[email protected] _modules]# /tmp/etcd_watch.py
connect failed
看起來不錯 - 這是所有工作的蟒蛇。那麼問題是什麼?我有這樣的鹽ETCD模塊:
[[email protected] _modules]# cat sjmh.py
import etcd
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
return c.read('/test1', wait=True, timeout=2)
except etcd.EtcdConnectionFailed:
return False
當我們運行:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
The minion function caused an exception: Traceback (most recent call last):
File "/usr/lib/python2.6/site-packages/salt/minion.py", line 1173, in _thread_return
return_data = func(*args, **kwargs)
File "/var/cache/salt/minion/extmods/modules/sjmh.py", line 5, in test
c.read('/test1', wait=True, timeout=2)
File "/usr/lib/python2.6/site-packages/etcd/client.py", line 481, in read
timeout=timeout)
File "/usr/lib/python2.6/site-packages/etcd/client.py", line 769, in api_execute
_ = response.data
File "/usr/lib/python2.6/site-packages/urllib3/response.py", line 150, in data
return self.read(cache_content=True)
File "/usr/lib/python2.6/site-packages/urllib3/response.py", line 218, in read
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
ReadTimeoutError: HTTPConnectionPool(host='127.0.0.1', port=4001): Read timed out.
HRM,這是不可思議。 etcd的讀取應該已經返回etcd.EtcdConnectionFailed。所以,我們再來看看。我們現在模塊是這樣的:
import etcd
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
return c.read('/test1', wait=True, timeout=2)
except Exception as e:
return str(type(e))
而我們得到:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
<class 'urllib3.exceptions.ReadTimeoutError'>
好了,我們知道我們可以趕上這個事情。我們現在知道它拋出了一個ReadTimeoutError,所以讓我們來捕捉它。我們的模塊的最新版本:
import etcd
import urllib3.exceptions
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
c.read('/test1', wait=True, timeout=2)
except urllib3.exceptions.ReadTimeoutError as e:
return 'caught ya!'
except Exception as e:
return str(type(e))
而我們測試的..
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
<class 'urllib3.exceptions.ReadTimeoutError'>
呃,等等,什麼?我們爲什麼不抓住這個?例外工作,對.. ..?
怎麼樣,如果我們試圖從urllib3趕上基類..
[[email protected] _modules]# cat sjmh.py
import etcd
import urllib3.exceptions
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
c.read('/test1', wait=True, timeout=2)
except urllib3.exceptions.HTTPError:
return 'got you this time!'
希望並祈禱..
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
The minion function caused an exception: Traceback (most recent call last):
File "/usr/lib/python2.6/site-packages/salt/minion.py", line 1173, in _thread_return
return_data = func(*args, **kwargs)
File "/var/cache/salt/minion/extmods/modules/sjmh.py", line 7, in test
c.read('/test1', wait=True, timeout=2)
File "/usr/lib/python2.6/site-packages/etcd/client.py", line 481, in read
timeout=timeout)
File "/usr/lib/python2.6/site-packages/etcd/client.py", line 769, in api_execute
_ = response.data
File "/usr/lib/python2.6/site-packages/urllib3/response.py", line 150, in data
return self.read(cache_content=True)
File "/usr/lib/python2.6/site-packages/urllib3/response.py", line 218, in read
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
ReadTimeoutError: HTTPConnectionPool(host='127.0.0.1', port=4001): Read timed out.
BLAST YE!好的,讓我們嘗試一個不同的方法,返回一個不同的etcd異常。我們的模塊現在看起來是這樣的:
import etcd
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
c.delete('/')
except etcd.EtcdRootReadOnly:
return 'got you this time!'
而且我們來看:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
got you this time!
作爲最後的測試,我做了這個模塊,我可以運行從直蟒蛇,或鹽模塊。 。
import etcd
import urllib3
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
c.read('/test1', wait=True, timeout=2)
except urllib3.exceptions.ReadTimeoutError:
return 'got you this time!'
except etcd.EtcdConnectionFailed:
return 'cant get away from me!'
except etcd.EtcdException:
return 'oh no you dont'
except urllib3.exceptions.HTTPError:
return 'get back here!'
except Exception as e:
return 'HOW DID YOU GET HERE? {0}'.format(type(e))
if __name__ == "__main__":
print test()
通過蟒蛇:
[[email protected] _modules]# python ./sjmh.py
cant get away from me!
通過鹽:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
HOW DID YOU GET HERE? <class 'urllib3.exceptions.ReadTimeoutError'>
因此,我們可以從ETCD捕獲異常,它拋出。但是,當我們通過它的寂寞運行python-etcd時,通常我們能夠捕獲urllib3 ReadTimeoutError,當我通過salt運行它時,似乎沒有任何東西能夠捕獲urllib3異常,除了一個'Exception'子句。
我可以做到這一點,但我真的很好奇,鹽是怎麼做的,這使得一個異常是無法捕捉的。在使用python之前我從來沒有見過這樣的東西,所以我很好奇它是如何發生的以及我如何解決它。
編輯:
所以我終於能夠抓住它。
import etcd
import urllib3.exceptions
from urllib3.exceptions import ReadTimeoutError
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
c.read('/test1', wait=True, timeout=2)
except urllib3.exceptions.ReadTimeoutError:
return 'caught 1'
except urllib3.exceptions.HTTPError:
return 'caught 2'
except ReadTimeoutError:
return 'caught 3'
except etcd.EtcdConnectionFailed as ex:
return 'cant get away from me!'
except Exception as ex:
return 'HOW DID YOU GET HERE? {0}'.format(type(ex))
if __name__ == "__main__":
print test()
當運行:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
caught 3
它仍然沒有任何意義,但。從我所瞭解的例外情況來看,回報率應該是'抓住1'。爲什麼我必須直接導入異常的名稱,而不是僅僅使用完整的類名?
更多編輯!
因此,在兩個類之間加入比較會產生'False' - 這很明顯,因爲except子句不起作用,所以這些不可能是相同的。
在我調用c.read()之前,我在腳本中添加了以下內容。
log.debug(urllib3.exceptions.ReadTimeoutError.__module__)
log.debug(ReadTimeoutError.__module__)
現在我得到這個在日誌中:
[DEBUG ] requests.packages.urllib3.exceptions
[DEBUG ] urllib3.exceptions
所以,這似乎是陷入事情是這樣的原因。這也是重複性由剛剛下載ETCD並請求庫,做這樣的事情:
#!/usr/bin/python
#import requests
import etcd
c = etcd.Client('127.0.0.1', 4001)
c.read("/blah", wait=True, timeout=2)
你將最終獲得提出的「正確」的例外 - etcd.EtcdConnectionFailed。但是,取消註釋'請求',並且您將以urllib3.exceptions.ReadTimeoutError結束,因爲etcd現在不再捕獲異常。
所以看起來,當請求被導入時,它會重寫urllib3的異常,而其他任何試圖捕獲這些異常的模塊都會失敗。此外,似乎較新版本的請求不存在此問題。
' 「HRM,這是不可思議。ETCD的閱讀應該已經回到etcd.EtcdConnectionFailed」'。不是很奇怪。'ReadTimeoutError'描述了接收數據的時間已經在底層協議級別到期的事實。 'ConnectionFailed'描述客戶端無法連接到遠程服務(出於多種原因)的事實。它們是您可能遇到的兩種截然不同的情況,在網絡中非常常見,尤其是在遠程服務器資源不足且速度慢的情況下。 – Pynchia
Pynchia,我說它應該返回它,因爲一般來說,在python-etcd 0.4.2中,當等待調用超時到期時,它會引發etcd.EtcdConnectionFailed。這是一個本地etcd實例,我證實etcd已經啓動。另外,正如最後一個腳本所證明的,網絡以外的事情正在發生。 – sjmh
哦,是的。這是Connection可能失敗的另一個原因。但在你的情況下,由於超時,讀取失敗。也就是說,圖書館可能會表現得很奇怪。 – Pynchia