Seite 1 von 2
Timeout setzen?
Verfasst: Dienstag 29. September 2015, 10:41
von chpo7234
Hallo Leute,
ich beschäftige mich leider noch nicht all zu lange mit Python. In meinem Script pinge ich verschiedene Hosts an, und falls verfügbar, wird noch mal eine SNMP-Abfrage gemacht. Leider dauert beides beides ziemlich lange, falls es nicht verfügbar ist. Meine Frage also: wie kann ich einen Befehl mit Timeout bearbeiten?
Beispielsweise die folgende Abfrage
Code: Alles auswählen
import os
if os.system("ping -c1 127.0.0.1")==0:
print("erreichbar")
else:
print("nicht erreichbar")
Ich weiß, dass es bestimmte Parameter (z.B. -W) für den Ping-Befehl gibt. Jedoch könnte ich diese nicht auf die SNMP-Abfrage anwenden.
Lieben Gruß
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 10:52
von sparrow
Dann zeig doch mal deine SNMP-Anfrage.
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 11:08
von BlackJack
@chpo7234: Anstelle von `os.system()` sollte man übrigens besser das `subprocess`-Modul verwenden, zum Beispiel `call()`:
Code: Alles auswählen
ip_address = '127.0.0.1'
if subprocess.call(['ping', '-c1', ip_address]) == 0:
# ...
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 11:30
von chpo7234
Danke für deinen Rat, BlackJack!
Mein Script sieht bis jetzt wie folgt aus:
Code: Alles auswählen
from pysnmp.entity.rfc3413.oneliner import cmdgen #SNMP-Modul
import sys #sys: fuer die Parameter-Uebergabe
import os #os: fuer das anpingen
import socket #socket: rDNS
ip = sys.argv[1]
abstand = " ==> "
if os.system("ping -c1 -W1 " + ip) == 0:
cmdGen = cmdgen.CommandGenerator()
rDNS = ip + abstand + socket.gethostbyaddr(ip)[0]
errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
cmdgen.CommunityData("public"),
cmdgen.UdpTransportTarget((ip, 161)),
"1.3.6.1.2.1.1.1.0"
)
if errorIndication or errorStatus:
print(rDNS)
else:
print(rDNS + abstand + varBinding[0][1])
else:
print(ip + abstand + "ist <b>nicht verfuegbar</b>!<br>")
subprocess werde ich hier demnach noch anpassen.
Gruß
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 12:13
von sparrow
Versuch:
Code: Alles auswählen
errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
cmdgen.CommunityData("public"),
cmdgen.UdpTransportTarget((ip, 161), timeout=1.5, retries=0),
"1.3.6.1.2.1.1.1.0"
)
API gibt es
hier.
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 12:23
von chpo7234
Moin Problem ist leider, dass ich letztendlich nicht nur eine IP, sondern einen ganzen Adressbereich scannen will. Wenn denn mal 200 oder mehr IP's gescannt werden, möchte ich vermeiden, dass der SNMP- oder Ping-Versuch über eine Sekunde hinaus geht - da die Wartezeit sonst immens steigt. Lese mir zur Zeit ein paar Artikel über signal und setitimer durch. Vielleicht finde ich ja noch einen annehmbaren Code. Falls wer Ergänzungen oder Lösungsvorschläge hat, wäre ich erfreut!
Lieben Gruß
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 12:27
von sparrow
Und was ist, wenn die Gegenstelle wirklich länger als eine Sekunde braucht?
Beschäftige dich Threads und einem Threadpool.
Während ein Thread darauf wartet, dass eine Antwort kommt, können ja parallel bereits andere IPs angefragt werden.
Dann kannst du den Timeout unangetastet lassen.
200 IPs parallel zu pingen sollte kein Problem sein.
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 12:41
von chpo7234
Danke für den Rat!
Momentan sieht die Situation so aus, dass eine PHP-Seite den erforderten Adressbereich mittels Schleife durchläuft, und dann jeweils immer das Python-Script aufruft. Wäre das so denn noch zu realisieren? Oder müsste ich den Adressbereich dann eher direkt an Python übergeben, so dass dieser den Bereich parallel abarbeiten kann?
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 12:43
von DasIch
sparrow hat geschrieben:Und was ist, wenn die Gegenstelle wirklich länger als eine Sekunde braucht?
Da sich ein hartnäckiges Gerücht hält, auf Pings nicht zu antworten hätte irgendwelche Vorteile für die Sicherheit, konfigurieren viele Admins Systeme entsprechend. Wenn man Addressbereiche scannt wird man zwangsläufig auf einige solche Systeme stoßen was den Scan mit üblichen 30s Timeouts ins unbrauchbar langsam macht. Eine Timeout von einer Sekunde ist vielleicht etwas kurz aber auch nicht vollkommen absurd wenn man bereit ist false negatives zu haben.
Re: Timeout setzen?
Verfasst: Dienstag 29. September 2015, 13:37
von BlackJack
@chpo7234: Ja man würde dann eher den Addressbereich an das Python-Programm übergeben.
Wegen Threadpool: `concurrent.futures` könnte man sich da anschauen wenn man sich nicht selbst einen Pool basteln möchte.
Ansonsten kann man auch das Programm `fping` anstelle von `ping` ins Auge fassen.
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 06:12
von chpo7234
Moin moin,
ich bin es noch mal.. Der Tipp mit dem Modul "concurrent.futures" war soweit echt super, da bin ich dir echt dankbar, BlackJack! Ich habe alles soweit umgesetzt und über die Shell werden die Abfragen asynchron abgefragt. Super! Problem ist nun nur leider, sobald ich in eines meiner Python-Scripte das Modul "concurrent.futures" importiere, möchte php das Script nicht mehr ausführen und überspringt es scheinbar. Das ist echt schade, nach der ganzen Arbeit.
Habt ihr einen Tipp, wie ich das beheben kann oder könnte jemand eine Annahme treffen, wieso das so ist?
Andernfalls versuche ich nun meinen eigenen Threadpool(
http://www.python-kurs.eu/threads.php) zu erstellen, abseits des Moduls "concurrent.futures", und hoffe so, dass PHP das auf diesem Wege mag...
Lieben Gruß
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 06:37
von Sirius3
@chpo7234: Meldet Python denn einen Fehler? Welche Python-Version benutzt Du? concurrent.futures gibt es erst ab Python 3.2. Für Python 2 gibt es einen
Backport, den Du extra installieren mußt.
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 06:55
von chpo7234
Nein, Python meldet beim Ausführen in der Shell keine Fehler. Alles wird so gemacht, wie ich es möchte. Sobald ich das Script mit PHP über Python3 aufrufe, wird es übersprungen. Und das macht PHP bei jedem Python-Script, sobald ich das futures-Modul einbinde.
Ich bastel mir jetzt etwas mit dem Modul threading, damit klappt es scheinbar mit PHP.

Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 07:20
von BlackJack
@chpo7234: Wenn es mit `threading` klappt, dann sollte es auch mit `concurrent.futures` klappen wenn man dort den `ThreadPoolExecutor` verwendet. Der benutzt ja letztendlich auch `threading`.
Das Programm wird auch sicher nicht von PHP übersprungen sondern es wird ausgeführt und das funktioniert dann aus irgendwelchen Gründen nicht, die man dann doch aber in den Protokolldateien vom Webserver nachlesen kann.
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 09:03
von chpo7234
Achja, Log-Dateien...
Folgender Eintrag ließ sich auf den Fehler zurückführen:
File "/opt//omd/sites/usrname/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/patch.py", line 31, in <module>
from __builtin__ import property as bltin_property
ImportError: No module named '__builtin__'
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 09:40
von BlackJack
@chpo7234: Ähm das ist fast unmöglich weil das Modul im Python (2) Interpreter fest eingebaut ist. Da kann es also nicht einmal Probleme geben mit Pfaden oder Dateien. Kann es sein dass Du einem Python 3 irgendwie versuchst die Pfade zu einer Python 2 Installation als Modulsuchpfad unterzujubeln‽ Ebenfalls komisch ist das `multiprocessing` ein EGG ist, denn das ist eigentlich Bestandteil der Standardbibliothek. Wo kommt das her?
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 10:35
von chpo7234
Sorry, hier noch mal der vollständige Fehlerlog:
Traceback (most recent call last):
File "tpe.py", line 7, in <module>
import concurrent.futures #multithread => Fuehrt PHP nicht aus!
File "/usr/lib/python3.4/concurrent/futures/__init__.py", line 17, in <module>
from concurrent.futures.process import ProcessPoolExecutor
File "/usr/lib/python3.4/concurrent/futures/process.py", line 53, in <module>
import multiprocessing
File "/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/__init__.py", line 64, in <module>
import multiprocessing.patch
File "/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/patch.py", line 31, in <module>
from __builtin__ import property as bltin_property
ImportError: No module named '__builtin__'
Wenn ich das future-Modul in irgendein Python-Script einbinde, denn lässt es sich über die Bash NUR über python3 öffnen. Über PHP funktioniert es über den Befehl "python3" nicht: denn kommt halt der Error-Log. Ich habe soweit eigentlich keine Pfade zugewiesen. Im php führe ich die Datei wie folgt aus: exec("python3 Dateiname parameter1")
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 11:28
von BlackJack
@chpo7234: Für Python 2 muss man das `concurrent.futures`-Modul nachträglich installieren, bei Python 3 ist es bereits in der Standardbibliothek.
Deine Python (3) Installation oder die Konfiguration Deines Systems ist kaputt, denn das hier
/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg
sollte nicht existieren. Das ist ganz klar ein Modul/Package für Python 2.7 und hat im Modulsuchpfad von Python 3 nichts zu suchen.
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 14:08
von chpo7234
Danke, werde ich mich noch mal mit beschäftigen!
Bis jetzt habe ich Code aus folgendem Artikel abgeleitet:
http://www.python-kurs.eu/threads.php (letztes Beispiel)
Irgendwie habe ich jetzt aber nicht das Gefühl, dass die Threads asynchron abgearbeitet werden. Vor allem, da sie mir auch, wie zuvor eingelesen, wieder ausgegeben werden. Das heißt: Ausgabe beginnt mit erster IP und endet mit letzter. Hat hier sonst jemand eine andere Url oder einen anderen Vorschlag für mich?
60 Sekunden für 500 Pings und SNMP-Abfragen im Multithread halte ich irgendwie für zu lange..
Ich bin ein wirklicher Python-Anfänger und mir ist es auch ein wenig unangenehm, aber ich poste trotzdem mal mein Code.
Code: Alles auswählen
import os # ping
import re # regular expression
import threading # Multithreading
import sys # Parameter-UEbergabe
import socket # rDNS
import subprocess
import ipconvert as conv
from pysnmp.entity.rfc3413.oneliner import cmdgen # SNMP
wordwrap = "<br>"
space = " => "
###################################################################################
##
## Methode: Check SNMP- und rDNS-Status
##
###################################################################################
def info_rDNS_SNMP(ip):
try:
rDNS = socket.gethostbyaddr(ip)[0]
except:
rDNS = " "
cmdGen = cmdgen.CommandGenerator()
errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
cmdgen.CommunityData("public"),
cmdgen.UdpTransportTarget((ip, 161), timeout=1, retries=0),
"1.3.6.1.2.1.1.1.0"
)
if errorIndication or errorStatus:
return rDNS
else:
return rDNS + space + varBinding[0][1]
#####################################################################################
##
## Klasse: IP-Check
##
#####################################################################################
class ip_check(threading.Thread):
############################
## Initialisierung
############################
def __init__ (self,ip):
threading.Thread.__init__(self)
self.ip = ip
self.__successful_pings = -1
############################
## Multithread
############################
def run(self):
ping_out = os.popen("ping -q -c2 -W1 "+self.ip,"r")
while True:
line = ping_out.readline()
if not line: break
n_received = re.findall(received_packages,line)
if n_received:
self.__successful_pings = int(n_received[0])
############################
## Ping-Ergebnis
############################
def status(self):
if self.__successful_pings == 0:
return "no response"
elif self.__successful_pings == 1:
return "alive, but 50 % package loss"
elif self.__successful_pings == 2: #Host erreichbar
return info_rDNS_SNMP(self.ip)
else:
return "shouldn't occur"
######################################################################################
##
## Pruefe IP's
##
######################################################################################
received_packages = re.compile(r"(\d) received")
check_results = []
results = []
string_results = ""
###############################
## Checke IP-Addressbereich
###############################
start = int(conv.ip2long(sys.argv[1]))
end = int(conv.ip2long(sys.argv[2]))
for lv in range(start,end+1):
ip = conv.long2ip(lv)
current = ip_check(ip)
check_results.append(current)
current.start()
################################
## Ergebnisse in Liste speichern
################################
for el in check_results:
el.join()
results.append(string_results + el.ip + " : " + el.status())
#results.sort()
#for entry in results:
# print(entry)
Lieben Gruß
Re: Timeout setzen?
Verfasst: Donnerstag 1. Oktober 2015, 15:28
von BlackJack
@chpo7234: Doch die werden (teilweise) asynchron abgearbeitet. Die Reihenfolge der Ausgabe kommt dadurch zustande das Du die Ergebnisse von den Thread-Exemplaren in der Reihenfolge abfragst in der sie erstellt und gestartet wurden. Da muss dann die Ausgabe eventuell auf ein Ergebnis warten, aber an der Gesamtlaufzeit ändert das nichts. Allerdings werden SNMP-Abfragen alle nacheinander im Hauptthread ausgeführt statt parallel, denn der Code wird ja erst ausgeführt wenn `status()` aufgerufen wird.
Anmerkungen zum Quelltext: Formatierung und Namensschreibweisen entsprechen teilweise nicht dem
Style Guide for Python Code.
Diese umrahmten Kommentare zerstören IMHO den Lesefluss ungemein. Der Inhalt ist zum Teil auch total überflüssig. Man muss vor einer Klasse nicht kommentieren dass das eine Klasse ist oder vor einer `__init__()` dass dort initialisiert wird.
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer `main()`-Funktion.
Keine nackten ``except:``\s verwenden. Da wird *alles* behandelt, auch Sachen mit denen Du gar nicht rechnest und für die die Behandlung unpassend ist.
Zwei führende Unterstriche bei Attributen sind dazu da um Namenskollisionen bei Mehrfachvererbung oder tiefen Typhierharchien zu vermeiden. Beides Sachen die weder hier noch allgemein in Python oft gemacht werden. Implementierungsdetails werden mit *einem* führenden Unterstrich gekennzeichnet.
Als ”(noch) nichts”-Wert sollte man keinen speziellen Wert nehmen der vom Typ her auch mit einem tatsächlichen Wert passen würde. In Python gibt es den Wert `None` für so etwas. Ausserdem ist hier ein Fehler. Die Anzahl wird bei Dir mit -1 initialisiert, aber wenn von ``ping`` keine Ausgabe kommt auf die der reguläre Ausdruck zutrifft, dann bleibt der Wert bei -1 und führt dann bei `status()` zu einem Rückgabewert der eher eine Ausnahme sein sollte.
`re.findall()` macht keinen Sinn wenn man nur den ersten Treffer überhaupt verarbeitet.
Namen sollte man erst definieren wenn man sie benötigt und nicht auf Vorrat irgendwo am Anfang einer Funktion. Das ist unübersichtlich und bei Programmänderungen vergisst man gerne mal solche Definitionen und sammelt so Müll an.
Man sollte keine Abkürzungen verwenden die nicht allgemein bekannt sind. Ich rätsele immer noch was `lv` bedeuten sollte. Auch `el` ist nicht schön.
`string_results` hat keinen Effekt. Eine leere Zeichenkette kann man so oft ”addieren” wie man lustig ist, das ändert nichts am Ergebnis. Der Name lässt auch vermuten, dass das eigentlich etwas anderes sein/werden sollte.
Wenn das Ergebnis an ein anderes Programm übermittelt werden soll, würde ich keinen Text verwenden und auch nicht krude HTML-Teile ausgeben sondern ein verbreitetes Serialisierungsformat verwenden wie beispielsweise JSON.
Ich lande dann bei so etwas (ungetestet):
Code: Alles auswählen
import json
import re
import socket
import sys
from collections import OrderedDict
from subprocess import check_output
from threading import Thread
import ipconvert
from pysnmp.entity.rfc3413.oneliner import cmdgen
ARROW = '=>'
def check_snmp(ip):
try:
reverse_dns = socket.gethostbyaddr(ip)[0]
except socket.error:
reverse_dns = ' '
command_generator = cmdgen.CommandGenerator()
error_indication, error_status, _, var_binding = command_generator.getCmd(
cmdgen.CommunityData('public'),
cmdgen.UdpTransportTarget((ip, 161), timeout=1, retries=0),
'1.3.6.1.2.1.1.1.0'
)
if error_indication or error_status:
return reverse_dns
else:
return '{0} {1} {2}'.format(reverse_dns, ARROW, var_binding[0][1])
class IpCheck(Thread):
RECEIVED_PACKAGES_RE = re.compile(r'(\d+) received')
def __init__(self, ip):
Thread.__init__(self)
self.ip = ip
self.result = None
def run(self):
match = self.RECEIVED_PACKAGES_RE.search(
check_output(['ping', '-q', '-c2', '-W1', self.ip])
)
successful_ping_count = int(match.group(1)) if match else 0
if successful_ping_count == 0:
self.result = 'no response'
elif successful_ping_count == 1:
self.result = 'alive, but 50% package loss'
elif successful_ping_count == 2:
self.result = check_snmp(self.ip)
else:
assert False
def ip_range(start, end):
return (
ipconvert.long2ip(i)
for i in range(ipconvert.ip2long(start), ipconvert.ip2long(end) + 1)
)
def main():
start = sys.argv[1]
end = sys.argv[2]
ip_checks = []
for ip in ip_range(start, end):
ip_check = IpCheck(ip)
ip_check.start()
ip_checks.append(ip_check)
result = OrderedDict()
for ip_check in ip_checks:
ip_check.join()
result[ip_check.ip] = ip_check.result
print(json.dumps(result))
if __name__ == '__main__':
main()