Create multipath disk devices with libvirt driver

- add environment variable SLAVE_MULTIPATH_DISKS_COUNT:
  * default = 0 : multipath disabled
  * if 1 or more: add specified amount of disk devices that
    have the same volume file and the same serial.
    bus set to 'scsi'.
  SLAVE_MULTIPATH_DISKS_COUNT is applied only to slave nodes,
  on 'system' and 'cinder' disks. 'swift' remains on 'virtio'.
- add WWN to disk devices in node XML, if the same volume is
  attached to more than one disk device.
- added unit test for a node with attached multipath devices.
- DiskDevice model now part of libvirt driver
- Fix for warning: no MIDDLEWARE_CLASSES in settings
- imported DiskDevice in baremetal driver

Change-Id: I3e157afa8707e71ee45980fa5f09c1da35176930
blueprint: fuel-devops-multipath-disk-devices
This commit is contained in:
Anton Studenov 2016-06-01 12:49:11 +03:00
parent ed01e8f82f
commit 8667612c79
18 changed files with 314 additions and 92 deletions

View File

@ -13,6 +13,7 @@
# under the License.
from devops.models import DiskDevice
from devops.driver.baremetal.ipmi_driver import IpmiDriver as Driver
from devops.driver.baremetal.ipmi_driver \
import IpmiL2NetworkDevice as L2NetworkDevice
@ -20,4 +21,11 @@ from devops.driver.baremetal.ipmi_driver import IpmiNode as Node
from devops.driver.baremetal.ipmi_driver import IpmiVolume as Volume
from devops.models import Interface
__all__ = ['Driver', 'L2NetworkDevice', 'Volume', 'Node', 'Interface']
__all__ = [
'DiskDevice',
'Driver',
'Interface',
'L2NetworkDevice',
'Volume',
'Node',
]

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from devops.models import DiskDevice
from devops.driver.dummy.dummy_driver import DummyDriver as Driver
from devops.models import Interface
from devops.driver.dummy.dummy_driver import \
@ -20,6 +21,7 @@ from devops.driver.dummy.dummy_driver import DummyVolume as Volume
from devops.driver.dummy.dummy_driver import DummyNode as Node
__all__ = [
'DiskDevice',
'Driver',
'Interface',
'L2NetworkDevice',

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from devops.driver.libvirt.libvirt_driver import \
LibvirtDiskDevice as DiskDevice
from devops.driver.libvirt.libvirt_driver import LibvirtDriver as Driver
from devops.driver.libvirt.libvirt_driver import LibvirtInterface as Interface
from devops.driver.libvirt.libvirt_driver import \
@ -20,6 +22,7 @@ from devops.driver.libvirt.libvirt_driver import LibvirtVolume as Volume
from devops.driver.libvirt.libvirt_driver import LibvirtNode as Node
__all__ = [
'DiskDevice',
'Driver',
'Interface',
'L2NetworkDevice',

View File

@ -42,6 +42,7 @@ from devops.models.driver import Driver
from devops.models.network import Interface
from devops.models.network import L2NetworkDevice
from devops.models.node import Node
from devops.models.volume import DiskDevice
from devops.models.volume import Volume
@ -657,6 +658,9 @@ class LibvirtVolume(Volume):
capacity = ParamField(default=None)
format = ParamField(default='qcow2', choices=('qcow2', 'raw'))
source_image = ParamField(default=None)
serial = ParamField()
wwn = ParamField()
multipath_count = ParamField(default=0)
@property
def _libvirt_volume(self):
@ -696,6 +700,10 @@ class LibvirtVolume(Volume):
)
libvirt_volume = pool.createXML(xml, 0)
self.uuid = libvirt_volume.key()
if not self.serial:
self.serial = uuid.uuid4().hex
if not self.wwn:
self.wwn = '0' + ''.join(uuid.uuid4().hex)[:15]
super(LibvirtVolume, self).define()
# Upload predefined image to the volume
@ -890,7 +898,8 @@ class LibvirtNode(Node):
disk_volume_path=disk.volume.get_path(),
disk_bus=disk.bus,
disk_target_dev=disk.target_dev,
disk_serial=uuid.uuid4().hex,
disk_serial=disk.volume.serial,
disk_wwn=disk.volume.wwn if disk.multipath_enabled else None,
))
local_interfaces = []
@ -1430,6 +1439,26 @@ class LibvirtNode(Node):
if target is not None:
return target.get('dev')
def attach_volume(self, volume, device='disk', type='file',
bus='virtio', target_dev=None):
"""Attach volume to node
:rtype : DiskDevice
"""
cls = self.driver.get_model_class('DiskDevice')
if volume.multipath_count:
for x in range(volume.multipath_count):
cls.objects.create(
device=device, type=type, bus='scsi',
target_dev=target_dev or self.next_disk_name(),
volume=volume, node=self)
else:
return cls.objects.create(
device=device, type=type, bus=bus,
target_dev=target_dev or self.next_disk_name(),
volume=volume, node=self)
class LibvirtInterface(Interface):
@ -1486,3 +1515,15 @@ class LibvirtInterface(Interface):
filterref=self.l2_network_device.network_name,
uuid=self._nwfilter.UUIDString())
self.driver.conn.nwfilterDefineXML(filter_xml)
class LibvirtDiskDevice(DiskDevice):
device = ParamField(default='disk', choices=('disk', 'cdrom'))
type = ParamField(default='file', choices=('file'))
bus = ParamField(default='virtio', choices=('virtio', 'ide', 'scsi'))
target_dev = ParamField()
@property
def multipath_enabled(self):
return self.volume.multipath_count > 0

View File

@ -140,7 +140,7 @@ class LibvirtXMLBuilder(object):
@classmethod
def _build_disk_device(cls, device_xml, disk_type, disk_device,
disk_volume_format, disk_volume_path, disk_bus,
disk_target_dev, disk_serial):
disk_target_dev, disk_serial, disk_wwn):
"""Build xml for disk
:param device_xml: XMLBuilder
@ -162,6 +162,8 @@ class LibvirtXMLBuilder(object):
dev=disk_target_dev,
bus=disk_bus)
device_xml.serial(disk_serial)
if disk_wwn:
device_xml.wwn(disk_wwn)
@classmethod
def _build_interface_device(cls, device_xml, interface_type,

View File

@ -197,6 +197,7 @@ def create_slave_config(slave_name, slave_role, slave_vcpu, slave_memory,
second_volume_capacity=None,
third_volume_capacity=None,
use_all_disks=False,
multipath_count=0,
networks_multiplenetworks=None,
networks_nodegroups=None,
networks_bonding=None,
@ -272,17 +273,19 @@ def create_slave_config(slave_name, slave_role, slave_vcpu, slave_memory,
{
'name': 'system',
'capacity': slave_volume_capacity,
'multipath_count': multipath_count,
}
]
if use_all_disks:
volumes.extend([
{
'name': 'cinder',
'capacity': second_volume_capacity or slave_volume_capacity
'capacity': second_volume_capacity or slave_volume_capacity,
'multipath_count': multipath_count,
},
{
'name': 'swift',
'capacity': third_volume_capacity or slave_volume_capacity
'capacity': third_volume_capacity or slave_volume_capacity,
}
])
else:
@ -290,14 +293,15 @@ def create_slave_config(slave_name, slave_role, slave_vcpu, slave_memory,
volumes.append(
{
'name': 'cinder',
'capacity': second_volume_capacity
'capacity': second_volume_capacity,
'multipath_count': multipath_count,
}
)
if third_volume_capacity:
volumes.append(
{
'name': 'swift',
'capacity': third_volume_capacity
'capacity': third_volume_capacity,
}
)
@ -427,6 +431,7 @@ def create_devops_config(boot_from,
second_volume_capacity,
third_volume_capacity,
use_all_disks,
multipath_count,
ironic_nodes_count,
networks_bonding,
networks_bondinginterfaces,
@ -491,6 +496,7 @@ def create_devops_config(boot_from,
interfaceorder=interfaceorder,
numa_nodes=numa_nodes,
use_all_disks=use_all_disks,
multipath_count=multipath_count,
networks_multiplenetworks=networks_multiplenetworks,
networks_nodegroups=networks_nodegroups,
networks_bonding=networks_bonding,

View File

@ -31,7 +31,7 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('params', jsonfield.fields.JSONField(default={})),
('name', models.CharField(max_length=255)),
('net', models.CharField(unique=True, max_length=255)),
('net', models.CharField(max_length=255, unique=True)),
],
options={
'db_table': 'devops_address_pool',
@ -41,10 +41,7 @@ class Migration(migrations.Migration):
name='DiskDevice',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
('device', models.CharField(choices=[('disk', 'disk'), ('cdrom', 'cdrom')], max_length=255)),
('type', models.CharField(choices=[('file', 'file')], max_length=255)),
('bus', models.CharField(choices=[('virtio', 'virtio')], max_length=255)),
('target_dev', models.CharField(max_length=255)),
('params', jsonfield.fields.JSONField(default={})),
],
options={
'db_table': 'devops_diskdevice',
@ -67,7 +64,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('name', models.CharField(unique=True, max_length=255)),
('name', models.CharField(max_length=255, unique=True)),
],
options={
'db_table': 'devops_environment',
@ -78,8 +75,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
('params', jsonfield.fields.JSONField(default={})),
('label', models.CharField(null=True, max_length=255)),
('mac_address', models.CharField(unique=True, max_length=255)),
('label', models.CharField(max_length=255, null=True)),
('mac_address', models.CharField(max_length=255, unique=True)),
('type', models.CharField(max_length=255)),
('model', models.CharField(choices=[('virtio', 'virtio'), ('e1000', 'e1000'), ('pcnet', 'pcnet'), ('rtl8139', 'rtl8139'), ('ne2k_pci', 'ne2k_pci')], max_length=255)),
],
@ -94,7 +91,7 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('params', jsonfield.fields.JSONField(default={})),
('name', models.CharField(max_length=255)),
('address_pool', models.ForeignKey(null=True, to='devops.AddressPool')),
('address_pool', models.ForeignKey(to='devops.AddressPool', null=True)),
],
options={
'db_table': 'devops_l2_network_device',
@ -106,7 +103,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
('label', models.CharField(max_length=255)),
('networks', jsonfield.fields.JSONField(default=[])),
('aggregation', models.CharField(null=True, max_length=255)),
('aggregation', models.CharField(max_length=255, null=True)),
('parents', jsonfield.fields.JSONField(default=[])),
],
options={
@ -119,7 +116,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('name', models.CharField(max_length=255)),
('address_pool', models.ForeignKey(null=True, to='devops.AddressPool')),
('address_pool', models.ForeignKey(to='devops.AddressPool', null=True)),
],
options={
'db_table': 'devops_network_pool',
@ -132,7 +129,7 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('params', jsonfield.fields.JSONField(default={})),
('name', models.CharField(max_length=255)),
('role', models.CharField(null=True, max_length=255)),
('role', models.CharField(max_length=255, null=True)),
],
options={
'db_table': 'devops_node',
@ -145,8 +142,8 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('params', jsonfield.fields.JSONField(default={})),
('name', models.CharField(max_length=255)),
('backing_store', models.ForeignKey(null=True, to='devops.Volume')),
('node', models.ForeignKey(null=True, to='devops.Node')),
('backing_store', models.ForeignKey(to='devops.Volume', null=True)),
('node', models.ForeignKey(to='devops.Node', null=True)),
],
options={
'db_table': 'devops_volume',
@ -157,8 +154,8 @@ class Migration(migrations.Migration):
fields=[
('created', models.DateTimeField(default=datetime.datetime.utcnow)),
('name', models.CharField(max_length=255)),
('driver', models.OneToOneField(serialize=False, primary_key=True, to='devops.Driver')),
('environment', models.ForeignKey(null=True, to='devops.Environment')),
('driver', models.OneToOneField(serialize=False, to='devops.Driver', primary_key=True)),
('environment', models.ForeignKey(to='devops.Environment', null=True)),
],
options={
'db_table': 'devops_group',
@ -172,7 +169,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interface',
name='l2_network_device',
field=models.ForeignKey(null=True, to='devops.L2NetworkDevice'),
field=models.ForeignKey(to='devops.L2NetworkDevice', null=True),
),
migrations.AddField(
model_name='interface',
@ -187,7 +184,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='diskdevice',
name='volume',
field=models.ForeignKey(null=True, to='devops.Volume'),
field=models.ForeignKey(to='devops.Volume', null=True),
),
migrations.AddField(
model_name='addresspool',
@ -197,7 +194,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='address',
name='interface',
field=models.ForeignKey(null=True, to='devops.Interface'),
field=models.ForeignKey(to='devops.Interface', null=True),
),
migrations.AlterUniqueTogether(
name='volume',
@ -206,17 +203,17 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='node',
name='group',
field=models.ForeignKey(null=True, to='devops.Group'),
field=models.ForeignKey(to='devops.Group', null=True),
),
migrations.AddField(
model_name='networkpool',
name='group',
field=models.ForeignKey(null=True, to='devops.Group'),
field=models.ForeignKey(to='devops.Group', null=True),
),
migrations.AddField(
model_name='l2networkdevice',
name='group',
field=models.ForeignKey(null=True, to='devops.Group'),
field=models.ForeignKey(to='devops.Group', null=True),
),
migrations.AlterUniqueTogether(
name='addresspool',

View File

@ -275,6 +275,7 @@ class Environment(BaseModel):
second_volume_capacity=settings.NODE_VOLUME_SIZE,
third_volume_capacity=settings.NODE_VOLUME_SIZE,
use_all_disks=settings.USE_ALL_DISKS,
multipath_count=settings.SLAVE_MULTIPATH_DISKS_COUNT,
ironic_nodes_count=settings.IRONIC_NODES_COUNT,
networks_bonding=settings.BONDING,
networks_bondinginterfaces=settings.BONDING_INTERFACES,

View File

@ -29,7 +29,6 @@ from devops.models.base import BaseModel
from devops.models.base import ParamedModel
from devops.models.base import ParamField
from devops.models.network import NetworkConfig
from devops.models.volume import DiskDevice
from devops.models.volume import Volume
@ -254,14 +253,27 @@ class Node(ParamedModel, BaseModel):
name=name,
**params
)
DiskDevice.node_attach_volume(
node=self,
# TODO(astudenov): make a separete section in template for disk devices
self.attach_volume(
volume=volume,
device=device,
bus=bus,
)
return volume
# NEW
def attach_volume(self, volume, device='disk', type='file',
bus='virtio', target_dev=None):
"""Attach volume to node
:rtype : DiskDevice
"""
cls = self.driver.get_model_class('DiskDevice')
return cls.objects.create(
device=device, type=type, bus=bus,
target_dev=target_dev or self.next_disk_name(),
volume=volume, node=self)
# NEW
def get_volume(self, **kwargs):
try:

View File

@ -15,8 +15,8 @@
from django.db import models
from devops.models.base import BaseModel
from devops.models.base import choices
from devops.models.base import ParamedModel
from devops.models.base import ParamField
class Volume(ParamedModel, BaseModel):
@ -43,26 +43,18 @@ class Volume(ParamedModel, BaseModel):
self.delete()
class DiskDevice(models.Model):
class DiskDevice(ParamedModel):
class Meta(object):
db_table = 'devops_diskdevice'
app_label = 'devops'
node = models.ForeignKey('Node', null=False)
volume = models.ForeignKey('Volume', null=True)
device = choices('disk', 'cdrom')
type = choices('file')
bus = choices('virtio')
target_dev = models.CharField(max_length=255, null=False)
@classmethod
def node_attach_volume(cls, node, volume, device='disk', vol_type='file',
bus='virtio', target_dev=None):
"""Attach volume to node
:rtype : DiskDevice
"""
return cls.objects.create(
device=device, type=vol_type, bus=bus,
target_dev=target_dev or node.next_disk_name(),
volume=volume, node=node)
# TODO(astudenov): temporarily added for ipmi driver
# and driverless testcase. These fields should be removed
# after refactoring of volume section in themplate
device = ParamField()
type = ParamField()
bus = ParamField()
target_dev = ParamField()

View File

@ -32,6 +32,8 @@ DRIVER_PARAMETERS = {
'enable_acpi': get_var_as_bool('DRIVER_ENABLE_ACPI', False),
}
MIDDLEWARE_CLASSES = [] # required for django
INSTALLED_APPS = ['devops']
LOGS_DIR = os.environ.get('LOGS_DIR', os.path.expanduser('~/.devops'))
@ -215,6 +217,8 @@ HARDWARE = {
}
USE_ALL_DISKS = get_var_as_bool('USE_ALL_DISKS', True)
SLAVE_MULTIPATH_DISKS_COUNT = int(
os.environ.get("SLAVE_MULTIPATH_DISKS_COUNT", 0))
ISO_PATH = os.environ.get('ISO_PATH')
IRONIC_ENABLED = get_var_as_bool('IRONIC_ENABLED', False)

View File

@ -194,6 +194,7 @@ class Shell(object):
second_volume_capacity=self.params.second_disk_size,
third_volume_capacity=self.params.third_disk_size,
use_all_disks=settings.USE_ALL_DISKS,
multipath_count=settings.SLAVE_MULTIPATH_DISKS_COUNT,
ironic_nodes_count=settings.IRONIC_NODES_COUNT,
networks_bonding=settings.BONDING,
networks_bondinginterfaces=settings.BONDING_INTERFACES,

View File

@ -16,7 +16,7 @@
from django.test import TestCase
import mock
from devops.driver.libvirt.libvirt_driver import LibvirtDriver
from devops.driver.libvirt.libvirt_driver import LibvirtManager
CAPS_XML = """
@ -88,6 +88,9 @@ class LibvirtTestCase(TestCase):
return m
def setUp(self):
# reset device names
LibvirtDriver._device_name_generators = {}
self.libvirt_vol_up_mock = self.patch('libvirt.virStorageVol.upload')
self.libvirt_stream_snd_mock = self.patch('libvirt.virStream.sendAll')
self.libvirt_stream_fin_mock = self.patch('libvirt.virStream.finish')

View File

@ -179,8 +179,6 @@ class TestLibvirtDriverDeviceNames(LibvirtTestCase):
self.d = self.group.driver
self.d._device_name_generators = dict()
self.dev_mock = mock.Mock(spec=libvirt.virNodeDevice)
self.dev_mock.listCaps.return_value = ['net']
self.dev_mock.XMLDesc.return_value = """

View File

@ -0,0 +1,156 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from devops.models import Environment
from devops.tests.driver.libvirt.base import LibvirtTestCase
class TestLibvirtNodeMultipath(LibvirtTestCase):
def setUp(self):
super(TestLibvirtNodeMultipath, self).setUp()
self.sleep_mock = self.patch('devops.helpers.retry.sleep')
self.libvirt_sleep_mock = self.patch(
'devops.driver.libvirt.libvirt_driver.sleep')
self.env = Environment.create('test_env')
self.group = self.env.add_group(
group_name='test_group',
driver_name='devops.driver.libvirt',
connection_string='test:///default',
storage_pool_name='default-pool',
vnc_password='123456',
)
self.ap = self.env.add_address_pool(
name='test_ap',
net='172.0.0.0/16:24',
tag=0,
ip_reserved=dict(l2_network_device=1),
)
self.net_pool = self.group.add_network_pool(
name='fuelweb_admin',
address_pool_name='test_ap',
)
self.l2_net_dev = self.group.add_l2_network_device(
name='test_l2_net_dev',
address_pool='test_ap',
forward=dict(mode='nat'),
)
self.node = self.group.add_node(
name='test_node',
role='default',
architecture='i686',
hypervisor='test',
)
self.interface = self.node.add_interface(
label='eth0',
l2_network_device_name='test_l2_net_dev',
interface_model='virtio',
)
self.interface.mac_address = '64:b6:87:44:14:17'
self.interface.save()
self.volume = self.node.add_volume(
name='test_volume',
capacity=5,
multipath_count=2,
serial='3b16d312420d4adbb2d5b04fcbd5221c',
)
self.d = self.group.driver
self.l2_net_dev.define()
@mock.patch('devops.driver.libvirt.libvirt_driver.uuid')
@mock.patch('libvirt.virConnect.defineXML')
def test_define_xml(self, define_xml_mock, uuid_mock):
uuid_mock.uuid4.side_effect = (
mock.Mock(hex='fe527bd28e0f4a84b9117dc97142c580'),
mock.Mock(hex='9cddb80fe82e480eb14c1a89f1c0e11d'))
define_xml_mock.return_value.UUIDString.return_value = 'fake_uuid'
self.volume.define()
self.node.define()
assert define_xml_mock.call_count == 1
xml = define_xml_mock.call_args[0][0]
assert xml == """<?xml version="1.0" encoding="utf-8"?>
<domain type="test">
<name>test_env_test_node</name>
<cpu mode="host-passthrough"/>
<vcpu>1</vcpu>
<memory unit="KiB">1048576</memory>
<clock offset="utc"/>
<clock>
<timer name="rtc" tickpolicy="catchup" track="wall">
<catchup limit="10000" slew="120" threshold="123"/>
</timer>
</clock>
<clock>
<timer name="pit" tickpolicy="delay"/>
</clock>
<clock>
<timer name="hpet" present="yes"/>
</clock>
<os>
<type arch="i686">hvm</type>
<boot dev="network"/>
<boot dev="cdrom"/>
<boot dev="hd"/>
</os>
<devices>
<controller model="nec-xhci" type="usb"/>
<emulator>/usr/bin/test-emulator</emulator>
<graphics autoport="yes" listen="0.0.0.0" passwd="123456" type="vnc"/>
<disk device="disk" type="file">
<driver cache="unsafe" type="qcow2"/>
<source file="/default-pool/test_env_test_node_test_volume"/>
<target bus="scsi" dev="sda"/>
<serial>3b16d312420d4adbb2d5b04fcbd5221c</serial>
<wwn>0fe527bd28e0f4a8</wwn>
</disk>
<disk device="disk" type="file">
<driver cache="unsafe" type="qcow2"/>
<source file="/default-pool/test_env_test_node_test_volume"/>
<target bus="scsi" dev="sdb"/>
<serial>3b16d312420d4adbb2d5b04fcbd5221c</serial>
<wwn>0fe527bd28e0f4a8</wwn>
</disk>
<interface type="network">
<mac address="64:b6:87:44:14:17"/>
<source network="test_env_test_l2_net_dev"/>
<target dev="virnet0"/>
<model type="virtio"/>
<filterref filter="test_env_test_l2_net_dev_64:b6:87:44:14:17"/>
</interface>
<video>
<model heads="1" type="vga" vram="9216"/>
</video>
<serial type="pty">
<target port="0"/>
</serial>
<console type="pty">
<target port="0" type="serial"/>
</console>
</devices>
</domain>
"""

View File

@ -329,6 +329,7 @@ class TestNodeXml(BaseTestXMLBuilder):
disk_bus='usb',
disk_target_dev='sda',
disk_serial='ca9dcfe5a48540f39537eb3cbd96f370',
disk_wwn=None,
),
dict(
disk_type='file',
@ -338,6 +339,7 @@ class TestNodeXml(BaseTestXMLBuilder):
disk_bus='ide',
disk_target_dev='sdb',
disk_serial='8c81c0e0aba448fabcb54c34f61d8d07',
disk_wwn=None,
),
]
@ -536,8 +538,8 @@ class TestNodeXml(BaseTestXMLBuilder):
emulator='/usr/lib64/xen/bin/qemu-dm',
has_vnc=True,
vnc_password=None,
local_disk_devices=self.disk_devices,
interfaces=self.interfaces,
local_disk_devices=[],
interfaces=[],
acpi=True,
numa=[dict(cpus='0,1', memory=512),
dict(cpus='2,3', memory=512)],
@ -583,33 +585,6 @@ class TestNodeXml(BaseTestXMLBuilder):
<controller model="nec-xhci" type="usb"/>
<emulator>/usr/lib64/xen/bin/qemu-dm</emulator>
<graphics autoport="yes" listen="0.0.0.0" type="vnc"/>
<disk device="disk" type="file">
<driver cache="unsafe" type="raw"/>
<source file="/tmp/volume.img"/>
<target bus="usb" dev="sda" removable="on"/>
<readonly/>
<serial>ca9dcfe5a48540f39537eb3cbd96f370</serial>
</disk>
<disk device="cdrom" type="file">
<driver cache="unsafe" type="qcow2"/>
<source file="/tmp/volume2.img"/>
<target bus="ide" dev="sdb"/>
<serial>8c81c0e0aba448fabcb54c34f61d8d07</serial>
</disk>
<interface type="network">
<mac address="64:70:74:90:bc:84"/>
<source network="test_admin"/>
<target dev="virnet132"/>
<model type="e1000"/>
<filterref filter="test_filter1"/>
</interface>
<interface type="network">
<mac address="64:de:6c:44:de:46"/>
<source network="test_public"/>
<target dev="virnet133"/>
<model type="pcnet"/>
<filterref filter="test_filter2"/>
</interface>
<video>
<model heads="1" type="vga" vram="9216"/>
</video>

View File

@ -18,6 +18,7 @@ import pkgutil
from django.test import TestCase
from devops import driver
from devops.models import DiskDevice
from devops.models import Driver
from devops.models import Interface
from devops.models import L2NetworkDevice
@ -27,19 +28,33 @@ from devops.models import Volume
class TestDriverImport(TestCase):
@staticmethod
def assert_class_present(mod, mod_path, class_name, expected_class):
if not hasattr(mod, class_name):
raise AssertionError(
'{mod_path}.{class_name} does not exist'
''.format(mod_path=mod_path, class_name=class_name))
klass = getattr(mod, class_name)
if not issubclass(klass, expected_class):
raise AssertionError(
'{mod_path}.{class_name} is not subclass of {expected_class}'
''.format(mod_path=mod_path, class_name=class_name,
expected_class=expected_class))
def test_driver_imports(self):
for _, modname, ispkg in pkgutil.iter_modules(driver.__path__):
for _, mod_name, ispkg in pkgutil.iter_modules(driver.__path__):
if not ispkg:
continue
if modname == 'ipmi':
# skip ipmi
continue
mod_path = 'devops.driver.{}'.format(mod_name)
mod = importlib.import_module(mod_path)
mod = importlib.import_module('devops.driver.{}'.format(modname))
assert issubclass(getattr(mod, 'Driver'), Driver)
assert issubclass(getattr(mod, 'Interface'), Interface)
assert issubclass(getattr(mod, 'L2NetworkDevice'), L2NetworkDevice)
assert issubclass(getattr(mod, 'Node'), Node)
assert issubclass(getattr(mod, 'Volume'), Volume)
self.assert_class_present(mod, mod_path, 'DiskDevice', DiskDevice)
self.assert_class_present(mod, mod_path, 'Driver', Driver)
self.assert_class_present(mod, mod_path, 'Interface', Interface)
self.assert_class_present(
mod, mod_path, 'L2NetworkDevice', L2NetworkDevice)
self.assert_class_present(mod, mod_path, 'Node', Node)
self.assert_class_present(mod, mod_path, 'Volume', Volume)

View File

@ -41,6 +41,7 @@ class TestDefaultTemplate(TestCase):
second_volume_capacity=settings.NODE_VOLUME_SIZE,
third_volume_capacity=settings.NODE_VOLUME_SIZE,
use_all_disks=settings.USE_ALL_DISKS,
multipath_count=settings.SLAVE_MULTIPATH_DISKS_COUNT,
ironic_nodes_count=settings.IRONIC_NODES_COUNT,
networks_bonding=settings.BONDING,
networks_bondinginterfaces=settings.BONDING_INTERFACES,
@ -247,8 +248,10 @@ class TestDefaultTemplate(TestCase):
vcpu: 2
volumes:
- capacity: 50
multipath_count: 0
name: system
- capacity: 50
multipath_count: 0
name: cinder
- capacity: 50
name: swift
@ -271,6 +274,7 @@ class TestDefaultTemplate(TestCase):
second_volume_capacity=settings.NODE_VOLUME_SIZE,
third_volume_capacity=settings.NODE_VOLUME_SIZE,
use_all_disks=settings.USE_ALL_DISKS,
multipath_count=2,
ironic_nodes_count=settings.IRONIC_NODES_COUNT,
networks_bonding=settings.BONDING,
networks_bondinginterfaces=settings.BONDING_INTERFACES,
@ -485,8 +489,10 @@ class TestDefaultTemplate(TestCase):
vcpu: 8
volumes:
- capacity: 50
multipath_count: 2
name: system
- capacity: 50
multipath_count: 2
name: cinder
- capacity: 50
name: swift