#!/usr/bin/python2.7
"""
Re-connect interfaces of running VMs to their bridges.
"""
# Copyright 2010-2014 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

import os
import sys
import libvirt
import libxml2
from optparse import OptionParser
import subprocess

SYS_NET = "/sys/class/net"


class Repair(object):
	"""
	Re-connect interfaces of running VMs to their bridges.
	"""

	def __init__(self, verbose=False):
		self.verbose = verbose
		self.bridges = {}

	def debug(self, msg, *args):
		"""
		Print debug message (if enabled).
		"""
		if self.verbose:
			print >> sys.stderr, msg % args

	def process_iface(self, bridge, interface):
		"""
		Add interface to bridge.
		"""
		if bridge not in self.bridges:
			self.debug('Skipping bridge %s', bridge)
			return

		if interface in self.bridges[bridge]:
			self.debug('Skip adding %s to %s', interface, bridge)
			return

		self.debug('Adding %s to %s...', interface, bridge)
		cmd = ('brctl', 'addif', bridge, interface)
		proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		stdout, stderr = proc.communicate()
		result = proc.wait()
		if result != 0:
			print >> sys.stderr, "RETURN: %d" % (result,)
			if stdout:
				print >> sys.stderr, "STDOUT:"
				print >> sys.stderr, stdout
			if stderr:
				print >> sys.stderr, "STDERR:"
				print >> sys.stderr, stderr

	def process_vm(self, dom):
		"""
		Process a single VM.
		"""
		try:
			xml = dom.XMLDesc(0)
		except libvirt.libvirtError, ex:
			print >> sys.stderr, ex
			return

		try:
			doc = libxml2.parseDoc(xml)
		except libxml2.libxmlError, ex:
			print >> sys.stderr, ex
			return
		ctxt = doc.xpathNewContext()
		try:
			ifaces = ctxt.xpathEval("/domain/devices/interface[@type='bridge']")
			for iface in ifaces:
				ctxt.setContextNode(iface)
				bridge = ctxt.xpathEval("string(source/@bridge)")
				tap = ctxt.xpathEval("string(target/@dev)")
				if not bridge or not tap:
					self.debug("Incomplete bridge in VM '%s'", dom.name())
					continue
				self.process_iface(bridge, tap)
		finally:
			doc.freeDoc()
			ctxt.xpathFreeContext()


def main():
	"""
	Re-connect interfaces of running VMs to their bridges.
	"""
	parser = OptionParser()
	parser.add_option('-b', '--bridge',
			action="append", type="str", dest="bridge", default=[],
			help="Specify bridge")
	parser.add_option('-c', '--connect',
			action="store", type="str", dest="uri", default="qemu:///system",
			help="hypervisor connection URI")
	parser.add_option('-v', '--verbose',
			action="store_true", dest="verbose", default=False,
			help="Turn on verbose mode")
	options, _args = parser.parse_args()

	repair = Repair(options.verbose)

	if not options.bridge:
		repair.debug("No bridges specified.")
		for bridge in os.listdir(SYS_NET):
			pathname = os.path.join(SYS_NET, bridge, "bridge")
			if os.path.isdir(pathname):
				repair.debug("Found bridge %s", bridge)
				options.bridge.append(bridge)

	if not options.bridge:
		parser.error('No bridges found.')

	for bridge in options.bridge:
		pathname = os.path.join(SYS_NET, bridge, "brif")
		try:
			ifaces = os.listdir(pathname)
		except OSError:
			parser.error('Not a bridge: %s' % bridge)
		repair.bridges[bridge] = set(ifaces)
		repair.debug("Bridge %s: %s", bridge, ' '.join(ifaces))

	conn = libvirt.open(options.uri)
	for dom_id in conn.listDomainsID():
		dom = conn.lookupByID(dom_id)
		repair.process_vm(dom)
	conn.close()


if __name__ == "__main__":
	main()
