libvirt: enhance libvirt to set admin password

Libvirt 1.2.16 provides to set to the domain an user
password. This commit enhances Nova to use this new API
and let users request to set the admin password.
By default we will use root as user but if os_type windows
is specified we will use Administrator

Change-Id: I6bd2e67716222b3aa0d777d4a9411dc7c4a93a84
Implement: blueprint libvirt-set-admin-password
This commit is contained in:
Sahid Orentino Ferdjaoui 2015-06-23 08:09:51 -04:00
parent cd8e67b31f
commit 520a6b9285
7 changed files with 143 additions and 0 deletions

View File

@ -1824,6 +1824,10 @@ class QemuGuestAgentNotEnabled(Invalid):
msg_fmt = _('QEMU guest agent is not enabled') msg_fmt = _('QEMU guest agent is not enabled')
class SetAdminPasswdNotSupported(Invalid):
msg_fmt = _('Set admin password is not supported')
class MemoryPageSizeInvalid(Invalid): class MemoryPageSizeInvalid(Invalid):
msg_fmt = _("Invalid memory page size '%(pagesize)s'") msg_fmt = _("Invalid memory page size '%(pagesize)s'")

View File

@ -615,6 +615,9 @@ class Domain(object):
def detachDeviceFlags(self, xml, flags): def detachDeviceFlags(self, xml, flags):
self.detachDevice(xml) self.detachDevice(xml)
def setUserPassword(self, user, password, flags=0):
pass
def XMLDesc(self, flags): def XMLDesc(self, flags):
disks = '' disks = ''
for disk in self._def['devices']['disks']: for disk in self._def['devices']['disks']:

View File

@ -680,6 +680,93 @@ class LibvirtConnTestCase(test.NoDBTestCase):
break break
self.assertFalse(version_arg_found) self.assertFalse(version_arg_found)
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch.object(host.Host,
'has_min_version', return_value=True)
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
def test_set_admin_password(self, mock_get_guest, ver, mock_image):
self.flags(virt_type='kvm', group='libvirt')
instance = objects.Instance(**self.test_instance)
mock_image.return_value = {"properties": {
"hw_qemu_guest_agent": "yes"}}
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
mock_get_guest.return_value = mock_guest
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
drvr.set_admin_password(instance, "123")
mock_guest.set_user_password.assert_called_once_with("root", "123")
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch.object(host.Host,
'has_min_version', return_value=True)
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
def test_set_admin_password_windows(self, mock_get_guest, ver, mock_image):
self.flags(virt_type='kvm', group='libvirt')
instance = objects.Instance(**self.test_instance)
instance.os_type = "windows"
mock_image.return_value = {"properties": {
"hw_qemu_guest_agent": "yes"}}
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
mock_get_guest.return_value = mock_guest
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
drvr.set_admin_password(instance, "123")
mock_guest.set_user_password.assert_called_once_with(
"Administrator", "123")
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch.object(host.Host,
'has_min_version', return_value=False)
def test_set_admin_password_bad_version(self, mock_svc, mock_image):
self.flags(virt_type='kvm', group='libvirt')
instance = objects.Instance(**self.test_instance)
mock_image.return_value = {"properties": {
"hw_qemu_guest_agent": "yes"}}
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.assertRaises(exception.SetAdminPasswdNotSupported,
drvr.set_admin_password, instance, "123")
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch.object(host.Host,
'has_min_version', return_value=True)
def test_set_admin_password_bad_hyp(self, mock_svc, mock_image):
self.flags(virt_type='foo', group='libvirt')
instance = objects.Instance(**self.test_instance)
mock_image.return_value = {"properties": {
"hw_qemu_guest_agent": "yes"}}
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.assertRaises(exception.SetAdminPasswdNotSupported,
drvr.set_admin_password, instance, "123")
@mock.patch.object(host.Host,
'has_min_version', return_value=True)
def test_set_admin_password_guest_agent_not_running(self, mock_svc):
self.flags(virt_type='kvm', group='libvirt')
instance = objects.Instance(**self.test_instance)
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.assertRaises(exception.QemuGuestAgentNotEnabled,
drvr.set_admin_password, instance, "123")
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch.object(host.Host,
'has_min_version', return_value=True)
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
def test_set_admin_password_error(self, mock_get_guest, ver, mock_image):
self.flags(virt_type='kvm', group='libvirt')
instance = objects.Instance(**self.test_instance)
mock_image.return_value = {"properties": {
"hw_qemu_guest_agent": "yes"}}
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
mock_guest.set_user_password.side_effect = (
fakelibvirt.libvirtError("error"))
mock_get_guest.return_value = mock_guest
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.assertRaises(exception.NovaException,
drvr.set_admin_password, instance, "123")
@mock.patch.object(objects.Service, 'get_by_compute_host') @mock.patch.object(objects.Service, 'get_by_compute_host')
def test_set_host_enabled_with_disable(self, mock_svc): def test_set_host_enabled_with_disable(self, mock_svc):
# Tests disabling an enabled host. # Tests disabling an enabled host.

View File

@ -253,6 +253,10 @@ class GuestTestCase(test.NoDBTestCase):
self.assertEqual(disk, gblock._disk) self.assertEqual(disk, gblock._disk)
self.assertEqual(self.guest, gblock._guest) self.assertEqual(self.guest, gblock._guest)
def test_set_user_password(self):
self.guest.set_user_password("foo", "123")
self.domain.setUserPassword.assert_called_once_with("foo", "123", 0)
class GuestBlockTestCase(test.NoDBTestCase): class GuestBlockTestCase(test.NoDBTestCase):

View File

@ -919,3 +919,13 @@ class LibvirtConnTestCase(_VirtDriverTestCase, test.TestCase):
def test_get_device_name_for_instance(self): def test_get_device_name_for_instance(self):
self.skipTest("Tested by the nova.tests.unit.virt.libvirt suite") self.skipTest("Tested by the nova.tests.unit.virt.libvirt suite")
@catch_notimplementederror
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch("nova.virt.libvirt.host.Host.has_min_version")
def test_set_admin_password(self, ver, mock_image):
self.flags(virt_type='kvm', group='libvirt')
mock_image.return_value = {"properties": {
"hw_qemu_guest_agent": "yes"}}
instance, network_info = self._get_running_instance(obj=True)
self.connection.set_admin_password(instance, 'p4ssw0rd')

View File

@ -394,6 +394,9 @@ MIN_QEMU_HYPERV_FEATURE_VERSION = (1, 1, 0)
# parallels driver support # parallels driver support
MIN_LIBVIRT_PARALLELS_VERSION = (1, 2, 12) MIN_LIBVIRT_PARALLELS_VERSION = (1, 2, 12)
# Ability to set the user guest password with Qemu
MIN_LIBVIRT_SET_ADMIN_PASSWD = (1, 2, 16)
class LibvirtDriver(driver.ComputeDriver): class LibvirtDriver(driver.ComputeDriver):
capabilities = { capabilities = {
@ -1413,6 +1416,34 @@ class LibvirtDriver(driver.ComputeDriver):
LOG.info(_LI("Snapshot image upload complete"), LOG.info(_LI("Snapshot image upload complete"),
instance=instance) instance=instance)
def _can_set_admin_password(self, image_meta):
if (CONF.libvirt.virt_type not in ('kvm', 'qemu') or
not self._host.has_min_version(MIN_LIBVIRT_SET_ADMIN_PASSWD)):
raise exception.SetAdminPasswdNotSupported()
hw_qga = image_meta.properties.get('hw_qemu_guest_agent', '')
if not strutils.bool_from_string(hw_qga):
raise exception.QemuGuestAgentNotEnabled()
def set_admin_password(self, instance, new_pass):
image_meta = objects.ImageMeta.from_instance(instance)
self._can_set_admin_password(image_meta)
guest = self._host.get_guest(instance)
if instance.os_type == "windows":
user = "Administrator"
else:
user = "root"
try:
guest.set_user_password(user, new_pass)
except libvirt.libvirtError as ex:
error_code = ex.get_error_code()
msg = (_('Error from libvirt while set password for username '
'"%(user)s": [Error Code %(error_code)s] %(ex)s')
% {'user': user, 'error_code': error_code, 'ex': ex})
raise exception.NovaException(msg)
def _can_quiesce(self, instance, image_meta): def _can_quiesce(self, instance, image_meta):
if (CONF.libvirt.virt_type not in ('kvm', 'qemu') or if (CONF.libvirt.virt_type not in ('kvm', 'qemu') or
not self._host.has_min_version(MIN_LIBVIRT_FSFREEZE_VERSION)): not self._host.has_min_version(MIN_LIBVIRT_FSFREEZE_VERSION)):

View File

@ -256,6 +256,10 @@ class Guest(object):
"""Returns a block device wrapper for disk.""" """Returns a block device wrapper for disk."""
return BlockDevice(self, disk) return BlockDevice(self, disk)
def set_user_password(self, user, new_pass):
"""Configures a new user password."""
self._domain.setUserPassword(user, new_pass, 0)
class BlockDevice(object): class BlockDevice(object):
"""Wrapper around block device API""" """Wrapper around block device API"""