commit bfc6385a0052eb5425951de21f894a3771032332 Author: Radek Novacek rnovacek@redhat.com Date: Wed Oct 1 14:28:23 2014 +0200
libvirt: add support for remote libvirt usage
config.py | 46 +++++++++++++++++++++++------------- tests/test_config.py | 22 +++++++++++++++++ tests/test_libvirtd.py | 38 +++++++++++++++++++++++++++++- virt-who-config.5 | 6 ++-- virt-who.8 | 19 +++++++++++++++ virt-who.conf | 9 ++++++- virt/libvirtd/libvirtd.py | 57 ++++++++++++++++++++++++++++++++++++++++++++- virtwho.py | 23 +++++++++++++++--- 8 files changed, 193 insertions(+), 27 deletions(-) --- diff --git a/config.py b/config.py index f366180..0df1159 100644 --- a/config.py +++ b/config.py @@ -38,7 +38,10 @@ class Config(object): self._type = type if self._type not in VIRTWHO_TYPES: raise InvalidOption('Invalid type "%s", must be one of following %s' % (self._type, ", ".join(VIRTWHO_TYPES))) - self._server = server + if server is None and self._type == 'libvirt': + self._server = '' + else: + self._server = server self._username = username self._password = password self._owner = owner @@ -60,29 +63,38 @@ class Config(object): def fromParser(self, name, parser): type = parser.get(name, "type").lower() server = username = password = owner = env = None - if type != 'libvirt': + try: server = parser.get(name, "server") + except NoOptionError: + # Use '' as libvirt url when not given, for backward compatibility + if type == 'libvirt': + server = '' + else: + raise + try: username = parser.get(name, "username") + except NoOptionError: + username = None
+ try: + password = parser.get(name, "password") + except NoOptionError: + password = None + if password is None: try: - password = parser.get(name, "password") + crypted = parser.get(name, "encrypted_password") + password = Password.decrypt(crypted) except NoOptionError: password = None - if password is None: - try: - crypted = parser.get(name, "encrypted_password") - password = Password.decrypt(crypted) - except NoOptionError: - password = None
- try: - owner = parser.get(name, "owner") - except NoOptionError: - owner = None - try: - env = parser.get(name, "env") - except NoOptionError: - env = None + try: + owner = parser.get(name, "owner") + except NoOptionError: + owner = None + try: + env = parser.get(name, "env") + except NoOptionError: + env = None return Config(name, type, server, username, password, owner, env)
@property diff --git a/tests/test_config.py b/tests/test_config.py index 59e067e..08eb4f2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -184,3 +184,25 @@ env=staging self.assertEqual(config2.password, "password") self.assertEqual(config2.owner, "root") self.assertEqual(config2.env, "staging") + + def testLibvirtConfig(self): + with open(os.path.join(self.config_dir, "test1.conf"), "w") as f: + f.write(""" +[test1] +type=libvirt +server=1.2.3.4 +username=admin +password=password +owner=root +env=staging +""") + manager = ConfigManager(self.config_dir) + self.assertEqual(len(manager.configs), 1) + config = manager.configs[0] + self.assertEqual(config.name, "test1") + self.assertEqual(config.type, "libvirt") + self.assertEqual(config.server, "1.2.3.4") + self.assertEqual(config.username, "admin") + self.assertEqual(config.password, "password") + self.assertEqual(config.owner, "root") + self.assertEqual(config.env, "staging") diff --git a/tests/test_libvirtd.py b/tests/test_libvirtd.py index 58160e2..9eff12d 100644 --- a/tests/test_libvirtd.py +++ b/tests/test_libvirtd.py @@ -20,7 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import threading from base import TestBase -from mock import patch, Mock +from mock import patch, Mock, ANY import logging
from config import Config @@ -78,3 +78,39 @@ class TestLibvirtd(TestBase): LibvirtMonitor()._callback() self.assertTrue(event.is_set()) event.clear() + + @patch('libvirt.openReadOnly') + def test_remote_hostname(self, virt): + config = Config('test', 'libvirt', 'server') + Virt.fromConfig(self.logger, config).listDomains() + virt.assert_called_with('qemu+ssh://server/system?no_tty=1') + + @patch('libvirt.openReadOnly') + def test_remote_url(self, virt): + config = Config('test', 'libvirt', 'abc://server/test') + Virt.fromConfig(self.logger, config).listDomains() + virt.assert_called_with('abc://server/test?no_tty=1') + + @patch('libvirt.openReadOnly') + def test_remote_hostname_with_username(self, virt): + config = Config('test', 'libvirt', 'server', 'user') + Virt.fromConfig(self.logger, config).listDomains() + virt.assert_called_with('qemu+ssh://user@server/system?no_tty=1') + + @patch('libvirt.openReadOnly') + def test_remote_url_with_username(self, virt): + config = Config('test', 'libvirt', 'abc://server/test', 'user') + Virt.fromConfig(self.logger, config).listDomains() + virt.assert_called_with('abc://user@server/test?no_tty=1') + + @patch('libvirt.openAuth') + def test_remote_hostname_with_username_and_password(self, virt): + config = Config('test', 'libvirt', 'server', 'user', 'pass') + Virt.fromConfig(self.logger, config).listDomains() + virt.assert_called_with('qemu+ssh://user@server/system?no_tty=1', ANY, ANY) + + @patch('libvirt.openAuth') + def test_remote_url_with_username_and_password(self, virt): + config = Config('test', 'libvirt', 'abc://server/test', 'user', 'pass') + Virt.fromConfig(self.logger, config).listDomains() + virt.assert_called_with('abc://user@server/test?no_tty=1', ANY, ANY) diff --git a/virt-who-config.5 b/virt-who-config.5 index a055912..6fbc2a7 100644 --- a/virt-who-config.5 +++ b/virt-who-config.5 @@ -11,13 +11,13 @@ Only required key is \fBtype\fR that has to have one of the allowed virtualizati Another options that could be supplied are: .TP \fBserver\fR -hostname, IP address or URL of the server that provides virtualization information (not applicable for libvirt and vdsm mode). +hostname, IP address or URL of the server that provides virtualization information (not applicable for vdsm mode). .TP \fBusername\fR -username for authentication to the server (not applicable for libvirt and vdsm mode). +username for authentication to the server (not applicable for vdsm mode). .TP \fBpassword\fR -password for authentication to the server (not applicable for libvirt and vdsm mode). +password for authentication to the server (not applicable for vdsm mode). .TP \fBencrypted_password\fR encrypted password that is generated by virt-who-password(8) utility diff --git a/virt-who.8 b/virt-who.8 index a189e5e..f4ab3c6 100644 --- a/virt-who.8 +++ b/virt-who.8 @@ -49,6 +49,25 @@ Report host/guest associations to the Subscription Asset Manager [default] \fB--satellite\fR Report host/guest associations to the Satellite .IP +.SS Libvirt options +.IP +Use this options with \fB--libvirt\fR +.TP +\fB--libvirt-owner\fR=\fIOWNER\fR +Organization who has purchased subscriptions of the products, same as current system registration by default +.TP +\fB--libvirt-env\fR=\fIENV\fR +Environment where the libvirt server belongs to, same as current system registration by default +.TP +\fB--libvirt-server\fR=\fISERVER\fR +URL of the remote libvirt server to connect to, local server by default +.TP +\fB--libvirt-username\fR=\fIUSERNAME\fR +Username for connecting to libvirt, username of current user by default +.TP +\fB--libvirt-password\fR=\fIPASSWORD\fR +Password for connecting to libvirt +.IP .SS vCenter/ESX options .IP Use this options with \fB--esx\fR diff --git a/virt-who.conf b/virt-who.conf index fa68c7a..eff8133 100644 --- a/virt-who.conf +++ b/virt-who.conf @@ -39,7 +39,14 @@ VIRTWHO_DEBUG=0 # Register guest using Hyper-V #VIRTWHO_HYPERV=0
-# Option for ESX mode +# Options for Libvirt mode +#VIRTWHO_LIBVIRT_OWNER= +#VIRTWHO_LIBVIRT_ENV= +#VIRTWHO_LIBVIRT_SERVER= +#VIRTWHO_LIBVIRT_USERNAME= +#VIRTWHO_LIBVIRT_PASSWORD= + +# Options for ESX mode #VIRTWHO_ESX_OWNER= #VIRTWHO_ESX_ENV= #VIRTWHO_ESX_SERVER= diff --git a/virt/libvirtd/libvirtd.py b/virt/libvirtd/libvirtd.py index a46869c..60fdb32 100644 --- a/virt/libvirtd/libvirtd.py +++ b/virt/libvirtd/libvirtd.py @@ -22,6 +22,7 @@ import time import logging import libvirt import threading +import urlparse
import virt
@@ -113,6 +114,18 @@ class LibvirtMonitor(object): return self.eventLoopThread is not None and self.eventLoopThread.isAlive()
+def libvirt_cred_request(credentials, config): + """ Callback function for requesting credentials from libvirt """ + for credential in credentials: + if credential[0] == libvirt.VIR_CRED_AUTHNAME: + credential[4] = config.username + elif credential[0] == libvirt.VIR_CRED_PASSPHRASE: + credential[4] = config.password + else: + return -1 + return 0 + + class Libvirtd(virt.DirectVirt): """ Class for interacting with libvirt. """ CONFIG_TYPE = "libvirt" @@ -120,13 +133,55 @@ class Libvirtd(virt.DirectVirt): def __init__(self, logger, config, registerEvents=True): self.changedCallback = None self.logger = logger + self.config = config self.registerEvents = registerEvents libvirt.registerErrorHandler(lambda ctx, error: None, None)
+ def _get_url(self): + if self.config.server: + scheme = username = netloc = path = None + url = self.config.server + if "//" not in url: + url = "//" + url + splitted_url = urlparse.urlsplit(url) + + netloc = splitted_url.netloc + + if splitted_url.scheme: + scheme = splitted_url.scheme + else: + self.logger.info("Protocol is not specified in libvirt url, using qemu+ssh://") + scheme = 'qemu+ssh' + + if self.config.username: + username = self.config.username + elif splitted_url.username: + username = splitted_url.username + + if len(splitted_url.path) > 1: + path = splitted_url.path + else: + self.logger.info("Libvirt path is not specified in the url, using /system") + path = '/system' + + return "%(scheme)s://%(username)s%(netloc)s%(path)s?no_tty=1" % { + 'username': ("%s@" % username) if username else '', + 'scheme': scheme, + 'netloc': netloc, + 'path': path + } + return '' + def _connect(self): + url = self._get_url() + self.logger.info("Using libvirt url: %s", url if url else '""') monitor = LibvirtMonitor() try: - self.virt = libvirt.openReadOnly('') + if self.config.password: + auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE], libvirt_cred_request, self.config] + self.virt = libvirt.openAuth(url, auth, libvirt.VIR_CONNECT_RO) + else: + self.virt = libvirt.openReadOnly(url) except libvirt.libvirtError, e: self.logger.exception("Error in libvirt backend") raise virt.VirtError(str(e)) diff --git a/virtwho.py b/virtwho.py index 81f9c2a..783bad8 100644 --- a/virtwho.py +++ b/virtwho.py @@ -220,6 +220,14 @@ def parseOptions(): managerGroup.add_option("--satellite", action="store_const", dest="smType", const="satellite", help="Report host/guest associations to the Satellite") parser.add_option_group(managerGroup)
+ libvirtGroup = OptionGroup(parser, "Libvirt options", "Use this options with --libvirt") + libvirtGroup.add_option("--libvirt-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products, default is owner of current system") + libvirtGroup.add_option("--libvirt-env", action="store", dest="env", default="", help="Environment where the vCenter server belongs to, default is environment of current system") + libvirtGroup.add_option("--libvirt-server", action="store", dest="server", default="", help="URL of the libvirt server to connect to, default is empty for libvirt on local computer") + libvirtGroup.add_option("--libvirt-username", action="store", dest="username", default="", help="Username for connecting to the libvirt daemon") + libvirtGroup.add_option("--libvirt-password", action="store", dest="password", default="", help="Password for connecting to the libvirt daemon") + parser.add_option_group(libvirtGroup) + esxGroup = OptionGroup(parser, "vCenter/ESX options", "Use this options with --esx") esxGroup.add_option("--esx-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products") esxGroup.add_option("--esx-env", action="store", dest="env", default="", help="Environment where the vCenter server belongs to") @@ -303,14 +311,14 @@ def parseOptions(): if env in ["1", "true"]: options.virtType = "hyperv"
- def checkEnv(variable, option, name): + def checkEnv(variable, option, name, required=True): """ If `option` is empty, check enviromental `variable` and return its value. Exit if it's still empty """ if len(option) == 0: option = os.getenv(variable, "").strip() - if len(option) == 0: + if required and len(option) == 0: logger.error("Required parameter '%s' is not set, exitting" % name) sys.exit(1) return option @@ -321,6 +329,14 @@ def parseOptions(): if len(options.sat_password) == 0: options.sat_password = os.getenv("VIRTWHO_SATELLITE_PASSWORD", "")
+ if options.virtType == "libvirt": + options.owner = checkEnv("VIRTWHO_LIBVIRT_OWNER", options.owner, "owner", required=False) + options.env = checkEnv("VIRTWHO_LIBVIRT_ENV", options.env, "env", required=False) + options.server = checkEnv("VIRTWHO_LIBVIRT_SERVER", options.server, "server", required=False) + options.username = checkEnv("VIRTWHO_LIBVIRT_USERNAME", options.username, "username", required=False) + if len(options.password) == 0: + options.password = os.getenv("VIRTWHO_LIBVIRT_PASSWORD", "") + if options.virtType == "esx": options.owner = checkEnv("VIRTWHO_ESX_OWNER", options.owner, "owner") options.env = checkEnv("VIRTWHO_ESX_ENV", options.env, "env") @@ -394,8 +410,7 @@ class PIDLock(object): except (KeyboardInterrupt, SystemExit): raise except Exception, e: - if logger is not None: - logger.error("Unable to create pid file: %s" % str(e)) + print >>sys.stderr, "Unable to create pid file: %s" % str(e)
def __exit__(self, exc_type, exc_value, traceback): try: