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:
parent
ed01e8f82f
commit
8667612c79
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 = """
|
||||
|
|
|
@ -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>
|
||||
"""
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue