Allow adding libvirt name for node

This patch adds new field in node_list config that allows
specifying name of libvirt domain associated with a node.

* Now only 'ip' is required field for node.
* Host moved from namedtuple to custom class.

Change-Id: I47410d4e77c18a3874975812e09ea3bb7f3a574d
This commit is contained in:
Anton Studenov 2017-04-21 18:08:51 +03:00
parent 2fafe5f9b9
commit 77ba7fd26f
8 changed files with 190 additions and 66 deletions

View File

@ -11,20 +11,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import logging
import random
import warnings
from os_faults.api import error
from os_faults.api.util import public
from os_faults import utils
LOG = logging.getLogger(__name__)
Host = collections.namedtuple('Host', ['ip', 'mac', 'fqdn'])
class Host(utils.ComparableMixin, utils.ReprMixin):
ATTRS = ('ip', 'mac', 'fqdn', 'libvirt_name')
def __init__(self, ip, mac=None, fqdn=None, libvirt_name=None):
self.ip = ip
self.mac = mac
self.fqdn = fqdn
self.libvirt_name = libvirt_name
class NodeCollection(object):
class NodeCollection(utils.ReprMixin):
ATTRS = ('hosts', )
def __init__(self, cloud_management=None, hosts=None):
self.cloud_management = cloud_management
@ -34,9 +45,6 @@ class NodeCollection(object):
def hosts(self):
return sorted(self._hosts)
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, repr(self.hosts))
def __len__(self):
return len(self._hosts)

View File

@ -68,49 +68,51 @@ class LibvirtDriver(power_management.PowerDriver):
return self._cached_conn
def _find_domain_by_mac_address(self, mac_address):
def _find_domain_by_host(self, host):
for domain in self.conn.listAllDomains():
if mac_address in domain.XMLDesc():
if host.libvirt_name and host.libvirt_name == domain.name():
return domain
if host.mac and host.mac in domain.XMLDesc():
return domain
raise error.PowerManagementError(
'Domain with MAC address %s not found!' % mac_address)
'Domain not found for host %s.' % host)
def supports(self, host):
try:
self._find_domain_by_mac_address(host.mac)
self._find_domain_by_host(host)
except error.PowerManagementError:
return False
return True
def poweroff(self, host):
LOG.debug('Power off domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain = self._find_domain_by_host(host)
LOG.debug('Power off domain with name: %s', host.mac)
domain.destroy()
LOG.info('Domain powered off: %s', host.mac)
def poweron(self, host):
LOG.debug('Power on domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain = self._find_domain_by_host(host)
LOG.debug('Power on domain with name: %s', domain.name())
domain.create()
LOG.info('Domain powered on: %s', host.mac)
LOG.info('Domain powered on: %s', domain.name())
def reset(self, host):
LOG.debug('Reset domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain = self._find_domain_by_host(host)
LOG.debug('Reset domain with name: %s', domain.name())
domain.reset()
LOG.info('Domain reset: %s', host.mac)
LOG.info('Domain reset: %s', domain.name())
def shutdown(self, host):
LOG.debug('Shutdown domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain = self._find_domain_by_host(host)
LOG.debug('Shutdown domain with name: %s', domain.name())
domain.shutdown()
LOG.info('Domain is off: %s', host.mac)
LOG.info('Domain is off: %s', domain.name())
def snapshot(self, host, snapshot_name, suspend):
LOG.debug('Create snapshot "%s" for domain with MAC address: %s',
snapshot_name, host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain = self._find_domain_by_host(host)
LOG.debug('Create snapshot "%s" for domain with name: %s',
snapshot_name, domain.name())
if suspend:
domain.suspend()
domain.snapshotCreateXML(
@ -118,18 +120,18 @@ class LibvirtDriver(power_management.PowerDriver):
snapshot_name))
if suspend:
domain.resume()
LOG.debug('Created snapshot "%s" for domain with MAC address: %s',
snapshot_name, host.mac)
LOG.debug('Created snapshot "%s" for domain with name: %s',
snapshot_name, domain.name())
def revert(self, host, snapshot_name, resume):
LOG.debug('Revert snapshot "%s" for domain with MAC address: %s',
snapshot_name, host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain = self._find_domain_by_host(host)
LOG.debug('Revert snapshot "%s" for domain with name: %s',
snapshot_name, domain.name())
snapshot = domain.snapshotLookupByName(snapshot_name)
if domain.isActive():
domain.destroy()
domain.revertToSnapshot(snapshot)
if resume:
domain.resume()
LOG.debug('Reverted snapshot "%s" for domain with MAC address: %s',
snapshot_name, host.mac)
LOG.debug('Reverted snapshot "%s" for domain with name: %s',
snapshot_name, domain.name())

View File

@ -32,6 +32,7 @@ class NodeListDiscover(node_discover.NodeDiscover):
- ip: 10.0.0.51
mac: aa:bb:cc:dd:ee:01
fqdn: node1.local
libvirt_name: node1
- ip: 10.0.0.52
mac: aa:bb:cc:dd:ee:02
fqdn: node2.local
@ -54,16 +55,16 @@ class NodeListDiscover(node_discover.NodeDiscover):
'pattern': utils.MACADDR_REGEXP,
},
'fqdn': {'type': 'string'},
'libvirt_name': {'type': 'string'},
},
'required': ['ip', 'mac', 'fqdn'],
'required': ['ip'],
'additionalProperties': False,
},
'minItems': 1,
}
def __init__(self, conf):
self.hosts = [node_collection.Host(ip=host['ip'], mac=host['mac'],
fqdn=host['fqdn']) for host in conf]
self.hosts = [node_collection.Host(**host) for host in conf]
def discover_hosts(self):
"""Discover hosts

View File

@ -52,14 +52,14 @@ class NodeCollectionTestCase(test.TestCase):
hosts=copy.deepcopy(self.hosts))
self.hosts2 = [
node_collection.Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1',
fqdn='node1.com'),
node_collection.Host(ip='10.0.0.7', mac='09:7b:74:90:63:c7',
fqdn='node6.com'),
node_collection.Host(ip='10.0.0.3', mac='09:7b:74:90:63:c2',
fqdn='node2.com'),
node_collection.Host(ip='10.0.0.6', mac='09:7b:74:90:63:c6',
fqdn='node5.com'),
node_collection.Host(ip='10.0.0.7', mac='09:7b:74:90:63:c7',
fqdn='node6.com'),
node_collection.Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1',
fqdn='node1.com'),
]
self.node_collection2 = node_collection.NodeCollection(

View File

@ -101,7 +101,7 @@ class PowerManagerTestCase(test.TestCase):
self.pm.reset, self.hosts)
self.assertEqual("No supported driver found for host "
"Host(ip='10.0.0.3', mac='09:7b:74:90:63:c2', "
"fqdn='node2.com')", str(exc))
"fqdn='node2.com', libvirt_name=None)", str(exc))
def test_run_no_drivers(self):
self.pm = power_management.PowerManager()
@ -110,4 +110,4 @@ class PowerManagerTestCase(test.TestCase):
self.pm.reset, self.hosts)
self.assertEqual("No supported driver found for host "
"Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1', "
"fqdn='node1.com')", str(exc))
"fqdn='node1.com', libvirt_name=None)", str(exc))

View File

@ -49,19 +49,35 @@ class LibvirtDriverTestCase(test.TestCase):
self.assertEqual(conn, 'some cached connection')
@mock.patch(DRIVER_PATH + '.LibvirtDriver._get_connection')
def test__find_domain_by_mac_address(self, mock__get_connection):
def test__find_domain_by_host_mac(self, mock__get_connection):
host = node_collection.Host(ip='10.0.0.2', mac=':54:00:f9:b8:f9')
domain1 = mock.MagicMock()
domain1.XMLDesc.return_value = '52:54:00:ab:64:42'
domain2 = mock.MagicMock()
domain2.XMLDesc.return_value = '52:54:00:f9:b8:f9'
self.driver.conn.listAllDomains.return_value = [domain1, domain2]
domain = self.driver._find_domain_by_mac_address('52:54:00:f9:b8:f9')
domain = self.driver._find_domain_by_host(host)
self.assertEqual(domain, domain2)
@mock.patch(DRIVER_PATH + '.LibvirtDriver._get_connection')
def test__find_domain_by_mac_address_domain_not_found(
def test__find_domain_by_host_name(self, mock__get_connection):
host = node_collection.Host(ip='10.0.0.2', libvirt_name='foo')
domain1 = mock.MagicMock()
domain1.XMLDesc.return_value = '52:54:00:ab:64:42'
domain1.name.return_value = 'bar'
domain2 = mock.MagicMock()
domain2.XMLDesc.return_value = '52:54:00:f9:b8:f9'
domain2.name.return_value = 'foo'
self.driver.conn.listAllDomains.return_value = [domain1, domain2]
domain = self.driver._find_domain_by_host(host)
self.assertEqual(domain, domain2)
@mock.patch(DRIVER_PATH + '.LibvirtDriver._get_connection')
def test__find_domain_by_host_domain_not_found(
self, mock__get_connection):
host = node_collection.Host(ip='10.0.0.2')
domain1 = mock.MagicMock()
domain1.XMLDesc.return_value = '52:54:00:ab:64:42'
domain2 = mock.MagicMock()
@ -69,8 +85,7 @@ class LibvirtDriverTestCase(test.TestCase):
self.driver.conn.listAllDomains.return_value = [domain1, domain2]
self.assertRaises(error.PowerManagementError,
self.driver._find_domain_by_mac_address,
'00:00:00:00:00:01')
self.driver._find_domain_by_host, host)
@mock.patch(DRIVER_PATH + '.LibvirtDriver._get_connection')
def test_supports(self, mock__get_connection):
@ -88,25 +103,25 @@ class LibvirtDriverTestCase(test.TestCase):
self.assertFalse(self.driver.supports(self.host))
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
@ddt.data(('poweroff', 'destroy'), ('poweron', 'create'),
('reset', 'reset'), ('shutdown', 'shutdown'))
def test_driver_actions(self, actions, mock__find_domain_by_mac_address):
def test_driver_actions(self, actions, mock__find_domain_by_host):
getattr(self.driver, actions[0])(self.host)
domain = mock__find_domain_by_mac_address.return_value
domain = mock__find_domain_by_host.return_value
getattr(domain, actions[1]).assert_called_once_with()
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test_snapshot(self, mock__find_domain_by_mac_address):
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
def test_snapshot(self, mock__find_domain_by_host):
self.driver.snapshot(self.host, 'foo', suspend=False)
domain = mock__find_domain_by_mac_address.return_value
domain = mock__find_domain_by_host.return_value
domain.snapshotCreateXML.assert_called_once_with(
'<domainsnapshot><name>foo</name></domainsnapshot>')
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test_snapshot_suspend(self, mock__find_domain_by_mac_address):
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
def test_snapshot_suspend(self, mock__find_domain_by_host):
self.driver.snapshot(self.host, 'foo', suspend=True)
domain = mock__find_domain_by_mac_address.return_value
domain = mock__find_domain_by_host.return_value
domain.assert_has_calls((
mock.call.suspend(),
mock.call.snapshotCreateXML(
@ -114,34 +129,34 @@ class LibvirtDriverTestCase(test.TestCase):
mock.call.resume(),
))
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test_revert(self, mock__find_domain_by_mac_address):
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
def test_revert(self, mock__find_domain_by_host):
self.driver.revert(self.host, 'foo', resume=False)
domain = mock__find_domain_by_mac_address.return_value
domain = mock__find_domain_by_host.return_value
snapshot = domain.snapshotLookupByName.return_value
domain.snapshotLookupByName.assert_called_once_with('foo')
domain.revertToSnapshot.assert_called_once_with(snapshot)
self.assertFalse(domain.resume.called)
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test_revert_resume(self, mock__find_domain_by_mac_address):
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
def test_revert_resume(self, mock__find_domain_by_host):
self.driver.revert(self.host, 'foo', resume=True)
domain = mock__find_domain_by_mac_address.return_value
domain = mock__find_domain_by_host.return_value
snapshot = domain.snapshotLookupByName.return_value
domain.snapshotLookupByName.assert_called_once_with('foo')
domain.revertToSnapshot.assert_called_once_with(snapshot)
domain.resume.assert_called_once_with()
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test_revert_destroy(self, mock__find_domain_by_mac_address):
domain = mock__find_domain_by_mac_address.return_value
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
def test_revert_destroy(self, mock__find_domain_by_host):
domain = mock__find_domain_by_host.return_value
domain.isActive.return_value = True
self.driver.revert(self.host, 'foo', resume=True)
domain.destroy.assert_called_once_with()
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test_revert_destroy_nonactive(self, mock__find_domain_by_mac_address):
domain = mock__find_domain_by_mac_address.return_value
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_host')
def test_revert_destroy_nonactive(self, mock__find_domain_by_host):
domain = mock__find_domain_by_host.return_value
domain.isActive.return_value = False
self.driver.revert(self.host, 'foo', resume=True)
self.assertFalse(domain.destroy.called)

View File

@ -85,3 +85,60 @@ class RequiredVariablesTestCase(test.TestCase):
inst.method_that_miss_variables)
msg = 'BAR, BAZ required for MyClass.method_that_miss_variables'
self.assertEqual(str(err), msg)
class MyPoint(utils.ComparableMixin):
ATTRS = ('a', 'b')
def __init__(self, a, b):
self.a = a
self.b = b
class ComparableMixinTestCase(test.TestCase):
def test_operations(self):
p1 = MyPoint(1, 'a')
p2 = MyPoint(1, 'b')
p3 = MyPoint(2, 'c')
p4 = MyPoint(2, 'c')
self.assertTrue(p1 < p2)
self.assertTrue(p1 <= p2)
self.assertFalse(p1 == p2)
self.assertFalse(p1 >= p2)
self.assertFalse(p1 > p2)
self.assertTrue(p1 != p2)
self.assertTrue(hash(p1) != hash(p2))
self.assertTrue(p2 < p3)
self.assertTrue(p2 <= p3)
self.assertFalse(p2 == p3)
self.assertFalse(p2 >= p3)
self.assertFalse(p2 > p3)
self.assertTrue(p2 != p3)
self.assertTrue(hash(p2) != hash(p3))
self.assertFalse(p3 < p4)
self.assertTrue(p3 <= p4)
self.assertTrue(p3 == p4)
self.assertTrue(p3 >= p4)
self.assertFalse(p3 > p4)
self.assertFalse(p3 != p4)
self.assertEqual(hash(p3), hash(p4))
class MyRepr(utils.ReprMixin):
REPR_ATTRS = ('a', 'b', 'c')
def __init__(self):
self.a = 'foo'
self.b = {'foo': 'bar'}
self.c = 42
class ReprMixinTestCase(test.TestCase):
def test_repr(self):
r = MyRepr()
self.assertEqual("MyRepr(a='foo', b={'foo': 'bar'}, c=42)", repr(r))

View File

@ -61,3 +61,44 @@ def require_variables(*variables):
return fn(self, *args, **kawrgs)
return wrapper
return decorator
class ComparableMixin(object):
ATTRS = ()
def _cmp_attrs(self):
return tuple(getattr(self, attr) for attr in self.ATTRS)
def __lt__(self, other):
return self._cmp_attrs() < other._cmp_attrs()
def __le__(self, other):
return self._cmp_attrs() <= other._cmp_attrs()
def __eq__(self, other):
return self._cmp_attrs() == other._cmp_attrs()
def __ge__(self, other):
return self._cmp_attrs() >= other._cmp_attrs()
def __gt__(self, other):
return self._cmp_attrs() > other._cmp_attrs()
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._cmp_attrs())
class ReprMixin(object):
ATTRS = ()
REPR_ATTRS = ()
def __repr__(self):
return '{}({})'.format(
self.__class__.__name__,
', '.join('{}={}'.format(attr, repr(getattr(self, attr)))
for attr in self.REPR_ATTRS or self.ATTRS))