Kaynağa Gözat

add comments

Helge Jung 9 yıl önce
ebeveyn
işleme
4a1195ca3d
4 değiştirilmiş dosya ile 106 ekleme ve 17 silme
  1. 61 10
      modules/ffpb.py
  2. 14 4
      modules/ffpb_monitoring.py
  3. 20 2
      modules/ffpb_nodeinfos.py
  4. 11 1
      modules/ffpb_status.py

+ 61 - 10
modules/ffpb.py

@@ -29,13 +29,15 @@ ffpb_resolver = dns.resolver.Resolver ()
 ffpb_resolver.nameservers = ['10.132.254.53']
 
 class MsgHandler(SocketServer.BaseRequestHandler):
+	"""Reads line from TCP stream and forwards it to configured IRC channels."""
+
 	def handle(self):
-		data = self.request.recv(2048).strip()
+		data = self.request.recv(2048).strip() 
 		sender = self._resolve_name (self.client_address[0])
 
 		bot = self.server.bot
 		if bot is None:
-			print("ERROR: No bot in handle() :-(")
+			print("ERROR: No bot in handle( ) :-(")
 			return
 
 		target = bot.config.core.owner
@@ -51,6 +53,9 @@ class MsgHandler(SocketServer.BaseRequestHandler):
 		bot.msg(target, "[{0}] {1}".format(sender, str(data)))
 
 	def _resolve_name (self, ip):
+		"""Resolves the host name of the given IP address
+		and strips away the suffix (.infra)?.ffpb"""
+
 		if ip.startswith ("127."):
 			return "localhost"
 
@@ -78,13 +83,16 @@ def setup(bot):
 		highscores['clients_ts'] = time.time()
 	bot.memory['highscores'] = highscores
 
+	# no need to configure anything else if the ffpb config section is missing
 	if not bot.config.has_section('ffpb'):
 		return
 
+	# open the git repository containing the peers files
 	if not bot.config.ffpb.peers_directory is None:
 		peers_repo = git.Repo(bot.config.ffpb.peers_directory)
 		assert peers_repo.bare == False
 
+	# if configured start the messaging server
 	if int(bot.config.ffpb.msg_enable) == 1:
 		host = "localhost"
 		port = 2342
@@ -100,19 +108,22 @@ def setup(bot):
 		msgserver_thread.daemon = True
 		msgserver_thread.start()
 
+	# initially fetch ALFRED data
 	alfred_method = bot.config.ffpb.alfred_method
 	ffpb_updatealfred(bot)
 
 def shutdown(bot):
 	global msgserver
 
+	# store highscores
 	if 'highscores' in bot.memory and not bot.memory['highscores'] is None:
 		bot.memory['highscores'].sync()
 		bot.memory['highscores'].close()
 		del(bot.memory['highscores'])
 
+	# shut down messaging server
 	if not msgserver is None:
-		msgserver.shutdown()
+		msgserver.shutdown() 
 		print("Closed messaging server.")
 		msgserver = None
 
@@ -120,6 +131,8 @@ def shutdown(bot):
 @willie.module.commands("hilfe")
 @willie.module.commands("man")
 def ffpb_help(bot, trigger):
+	"""Meldet häufig benutzte Funktionen."""
+
 	functions = {
 		"!ping <knoten>": "Prüfe ob der Knoten erreichbar ist.",
 		"!status": "Aktuellen Status des Netzwerks (insb. Anzahl Knoten und Clients) ausgegeben.",
@@ -140,9 +153,12 @@ def ffpb_help(bot, trigger):
 			bot.say("Hilfe zu '" + fun + "': " + functions[fun])
 			return
 
-	bot.say("Allgemeine Hilfe gibt's mit !help - ohne Parameter.")
+	bot.say("Allgemeine Hilfe gib t's mit !help - ohne Parameter.")
 
 def ffpb_findnode(bot, name):
+	"""helper: try to identify the node the user meant by the given name"""
+
+	# no name, no node
 	if name is None or len(name) == 0:
 		return None
 
@@ -152,7 +168,7 @@ def ffpb_findnode(bot, name):
 	alfred_data = bot.memory['alfred_data'] if 'alfred_data' in bot.memory else None
 
 	if not alfred_data is None:
-		# try to match MAC
+		# try to match MAC in ALFRED data
 		m = re.search("^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]$", name)
 		if (not m is None):
 			mac = m.group(0).lower()
@@ -186,7 +202,7 @@ def ffpb_findnode(bot, name):
 				else:
  					names[h] = nodeid
 
-	# still not found -> try peers_repo
+	# try peers_repo
 	if not peers_repo is None:
 		peer_name = None
 		peer_mac = None
@@ -220,16 +236,22 @@ def ffpb_findnode(bot, name):
 			# if we got exactly one candidate that might be it
 			return alfred_data[names[possibilities[0]]]
 
+	# none of the above was able to identify the requested node
 	return None
 
 def ffpb_get_alfreddata(bot, ensure_recent=True):
+	"""helper: return current ALFRED data (or None, 
+	if the data is outdated and ensure_recent is set)"""
+
 	if not 'alfred_data' in bot.memory or bot.memory['alfred_data'] is None:
 		return None
 
 	if ensure_recent:
+		# get timestamp of last ALFRED update (set by ffpb_updatealfred())
 		alfred_update = bot.memory['alfred_update'] if 'alfred_update' in bot.memory else None
 		if alfred_update is None: return None
 
+		# data must not be older than 5 minutes
 		timeout = datetime.datetime.now() - datetime.timedelta(minutes=5)
 		is_outdated = timeout > alfred_update
 		#print("ALFRED outdated? {0} (timeout={1} vs. lastupdate={2})".format(is_outdated, timeout, alfred_update))
@@ -239,14 +261,15 @@ def ffpb_get_alfreddata(bot, ensure_recent=True):
 	return bot.memory['alfred_data']
 
 def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata = True):
+	"""helper: call ffpb_findnode() and give common answers via bot if nothing has been found"""
 	if (name is None or len(name) == 0):
 		bot.reply("Grün.")
 		return None
 
 	alfred_data = ffpb_get_alfreddata(bot, ensure_recent_alfreddata)
-	if alfred_data is None:
+	if alfred_data is None and ensure_recent_alfreddata:
 		bot.say("Ich habe gerade keine (aktuellen) Informationen, daher sage ich mal lieber nichts zu '" + name + "'.")
-		return None
+		return None 
 
 	node = ffpb_findnode(bot, name)
 	if node is None:
@@ -255,6 +278,8 @@ def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata = True):
 	return node
 
 def mac2ipv6(mac, prefix=None):
+	"""Calculate IPv6 address from given MAC,
+	optionally replacing the fe80:: prefix with a given one."""
 	result = str(netaddr.EUI(mac).ipv6_link_local())
 	if (not prefix is None) and (result.startswith("fe80::")):
 		result = prefix + result[6:]
@@ -297,6 +322,8 @@ def ffpb_updatealfred(bot):
 
 @willie.module.commands('debug-alfred')
 def ffpb_debug_alfred(bot, trigger):
+	"""Zeige Stand der Alfred-Daten an."""
+
 	alfred_data = ffpb_get_alfreddata(bot)
 
 	if alfred_data is None:
@@ -306,18 +333,24 @@ def ffpb_debug_alfred(bot, trigger):
 
 @willie.module.commands('alfred-data')
 def ffpb_peerdata(bot, trigger):
+	"""Zeige Daten zum angegebenen Node an."""
+
+	# user must be a bot admin
 	if (not trigger.admin):
 		bot.say('I wont leak (possibly) sensitive data to you.')
 		return
 
+	# query must be a PM or as OP in the channel
 	if (not trigger.is_privmsg) and (not trigger.nick in bot.ops[trigger.sender]):
 		bot.say('Kein Keks? Keine Daten.')
 		return
 
+	# identify node or bail out
 	target_name = trigger.group(2)
 	node = ffpb_findnode_from_botparam(bot, target_name)
 	if node is None: return
 
+	# reply each key in the node's data
 	for key in node:
 		if key in [ 'hostname' ]: continue
 		bot.say("{0}.{1} = {2}".format(node['hostname'], key, str(node[key])))
@@ -325,6 +358,7 @@ def ffpb_peerdata(bot, trigger):
 @willie.module.interval(60)
 def ffpb_updatepeers(bot):
 	"""Aktualisiere die Knotenliste und melde das Diff"""
+
 	if peers_repo is None:
 		print('WARNING: peers_repo is None')
 		return
@@ -366,6 +400,9 @@ def ffpb_updatepeers(bot):
 		bot.msg(bot.config.ffpb.msg_target, response)
 
 def ffpb_fetch_stats(bot, url, memoryid):
+	"""Fetch a ffmap-style nodes.json from the given URL and
+	store it in the bot's memory."""
+
 	response = urllib2.urlopen(url)
 
 	data = json.load(response)
@@ -400,6 +437,8 @@ def ffpb_fetch_stats(bot, url, memoryid):
 
 @willie.module.interval(15)
 def ffpb_get_stats(bot):
+	"""Hole aktuelle Statistik-Daten, falls sich der Highscore ändert melde dies."""
+
 	highscores = bot.memory['highscores'] if 'highscores' in bot.memory else None
 	if highscores is None:
 		print('HIGHSCORE not in bot memory')
@@ -472,7 +511,8 @@ def pretty_date(time=False):
 
 @willie.module.commands('ping')
 def ffpb_ping(bot, trigger=None, target_name=None):
-	"""Ping FFPB-Knoten"""
+	"""Ping an Knoten"""
+
 	if target_name is None: target_name = trigger.group(2)
 	node = ffpb_findnode_from_botparam(bot, target_name, ensure_recent_alfreddata=False)
 	if node is None: return None
@@ -497,7 +537,8 @@ def ffpb_ping(bot, trigger=None, target_name=None):
 
 @willie.module.commands('exec-on-peer')
 def ffpb_remoteexec(bot, trigger):
-	"""Remote Execution fuer FFPB_Knoten"""
+	"""Remote Execution für Knoten (mit SSH-Key des Bots)"""
+
 	bot_params = trigger.group(2).split(' ',1)
 	if len(bot_params) != 2:
 		bot.say('Wenn du nicht sagst wo mach ich remote execution bei dir!')
@@ -507,28 +548,37 @@ def ffpb_remoteexec(bot, trigger):
 	target_name = bot_params[0]
 	target_cmd = bot_params[1]
 
+	# remote execution may only be trigger by bot admins
 	if not trigger.admin:
 		bot.say('I can haz sudo?')
 		return
 
+	# make sure remote execution is done in public
 	if trigger.is_privmsg:
 		bot.say('Bitte per Channel.')
 		return
 
+	# double-safety: user must be op in the channel, too (hoping for NickServ authentication)
 	if not trigger.nick in bot.ops[trigger.sender]:
 		bot.say('Geh weg.')
 		return
 
+	# identify requested node or bail out
 	node = ffpb_findnode_from_botparam(bot, target_name, ensure_recent_alfreddata=False)
 	if node is None: return
 
+	# use the node's first non-linklocal address
 	target = [x for x in node["network"]["addresses"] if not x.lower().startswith("fe80:")][0]
 	target_alias = node["hostname"]
 
+	# assemble SSH command
 	cmd = 'ssh -6 -l root ' + target + ' -- "' + target_cmd + '"'
 	print("REMOTE EXEC = " + cmd)
 	try:
+		# call SSH
 		result = subprocess.check_output(['ssh', '-6n', '-l', 'root', '-o', 'BatchMode=yes', '-o','StrictHostKeyChecking=no', target, target_cmd], stderr=subprocess.STDOUT, shell=False)
+
+		# fetch results and send at most 8 of them as response
 		lines = str(result).splitlines()
 
 		if len(lines) == 0:
@@ -546,3 +596,4 @@ def ffpb_remoteexec(bot, trigger):
 
 	except subprocess.CalledProcessError as e:
 		bot.say('Fehler '+str(e.returncode)+' bei exec-on-peer('+target_alias+'): ' + e.output)
+

+ 14 - 4
modules/ffpb_monitoring.py

@@ -7,11 +7,13 @@ monitored_nodes = None
 def setup(bot):
 	global monitored_nodes
 
+	# load list of monitored nodes from disk
 	monitored_nodes = shelve.open('monitorednodes', writeback=True)
 
 def shutdown(bot):
 	global monitored_nodes
 
+	# store list of monitored nodes to disk
 	if not monitored_nodes is None:
 		monitored_nodes.sync()
 		monitored_nodes.close()
@@ -19,12 +21,14 @@ def shutdown(bot):
 
 @willie.module.interval(3*60)
 def ffpb_monitor_ping(bot):
+	"""Ping jeden Knoten der gemonitort wird."""
+
 	notify_target = bot.config.core.owner
 	if (not bot.config.ffpb.msg_target is None):
 		notify_target = bot.config.ffpb.msg_target
 
 	for node in monitored_nodes:
-		added = monitored_nodes[node][0]		
+ 		added = monitored_nodes[node][0]		
 		last_status = monitored_nodes[node][1]
 		last_check = monitored_nodes[node][2]
 
@@ -32,7 +36,7 @@ def ffpb_monitor_ping(bot):
 		monitored_nodes[node] = ( added, current_status, time.time() )
 		print("Monitoring ({0}) {1} (last: {2} at {3})".format(node, current_status, last_status, time.strftime('%Y-%m-%d %H:%M', time.localtime(last_check))))
 		if last_status != current_status and (last_status or current_status):
-			if last_check is None:
+ 			if last_check is None:
 				# erster Check, keine Ausgabe
 				continue
 
@@ -43,14 +47,20 @@ def ffpb_monitor_ping(bot):
 
 @willie.module.commands('monitor')
 def ffpb_monitor(bot, trigger):
-	if not trigger.admin:
+	"""Monitoring-Funktion des Bots,
+	Subkommandos add, del, info und list"""
+
+	# command is for bot admins only
+ 	if not trigger.admin:
 		bot.say('Ich ping hier nicht für jeden durch die Weltgeschichte.')
 		return
 
+	# ensure the user gave arguments (group 2 is the concatenation of all following groups)
 	if trigger.group(2) is None or len(trigger.group(2)) == 0:
-		bot.say('Das Monitoring sagt du hast doofe Ohren.')
+		bot.say('Da s Monitoring sagt du hast doofe Ohren.')
 		return
 
+	# read additional arguments
 	cmd = trigger.group(3)
 	node = trigger.group(4)
 	if not node is None: node = str(node)

+ 20 - 2
modules/ffpb_nodeinfos.py

@@ -7,10 +7,14 @@ def setup(bot):
 
 @willie.module.commands('info')
 def ffpb_peerinfo(bot, trigger):
+	"""Informationen zu einem Knoten abrufen"""
+
+	# identify node or bail out
 	target_name = trigger.group(2)
 	node = ffpb_findnode_from_botparam(bot, target_name)
 	if node is None: return
 
+	# read node information
 	info_mac = node["network"]["mac"]
 	info_name = node["hostname"]
 
@@ -53,10 +57,14 @@ def ffpb_peerinfo(bot, trigger):
 
 @willie.module.commands('uptime')
 def ffpb_peeruptime(bot, trigger):
-	target_name = trigger.group(2)
+	"""Zeige Uptime eines Knotens"""
+
+	# identify node or bail out
+	target_name = trigger.g roup(2)
 	node = ffpb_findnode_from_botparam(bot, target_name)
 	if node is None: return
 
+	# get name and raw uptime from node
 	info_name = node["hostname"]
 	info_uptime = ''
 	u_raw = None
@@ -65,8 +73,9 @@ def ffpb_peeruptime(bot, trigger):
 	elif 'uptime' in node:
 		u_raw = node['uptime']
 
+	# pretty print uptime
 	if not u_raw is None:
-		u = int(float(u_raw))
+		u = int(float(u_raw)) 
 		d, r1 = divmod(u, 86400)
 		h, r2 = divmod(r1, 3600)
 		m, s = divmod(r2, 60)
@@ -79,16 +88,25 @@ def ffpb_peeruptime(bot, trigger):
 	else:
 		info_uptime += '?'
 
+	# reply to user
 	bot.say('uptime(\'{0}\') = {1}'.format(info_name, info_uptime))
 
 @willie.module.commands('link')
 def ffpb_peerlink(bot, trigger):
+	"""Zeige MAC und Link zur Statusseite eines Knotens"""
+
+	# identify node or bail out
 	target_name = trigger.group(2)
 	node = ffpb_findnode_from_botparam(bot, target_name)
 	if node is None: return
 
+	# get node's mac
 	info_mac = node["network"]["mac"]
 	info_name = node["hostname"]
+
+	# get node's v6 address in the mesh (derived from MAC address)
 	info_v6 = mac2ipv6(info_mac, 'fdca:ffee:ff12:132:')
+
+	# reply to user
 	bot.say('[{1}] mac {0} -> http://[{2}]/'.format(info_mac, info_name, info_v6))
 

+ 11 - 1
modules/ffpb_status.py

@@ -28,20 +28,27 @@ def ffpb_highscore(bot, trigger):
 
 @willie.module.commands('rollout-status')
 def ffpb_rolloutstatus(bot, trigger):
+	"""Zeigt eine Statistik über alle Knoten an die die gegebene
+	Firmware-Version installiert haben."""
+
+	# initialize result dictionary
 	result = { }
 	for branch in [ 'stable', 'testing' ]:
 		result[branch] = None
 	skipped = 0
 
+	# command is restricted to bot-admins via PM or OPs in the channel
 	if (not (trigger.admin and trigger.is_privmsg)) and (not trigger.nick in bot.ops[trigger.sender]):
 		bot.say('Geh zur dunklen Seite, die haben Kekse - ohne Keks kein Rollout-Status.')
 		return
 
+	# get expected firmware version from command arguments
 	expected_release = trigger.group(2)
 	if expected_release is None or len(expected_release) == 0:
 		bot.say('Von welcher Firmware denn?')
 		return
 
+	# check each node in ALFRED data
 	for nodeid in alfred_data:
 		item = alfred_data[nodeid]
 		if (not 'software' in item) or (not 'firmware' in item['software']) or (not 'autoupdater' in item['software']):
@@ -57,8 +64,9 @@ def ffpb_rolloutstatus(bot, trigger):
 		result[branch]['total'] += 1
 		match = 'count' if release == expected_release else 'not'
 		mode = 'auto' if enabled else 'manual'
-		result[branch][mode+'_'+match] += 1
+		result[branch][mode+'_ '+match] += 1
 
+	# respond to user
 	output = "Rollout von '{0}':".format(expected_release)
 	for branch in result:
 		auto_count = result[branch]['auto_count']
@@ -66,6 +74,8 @@ def ffpb_rolloutstatus(bot, trigger):
 		manual_count = result[branch]['manual_count']
 		manual_total = manual_count + result[branch]['manual_not']
 		bot.say("Rollout von '{0}': {1} = {2}/{3} per Auto-Update, {4}/{5} manuell".format(expected_release, branch, auto_count, auto_total, manual_count, manual_total))
+
+	# output count of nodes for which the autoupdater-branch and/or firmware version could not be retrieved
 	if skipped > 0:
 		bot.say("Rollout von '{0}': {1} Knoten unklar".format(expected_release, skipped))