Stavo scrivendo alcuni moduli di etcd per SaltStack e mi sono imbattuto in questo strano problema in cui mi impedisce in qualche modo di ottenere un'eccezione e sono interessato a come lo sta facendo. Sembra specificamente incentrato su urllib3.Perché non riesco a catturare questa eccezione Python?
Un piccolo script (non sale):
import etcd
c = etcd.Client('127.0.0.1', 4001)
print c.read('/test1', wait=True, timeout=2)
E quando lo facciamo funzionare:
[[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.",)
Ok, cattura che sodomita:
#!/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'
eseguirlo:
[[email protected] _modules]# /tmp/etcd_watch.py
connect failed
Sembra buono: funziona tutto in python. Quindi qual è il problema? Ho questo nel modulo di sale 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
E quando si corre che:
[[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, questo è strano. La lettura di etcd dovrebbe aver restituito etcd.EtcdConnectionFailed. Quindi, guardiamo oltre. Il nostro modulo è ora questo:
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))
e otteniamo:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
<class 'urllib3.exceptions.ReadTimeoutError'>
Ok, sappiamo che siamo in grado di cogliere questa cosa. E ora sappiamo che ha generato un errore ReadTimeoutError, quindi prendiamocelo. La nuova versione del nostro modulo:
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))
E la nostra prova ..
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
<class 'urllib3.exceptions.ReadTimeoutError'>
Er, aspetta, cosa? Perché non l'abbiamo capito? Eccezioni funzionano, giusto ..?
ne dite se cercare di catturare la classe base da 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!'
sperare e pregare ..
[[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! Ok, proviamo con un metodo diverso che restituisce una diversa eccezione etcd. Il nostro modulo ora assomiglia a questo:
import etcd
def test():
c = etcd.Client('127.0.0.1', 4001)
try:
c.delete('/')
except etcd.EtcdRootReadOnly:
return 'got you this time!'
E la nostra corsa:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
got you this time!
Come prova finale, ho fatto questo modulo, che può funzionare sia in pitone dritto, o come modulo di sale. .
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()
Attraverso pitone:
[[email protected] _modules]# python ./sjmh.py
cant get away from me!
Attraverso sale:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
HOW DID YOU GET HERE? <class 'urllib3.exceptions.ReadTimeoutError'>
Quindi, siamo in grado di intercettare le eccezioni da etcd che getta. Ma, mentre normalmente siamo in grado di catturare l'urllib3 ReadTimeoutError quando eseguiamo python-etcd dal suo solitario, quando lo faccio passare attraverso, niente sembra essere in grado di catturare quell'eccezione urllib3, tranne una clausola 'Exception' coperta.
Posso farlo, ma sono davvero curioso di sapere cosa diavolo sta facendo, in modo che un'eccezione sia inafferrabile. Non l'ho mai visto prima quando lavoro con Python, quindi sarei curioso di sapere come sta accadendo e come posso aggirarlo.
Edit:
Così mi è stato finalmente in grado di prenderlo.
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()
E quando run:
[[email protected] _modules]# salt 'alpha' sjmh.test
alpha:
caught 3
Ancora non ha senso però. Da quello che so delle eccezioni, il ritorno dovrebbe essere 'preso 1'. Perché dovrei importare direttamente il nome dell'eccezione, piuttosto che usare solo il nome completo della classe?
ALTRE MODIFICHE!
Quindi, aggiungendo il confronto tra le due classi si produce 'False' - che è ovvio, perché la clausola except non funzionava, quindi quelle non potevano essere le stesse.
Ho aggiunto quanto segue allo script, proprio prima chiamo il c.read().
log.debug(urllib3.exceptions.ReadTimeoutError.__module__)
log.debug(ReadTimeoutError.__module__)
E ora ho questo nel registro:
[DEBUG ] requests.packages.urllib3.exceptions
[DEBUG ] urllib3.exceptions
Quindi, che sembra essere la ragione che sta ottenendo catturato così com'è. Questo è anche riproducibili, semplicemente scaricando l'etcd e richiede biblioteca e fare qualcosa di simile:
#!/usr/bin/python
#import requests
import etcd
c = etcd.Client('127.0.0.1', 4001)
c.read("/blah", wait=True, timeout=2)
si finirà per ottenere l'eccezione 'destra' in rilievo - etcd.EtcdConnectionFailed. Tuttavia, le "richieste" di uncomment e ti ritroverai con urllib3.exceptions.ReadTimeoutError, perché ora ecc non attira più l'eccezione.
Pertanto, quando le richieste vengono importate, riscrive le eccezioni urllib3 e qualsiasi altro modulo che tenta di intercettarle fallisce. Inoltre, sembra che le versioni più recenti delle richieste non abbiano questo problema.
'" Hrm, è strano. La lettura di etcd dovrebbe essere restituita etcd.EtcdConnectionFailed "'. Non proprio strano'ReadTimeoutError' descrive il fatto che il tempo per ricevere i dati è scaduto a livello di protocollo di underlaying. 'ConnectionFailed' descrive il fatto che il client non è stato in grado di connettersi al servizio remoto (per una serie di motivi). Si tratta di due condizioni distinte che si possono verificare, abbastanza comuni in rete, soprattutto se il server remoto ha risorse basse e lente. – Pynchia
Pynchia, ho detto che avrebbe dovuto restituirlo perché in generale, con python-etcd 0.4.2, quando il timeout scade su una chiamata in attesa, si solleva etcd.EtcdConnectionFailed. È un'istanza locale di etcd e ho verificato che etcd era attivo. Inoltre, come evidenziato dall'ultimo script, sta accadendo qualcosa di diverso dal networking. – sjmh
Oh sì. Questa è un'altra ragione per cui una connessione può fallire. Ma nel tuo caso la lettura sta fallendo a causa di un timeout. Detto questo, la biblioteca potrebbe comportarsi in modo strano. – Pynchia