Improve test coverage for fuel_agent

* should be 100%
* also comes with fixups
Partially implements: blueprint image-based-provisioning

Change-Id: I73ff92f65c0872ad82917d22900c3f410986629d
This commit is contained in:
Alexander Gordeev 2014-07-14 20:40:39 +04:00
parent 7730a743bb
commit 291ef045f5
20 changed files with 1457 additions and 23 deletions

View File

@ -67,7 +67,7 @@ class Nailgun(object):
@property
def ks_vgs(self):
vg_filter = lambda x: x['type'] == 'vg'
self.ks_vgs = filter(vg_filter, self.partition_data())
return filter(vg_filter, self.partition_data())
@property
def hu_disks(self):
@ -107,7 +107,7 @@ class Nailgun(object):
ks_spaces_validator.validate(data)
partition_scheme = objects.PartitionScheme()
for disk in enumerate(self.ks_disks):
for disk in self.ks_disks:
parted = partition_scheme.add_parted(
name=self._disk_dev(disk), label='gpt')
# legacy boot partition
@ -147,7 +147,7 @@ class Nailgun(object):
if partition_scheme.configdrive_device() is None:
parted.add_partition(size=20, configdrive=True)
for vg in enumerate(self.ks_vgs):
for vg in self.ks_vgs:
for volume in vg['volumes']:
if volume['size'] <= 0:
continue
@ -172,7 +172,7 @@ class Nailgun(object):
admin_interface = filter(
lambda x: (x['mac_address'] ==
data['kernel_options']['netcfg/choose_interface']),
[spec.update(name=name) for name, spec
[dict(name=name, **spec) for name, spec
in data['interfaces'].iteritems()])[0]
configdrive_scheme.set_common(
ssh_auth_key=data['ks_meta']['auth_key'],
@ -181,7 +181,7 @@ class Nailgun(object):
name_servers=data['name_servers'],
search_domain=data['name_servers_search'],
master_ip=data['ks_meta']['master_ip'],
master_url='http:/%s:8000/api' % self.data['master_ip'],
master_url='http://%s:8000/api' % data['ks_meta']['master_ip'],
udevrules=data['kernel_options']['udevrules'],
admin_mac=data['kernel_options']['netcfg/choose_interface'],
admin_ip=admin_interface['ip_address'],
@ -198,7 +198,7 @@ class Nailgun(object):
pskey=data['ks_meta']['mco_pskey'],
vhost=data['ks_meta']['mco_vhost'],
host=data['ks_meta']['mco_host'],
user=data['ks_meta']['mco_host'],
user=data['ks_meta']['mco_user'],
password=data['ks_meta']['mco_password'],
connector=data['ks_meta']['mco_connector']
)

View File

@ -93,3 +93,7 @@ class WrongConfigDriveDataError(BaseError):
class WrongImageDataError(BaseError):
pass
class TemplateWriteError(BaseError):
pass

View File

@ -65,7 +65,7 @@ class Manager(object):
def do_partitioning(self):
for parted in self.partition_scheme.parteds:
pu.make_label(parted.name, parted.label)
for prt in parted.partititons:
for prt in parted.partitions:
pu.make_partition(prt.device, prt.begin, prt.end, prt.type)
for flag in prt.flags:
pu.set_partition_flag(prt.device, prt.count, flag)
@ -94,6 +94,7 @@ class Manager(object):
cc_output_path = os.path.join(CONF.tmp_path, 'cloud_config.txt')
bh_output_path = os.path.join(CONF.tmp_path, 'boothook.txt')
# NOTE:file should be strictly named as 'user-data'
# the same is for meta-data as well
ud_output_path = os.path.join(CONF.tmp_path, 'user-data')
md_output_path = os.path.join(CONF.tmp_path, 'meta-data')
@ -123,7 +124,7 @@ class Manager(object):
raise errors.WrongPartitionSchemeError(
'Error while trying to get configdrive device: '
'configdrive device not found')
self.image_scheme.add_configdrive_image(
self.image_scheme.add_image(
uri='file://%s' % CONF.config_drive_path,
target_device=configdrive_device,
image_format='iso9660',

View File

@ -15,6 +15,7 @@
from fuel_agent.objects.configdrive import ConfigDriveCommon
from fuel_agent.objects.configdrive import ConfigDriveMcollective
from fuel_agent.objects.configdrive import ConfigDrivePuppet
from fuel_agent.objects.configdrive import ConfigDriveScheme
from fuel_agent.objects.image import Image
from fuel_agent.objects.image import ImageScheme
from fuel_agent.objects.partition import Fs

View File

@ -17,7 +17,8 @@ from fuel_agent import errors
class ConfigDriveCommon(object):
def __init__(self, ssh_auth_key, hostname, fqdn, name_servers,
search_domain, master_ip, master_url, timezone):
search_domain, master_ip, master_url, udevrules, admin_mac,
admin_ip, admin_mask, admin_iface_name, timezone):
self.ssh_auth_key = ssh_auth_key
self.hostname = hostname
self.fqdn = fqdn
@ -25,6 +26,11 @@ class ConfigDriveCommon(object):
self.search_domain = search_domain
self.master_ip = master_ip
self.master_url = master_url
self.udevrules = udevrules
self.admin_mac = admin_mac
self.admin_ip = admin_ip
self.admin_mask = admin_mask
self.admin_iface_name = admin_iface_name
self.timezone = timezone

View File

@ -70,6 +70,7 @@ class Parted(object):
return 'primary'
elif len(self.partitions) == 3 and not self.extended:
return 'extended'
#NOTE(agordeev): how to reach that condition?
else:
return 'logical'
@ -90,7 +91,7 @@ class Parted(object):
if self.next_type() == 'extended':
return None
separator = ''
if self.name.find('cciss') >= 0 or self.name.find('loop') >= 0:
if 'cciss' in self.name or 'loop' in self.name:
separator = 'p'
return '%s%s%s' % (self.name, separator, self.next_count())
@ -212,9 +213,10 @@ class PartitionScheme(object):
return fs
def add_md(self, **kwargs):
kwargs['name'] = kwargs.get('name') or self.md_next_name()
kwargs['level'] = kwargs.get('level') or 'mirror'
md = Md(**kwargs)
mdkwargs = {}
mdkwargs['name'] = kwargs.get('name') or self.md_next_name()
mdkwargs['level'] = kwargs.get('level') or 'mirror'
md = Md(**mdkwargs)
self.mds.append(md)
return md
@ -234,7 +236,7 @@ class PartitionScheme(object):
md = self.add_md(**kwargs)
fskwargs = {}
fskwargs['device'] = md.name
fskwargs['mount'] = kwargs.pop('mount')
fskwargs['mount'] = mount
fskwargs['fs_type'] = kwargs.pop('fs_type', None)
fskwargs['fs_options'] = kwargs.pop('fs_options', None)
fskwargs['fs_label'] = kwargs.pop('fs_label', None)
@ -248,11 +250,12 @@ class PartitionScheme(object):
name = '/dev/md%s' % count
if name not in [md.name for md in self.mds]:
return name
if count > 127:
if count >= 127:
raise errors.MDAlreadyExistsError(
'Error while generating md name: '
'names from /dev/md0 to /dev/md127 seem to be busy, '
'try to generate md name manually')
count += 1
def vg_by_name(self, vgname):
found = filter(lambda x: (x.name == vgname), self.vgs)
@ -292,6 +295,6 @@ class PartitionScheme(object):
# only if one uses cloud-init with configdrive.
def configdrive_device(self):
for parted in self.parteds:
for prt in parted.partititons:
for prt in parted.partitions:
if prt.configdrive:
return prt.name

View File

@ -0,0 +1,52 @@
# Copyright 2014 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.
from oslotest import base as test_base
from fuel_agent import errors
from fuel_agent.objects import configdrive
class TestConfigDriveScheme(test_base.BaseTestCase):
def setUp(self):
super(TestConfigDriveScheme, self).setUp()
self.cd_scheme = configdrive.ConfigDriveScheme()
def test_template_name(self):
actual = self.cd_scheme.template_name('what')
expected = '%s_%s.jinja2' % ('what', self.cd_scheme._profile)
self.assertEqual(expected, actual)
def test_template_data_no_common(self):
self.assertRaises(errors.WrongConfigDriveDataError,
self.cd_scheme.template_data)
def test_template_data_ok(self):
cd_common = configdrive.ConfigDriveCommon(
'ssh_auth_key', 'hostname', 'fqdn', 'name_servers',
'search_domain', 'master_ip', 'master_url', 'udevrules',
'admin_mac', 'admin_ip', 'admin_mask', 'admin_iface_name',
'timezone')
cd_puppet = configdrive.ConfigDrivePuppet('master')
cd_mcollective = configdrive.ConfigDriveMcollective(
'pskey', 'vhost', 'host', 'user', 'password', 'connector')
self.cd_scheme.common = cd_common
self.cd_scheme.puppet = cd_puppet
self.cd_scheme.mcollective = cd_mcollective
template_data = self.cd_scheme.template_data()
self.assertEqual(cd_common, template_data['common'])
self.assertEqual(cd_puppet, template_data['puppet'])
self.assertEqual(cd_mcollective, template_data['mcollective'])

View File

@ -0,0 +1,34 @@
# Copyright 2014 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 oslotest import base as test_base
from fuel_agent.utils import fs_utils as fu
from fuel_agent.utils import utils
class TestFSUtils(test_base.BaseTestCase):
@mock.patch.object(utils, 'execute')
def test_make_fs(self, mock_exec):
fu.make_fs('ext4', ' -F ', ' -L fake_label ', '/dev/fake')
mock_exec.assert_called_once_with('mkfs.ext4', '-F', '-L',
'fake_label', '/dev/fake')
@mock.patch.object(utils, 'execute')
def test_make_fs_swap(self, mock_exec):
fu.make_fs('swap', ' -f ', ' -L fake_label ', '/dev/fake')
mock_exec.assert_called_once_with('mkswap', '-f', '-L', 'fake_label',
'/dev/fake')

View File

@ -104,6 +104,7 @@ supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: yes
"""]
expected = {'driver': 'r8169',
@ -204,6 +205,12 @@ supports-register-dump: yes
expected = {'removable': '0', 'state': 'running', 'timeout': '30'}
self.assertEqual(expected, hu.extrareport('/dev/fake'))
@mock.patch('six.moves.builtins.open')
def test_extrareport_exceptions(self, mock_open):
mock_open.side_effect = Exception('foo')
expected = {}
self.assertEqual(expected, hu.extrareport('/dev/fake'))
@mock.patch.object(hu, 'blockdevreport')
@mock.patch.object(hu, 'udevreport')
def test_is_disk_uspec_bspec_none(self, mock_ureport, mock_breport):
@ -371,3 +378,8 @@ supports-register-dump: yes
uspec1 = {'ID_SERIAL_SHORT': 'fakeserial1'}
uspec2 = {'ID_SERIAL_SHORT': 'fakeserial2'}
self.assertFalse(hu.match_device(uspec1, uspec2))
def test_match_device_false(self):
uspec1 = {'ID_WWN': 'fakewwn1', 'DEVTYPE': 'disk'}
uspec2 = {'ID_WWN': 'fakewwn1', 'DEVTYPE': 'partition'}
self.assertFalse(hu.match_device(uspec1, uspec2))

View File

@ -0,0 +1,26 @@
# Copyright 2014 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.
from oslotest import base as test_base
from fuel_agent import errors
from fuel_agent.objects import image
class TestImage(test_base.BaseTestCase):
def test_unsupported_container(self):
self.assertRaises(errors.WrongImageDataError, image.Image, 'uri',
'dev', 'format', 'unsupported')

View File

@ -0,0 +1,204 @@
# Copyright 2014 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 copy
from oslotest import base as test_base
from fuel_agent.drivers import ks_spaces_validator as kssv
from fuel_agent import errors
SAMPLE_SCHEME = [
{
"name": "sda",
"extra": [
"disk/by-id/scsi-SATA_VBOX_HARDDISK_VB69050467-b385c7cd",
"disk/by-id/ata-VBOX_HARDDISK_VB69050467-b385c7cd"
],
"free_space": 64907,
"volumes": [
{
"type": "boot",
"size": 300
},
{
"mount": "/boot",
"size": 200,
"type": "raid",
"file_system": "ext2",
"name": "Boot"
},
{
"type": "lvm_meta_pool",
"size": 0
},
{
"size": 19438,
"type": "pv",
"lvm_meta_size": 64,
"vg": "os"
},
{
"size": 45597,
"type": "pv",
"lvm_meta_size": 64,
"vg": "image"
}
],
"type": "disk",
"id": "sda",
"size": 65535
},
{
"name": "sdb",
"extra": [
"disk/by-id/scsi-SATA_VBOX_HARDDISK_VBf2923215-708af674",
"disk/by-id/ata-VBOX_HARDDISK_VBf2923215-708af674"
],
"free_space": 64907,
"volumes": [
{
"type": "boot",
"size": 300
},
{
"mount": "/boot",
"size": 200,
"type": "raid",
"file_system": "ext2",
"name": "Boot"
},
{
"type": "lvm_meta_pool",
"size": 64
},
{
"size": 0,
"type": "pv",
"lvm_meta_size": 0,
"vg": "os"
},
{
"size": 64971,
"type": "pv",
"lvm_meta_size": 64,
"vg": "image"
}
],
"type": "disk",
"id": "sdb",
"size": 65535
},
{
"name": "sdc",
"extra": [
"disk/by-id/scsi-SATA_VBOX_HARDDISK_VB50ee61eb-84e74fdf",
"disk/by-id/ata-VBOX_HARDDISK_VB50ee61eb-84e74fdf"
],
"free_space": 64907,
"volumes": [
{
"type": "boot",
"size": 300
},
{
"mount": "/boot",
"size": 200,
"type": "raid",
"file_system": "ext2",
"name": "Boot"
},
{
"type": "lvm_meta_pool",
"size": 64
},
{
"size": 0,
"type": "pv",
"lvm_meta_size": 0,
"vg": "os"
},
{
"size": 64971,
"type": "pv",
"lvm_meta_size": 64,
"vg": "image"
}
],
"type": "disk",
"id": "disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0",
"size": 65535
},
{
"_allocate_size": "min",
"label": "Base System",
"min_size": 19374,
"volumes": [
{
"mount": "/",
"size": 15360,
"type": "lv",
"name": "root",
"file_system": "ext4"
},
{
"mount": "swap",
"size": 4014,
"type": "lv",
"name": "swap",
"file_system": "swap"
}
],
"type": "vg",
"id": "os"
},
{
"_allocate_size": "all",
"label": "Image Storage",
"min_size": 5120,
"volumes": [
{
"mount": "/var/lib/glance",
"size": 175347,
"type": "lv",
"name": "glance",
"file_system": "xfs"
}
],
"type": "vg",
"id": "image"
}
]
class TestKSSpacesValidator(test_base.BaseTestCase):
def setUp(self):
super(TestKSSpacesValidator, self).setUp()
self.fake_scheme = copy.deepcopy(SAMPLE_SCHEME)
def test_validate_ok(self):
kssv.validate(self.fake_scheme)
def test_validate_jsoschema_fail(self):
self.assertRaises(errors.WrongPartitionSchemeError, kssv.validate,
[{}])
def test_validate_no_disks_fail(self):
self.assertRaises(errors.WrongPartitionSchemeError, kssv.validate,
self.fake_scheme[-2:])
def test_validate_16T_root_volume_fail(self):
self.fake_scheme[3]['volumes'][0]['size'] = 16777216 + 1
self.assertRaises(errors.WrongPartitionSchemeError, kssv.validate,
self.fake_scheme)

View File

@ -285,3 +285,101 @@ class TestLvmUtils(test_base.BaseTestCase):
# then raise error if it doesn't
mock_vgdisplay.return_value = [{'name': 'some'}]
self.assertRaises(errors.VGNotFoundError, lu.vgremove, 'vgname')
@mock.patch.object(lu, 'lvdisplay')
@mock.patch.object(utils, 'execute')
def test_lvremove_ok(self, mock_exec, mock_lvdisplay):
mock_lvdisplay.return_value = [{'name': 'lvname'}, {'name': 'some'}]
lu.lvremove('lvname')
mock_exec.assert_called_once_with('lvremove', '-f', 'lvname',
check_exit_code=[0])
@mock.patch.object(lu, 'lvdisplay')
@mock.patch.object(utils, 'execute')
def test_lvremove_not_found(self, mock_exec, mock_lvdisplay):
mock_lvdisplay.return_value = [{'name': 'some'}]
self.assertRaises(errors.LVNotFoundError, lu.lvremove, 'lvname')
@mock.patch.object(lu, 'vgdisplay')
@mock.patch.object(lu, 'lvdisplay')
@mock.patch.object(utils, 'execute')
def test_lvcreate_ok(self, mock_exec, mock_lvdisplay, mock_vgdisplay):
mock_vgdisplay.return_value = [{'name': 'vgname', 'free': 2000},
{'name': 'some'}]
mock_lvdisplay.return_value = [{'name': 'some'}]
lu.lvcreate('vgname', 'lvname', 1000)
mock_exec.assert_called_once_with('lvcreate', '-L', '1000m', '-n',
'lvname', 'vgname',
check_exit_code=[0])
@mock.patch.object(lu, 'vgdisplay')
@mock.patch.object(utils, 'execute')
def test_lvcreate_not_found(self, mock_exec, mock_vgdisplay):
mock_vgdisplay.return_value = [{'name': 'some'}]
self.assertRaises(errors.VGNotFoundError, lu.lvcreate, 'vgname',
'lvname', 1)
@mock.patch.object(lu, 'vgdisplay')
@mock.patch.object(utils, 'execute')
def test_lvcreate_not_enough_space(self, mock_exec, mock_vgdisplay):
mock_vgdisplay.return_value = [{'name': 'vgname', 'free': 1},
{'name': 'some'}]
self.assertRaises(errors.NotEnoughSpaceError, lu.lvcreate, 'vgname',
'lvname', 2)
@mock.patch.object(lu, 'vgdisplay')
@mock.patch.object(lu, 'lvdisplay')
@mock.patch.object(utils, 'execute')
def test_lvcreate_lv_already_exists(self, mock_exec, mock_lvdisplay,
mock_vgdisplay):
mock_vgdisplay.return_value = [{'name': 'vgname', 'free': 2000},
{'name': 'some'}]
mock_lvdisplay.return_value = [{'name': 'lvname'}]
self.assertRaises(errors.LVAlreadyExistsError, lu.lvcreate, 'vgname',
'lvname', 1000)
@mock.patch.object(utils, 'execute')
def test_lvdisplay(self, mock_exec):
mock_exec.return_value = [
' lv_name1;1234.12m;vg_name;lv_uuid1\n'
' lv_name2;5678.79m;vg_name;lv_uuid2\n ']
expected_lvs = [{'name': 'lv_name1', 'size': 1235, 'vg': 'vg_name',
'uuid': 'lv_uuid1', 'path': '/dev/vg_name/lv_name1'},
{'name': 'lv_name2', 'size': 5679, 'vg': 'vg_name',
'uuid': 'lv_uuid2', 'path': '/dev/vg_name/lv_name2'}]
actual_lvs = lu.lvdisplay()
self.assertEqual(expected_lvs, actual_lvs)
mock_exec.assert_called_once_with('lvdisplay', '-C', '--noheading',
'--units', 'm', '--options',
'lv_name,lv_size,vg_name,lv_uuid',
'--separator', ';',
check_exit_code=[0])
@mock.patch.object(lu, 'pvdisplay')
@mock.patch.object(lu, 'vgdisplay')
@mock.patch.object(utils, 'execute')
def test_vgreduce_ok(self, mock_exec, mock_vgdisplay, mock_pvdisplay):
mock_vgdisplay.return_value = [{'name': 'vgname'}, {'name': 'some'}]
mock_pvdisplay.return_value = [{'vg': 'vgname', 'name': '/dev/fake1'},
{'vg': 'vgname', 'name': '/dev/fake2'}]
lu.vgreduce('vgname', '/dev/fake1', '/dev/fake2')
mock_exec.assert_called_once_with('vgreduce', '-f', 'vgname',
'/dev/fake1', '/dev/fake2',
check_exit_code=[0])
@mock.patch.object(lu, 'vgdisplay')
def test_vgreduce_vg_not_found(self, mock_vgdisplay):
mock_vgdisplay.return_value = [{'name': 'some'}]
self.assertRaises(errors.VGNotFoundError, lu.vgreduce, 'vgname1',
'/dev/fake1', '/dev/fake2')
@mock.patch.object(lu, 'pvdisplay')
@mock.patch.object(lu, 'vgdisplay')
@mock.patch.object(utils, 'execute')
def test_vgreduce_pv_not_attached(self, mock_exec, mock_vgdisplay,
mock_pvdisplay):
mock_vgdisplay.return_value = [{'name': 'vgname'}, {'name': 'some'}]
mock_pvdisplay.return_value = [{'vg': None, 'name': '/dev/fake1'},
{'vg': None, 'name': '/dev/fake2'}]
self.assertRaises(errors.PVNotFoundError, lu.vgreduce, 'vgname',
'/dev/fake1', '/dev/fake2')

View File

@ -0,0 +1,170 @@
# Copyright 2014 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 oslo.config import cfg
from oslotest import base as test_base
from fuel_agent import errors
from fuel_agent import manager
from fuel_agent.objects import partition
from fuel_agent.tests import test_nailgun
from fuel_agent.utils import fs_utils as fu
from fuel_agent.utils import hardware_utils as hu
from fuel_agent.utils import lvm_utils as lu
from fuel_agent.utils import md_utils as mu
from fuel_agent.utils import partition_utils as pu
from fuel_agent.utils import utils
CONF = cfg.CONF
class TestManager(test_base.BaseTestCase):
def setUp(self):
super(TestManager, self).setUp()
self.mgr = manager.Manager(test_nailgun.PROVISION_SAMPLE_DATA)
@mock.patch.object(hu, 'list_block_devices')
def test_do_parsing(self, mock_lbd):
mock_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE
self.mgr.do_parsing()
#NOTE(agordeev): there's no need for deeper assertions as all schemes
# thoroughly tested in test_nailgun
self.assertFalse(self.mgr.partition_scheme is None)
self.assertFalse(self.mgr.configdrive_scheme is None)
self.assertFalse(self.mgr.image_scheme is None)
@mock.patch.object(fu, 'make_fs')
@mock.patch.object(lu, 'lvcreate')
@mock.patch.object(lu, 'vgcreate')
@mock.patch.object(lu, 'pvcreate')
@mock.patch.object(mu, 'mdcreate')
@mock.patch.object(pu, 'set_partition_flag')
@mock.patch.object(pu, 'make_partition')
@mock.patch.object(pu, 'make_label')
@mock.patch.object(hu, 'list_block_devices')
def test_do_partitioning(self, mock_hu_lbd, mock_pu_ml, mock_pu_mp,
mock_pu_spf, mock_mu_m, mock_lu_p, mock_lu_v,
mock_lu_l, mock_fu_mf):
mock_hu_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE
self.mgr.do_parsing()
self.mgr.do_partitioning()
mock_pu_ml_expected_calls = [mock.call('/dev/sda', 'gpt'),
mock.call('/dev/sdb', 'gpt'),
mock.call('/dev/sdc', 'gpt')]
self.assertEqual(mock_pu_ml_expected_calls, mock_pu_ml.call_args_list)
mock_pu_mp_expected_calls = [
mock.call('/dev/sda', 0, 24, 'primary'),
mock.call('/dev/sda', 24, 224, 'primary'),
mock.call('/dev/sda', 224, 424, 'primary'),
mock.call('/dev/sda', 424, 624, 'primary'),
mock.call('/dev/sda', 624, 20062, 'primary'),
mock.call('/dev/sda', 20062, 65659, 'primary'),
mock.call('/dev/sda', 65659, 65679, 'primary'),
mock.call('/dev/sdb', 0, 24, 'primary'),
mock.call('/dev/sdb', 24, 224, 'primary'),
mock.call('/dev/sdb', 224, 424, 'primary'),
mock.call('/dev/sdb', 424, 65395, 'primary'),
mock.call('/dev/sdc', 0, 24, 'primary'),
mock.call('/dev/sdc', 24, 224, 'primary'),
mock.call('/dev/sdc', 224, 424, 'primary'),
mock.call('/dev/sdc', 424, 65395, 'primary')]
self.assertEqual(mock_pu_mp_expected_calls, mock_pu_mp.call_args_list)
mock_pu_spf_expected_calls = [mock.call('/dev/sda', 1, 'bios_grub'),
mock.call('/dev/sdb', 1, 'bios_grub'),
mock.call('/dev/sdc', 1, 'bios_grub')]
self.assertEqual(mock_pu_spf_expected_calls,
mock_pu_spf.call_args_list)
mock_mu_m_expected_calls = [mock.call('/dev/md0', 'mirror',
'/dev/sda3', '/dev/sdb3',
'/dev/sdc3')]
self.assertEqual(mock_mu_m_expected_calls, mock_mu_m.call_args_list)
mock_lu_p_expected_calls = [mock.call('/dev/sda5'),
mock.call('/dev/sda6'),
mock.call('/dev/sdb4'),
mock.call('/dev/sdc4')]
self.assertEqual(mock_lu_p_expected_calls, mock_lu_p.call_args_list)
mock_lu_v_expected_calls = [mock.call('os', '/dev/sda5'),
mock.call('image', '/dev/sda6',
'/dev/sdb4', '/dev/sdc4')]
self.assertEqual(mock_lu_v_expected_calls, mock_lu_v.call_args_list)
mock_lu_l_expected_calls = [mock.call('os', 'root', 15360),
mock.call('os', 'swap', 4014),
mock.call('image', 'glance', 175347)]
self.assertEqual(mock_lu_l_expected_calls, mock_lu_l.call_args_list)
mock_fu_mf_expected_calls = [
mock.call('ext2', '', '', '/dev/md0'),
mock.call('ext2', '', '', '/dev/sda4'),
mock.call('ext4', '', '', '/dev/mapper/os-root'),
mock.call('swap', '', '', '/dev/mapper/os-swap'),
mock.call('xfs', '', '', '/dev/mapper/image-glance')]
self.assertEqual(mock_fu_mf_expected_calls, mock_fu_mf.call_args_list)
@mock.patch.object(utils, 'execute')
@mock.patch.object(utils, 'render_and_save')
@mock.patch.object(hu, 'list_block_devices')
def test_do_configdrive(self, mock_lbd, mock_u_ras, mock_u_e):
mock_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE
self.mgr.do_parsing()
self.assertEqual(1, len(self.mgr.image_scheme.images))
self.mgr.do_configdrive()
mock_u_ras_expected_calls = [
mock.call(CONF.nc_template_path, 'cloud_config_ubuntu.jinja2',
mock.ANY, '%s/%s' % (CONF.tmp_path, 'cloud_config.txt')),
mock.call(CONF.nc_template_path, 'boothook_ubuntu.jinja2',
mock.ANY, '%s/%s' % (CONF.tmp_path, 'boothook.txt')),
mock.call(CONF.nc_template_path, 'meta-data_ubuntu.jinja2',
mock.ANY, '%s/%s' % (CONF.tmp_path, 'meta-data'))]
self.assertEqual(mock_u_ras_expected_calls, mock_u_ras.call_args_list)
mock_u_e_expected_calls = [
mock.call('write-mime-multipart',
'--output=%s' % ('%s/%s' % (CONF.tmp_path, 'user-data')),
'%s:text/cloud-boothook' % ('%s/%s' % (CONF.tmp_path,
'boothook.txt')),
'%s:text/cloud-config' % ('%s/%s' % (CONF.tmp_path,
'cloud_config.txt'))
),
mock.call('genisoimage', '-output', CONF.config_drive_path,
'-volid', 'cidata', '-joliet', '-rock',
'%s/%s' % (CONF.tmp_path, 'user-data'),
'%s/%s' % (CONF.tmp_path, 'meta-data'))]
self.assertEqual(mock_u_e_expected_calls, mock_u_e.call_args_list)
self.assertEqual(2, len(self.mgr.image_scheme.images))
cf_drv_img = self.mgr.image_scheme.images[-1]
self.assertEqual('file://%s' % CONF.config_drive_path, cf_drv_img.uri)
self.assertEqual('/dev/sda7',
self.mgr.partition_scheme.configdrive_device())
self.assertEqual('iso9660', cf_drv_img.image_format)
self.assertEqual('raw', cf_drv_img.container)
@mock.patch.object(partition.PartitionScheme, 'configdrive_device')
@mock.patch.object(utils, 'execute')
@mock.patch.object(utils, 'render_and_save')
@mock.patch.object(hu, 'list_block_devices')
def test_do_configdrive_no_configdrive_device(self, mock_lbd, mock_u_ras,
mock_u_e, mock_p_ps_cd):
mock_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE
self.mgr.do_parsing()
mock_p_ps_cd.return_value = None
self.assertRaises(errors.WrongPartitionSchemeError,
self.mgr.do_configdrive)

View File

@ -200,3 +200,10 @@ localhost.localdomain)
mock_mddisplay.return_value = [{'name': '/dev/md0'}]
self.assertRaises(
errors.MDNotFoundError, mu.mdremove, '/dev/md1')
@mock.patch.object(utils, 'execute')
def test_mdclean(self, mock_exec):
mu.mdclean('/dev/md0')
mock_exec.assert_called_once_with('mdadm', '--zero-superblock',
'--force', '/dev/md0',
check_exit_code=[0])

View File

@ -0,0 +1,487 @@
# Copyright 2014 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 oslotest import base as test_base
from fuel_agent.drivers import nailgun
from fuel_agent import errors
from fuel_agent.utils import hardware_utils as hu
PROVISION_SAMPLE_DATA = {
"profile": "ubuntu_1204_x86_64",
"name_servers_search": "\"domain.tld\"",
"uid": "1",
"interfaces": {
"eth2": {
"static": "0",
"mac_address": "08:00:27:b1:d7:15"
},
"eth1": {
"static": "0",
"mac_address": "08:00:27:46:43:60"
},
"eth0": {
"ip_address": "10.20.0.3",
"dns_name": "node-1.domain.tld",
"netmask": "255.255.255.0",
"static": "0",
"mac_address": "08:00:27:79:da:80"
}
},
"interfaces_extra": {
"eth2": {
"onboot": "no",
"peerdns": "no"
},
"eth1": {
"onboot": "no",
"peerdns": "no"
},
"eth0": {
"onboot": "yes",
"peerdns": "no"
}
},
"power_type": "ssh",
"power_user": "root",
"kernel_options": {
"udevrules": "08:00:27:79:da:80_eth0,08:00:27:46:43:60_eth1,"
"08:00:27:b1:d7:15_eth2",
"netcfg/choose_interface": "08:00:27:79:da:80"
},
"power_address": "10.20.0.253",
"name_servers": "\"10.20.0.2\"",
"ks_meta": {
"image_uri": "proto://fake_image_uri",
"image_format": "fake_image_format",
"image_container": "raw",
"timezone": "America/Los_Angeles",
"master_ip": "10.20.0.2",
"mco_enable": 1,
"mco_vhost": "mcollective",
"mco_pskey": "unset",
"mco_user": "mcollective",
"puppet_enable": 0,
"fuel_version": "5.0.1",
"install_log_2_syslog": 1,
"mco_password": "marionette",
"puppet_auto_setup": 1,
"puppet_master": "fuel.domain.tld",
"mco_auto_setup": 1,
"auth_key": "fake_auth_key",
"pm_data": {
"kernel_params": "console=ttyS0,9600 console=tty0 rootdelay=90 "
"nomodeset",
"ks_spaces": [
{
"name": "sda",
"extra": [
"disk/by-id/scsi-SATA_VBOX_HARDDISK_VB69050467-"
"b385c7cd",
"disk/by-id/ata-VBOX_HARDDISK_VB69050467-b385c7cd"
],
"free_space": 64907,
"volumes": [
{
"type": "boot",
"size": 300
},
{
"mount": "/boot",
"size": 200,
"type": "raid",
"file_system": "ext2",
"name": "Boot"
},
{
"mount": "/tmp",
"size": 200,
"type": "partition",
"file_system": "ext2",
"partition_guid": "fake_guid",
"name": "TMP"
},
{
"type": "lvm_meta_pool",
"size": 0
},
{
"size": 19438,
"type": "pv",
"lvm_meta_size": 64,
"vg": "os"
},
{
"size": 45597,
"type": "pv",
"lvm_meta_size": 64,
"vg": "image"
}
],
"type": "disk",
"id": "sda",
"size": 65535
},
{
"name": "sdb",
"extra": [
"disk/by-id/scsi-SATA_VBOX_HARDDISK_VBf2923215-"
"708af674",
"disk/by-id/ata-VBOX_HARDDISK_VBf2923215-708af674"
],
"free_space": 64907,
"volumes": [
{
"type": "boot",
"size": 300
},
{
"mount": "/boot",
"size": 200,
"type": "raid",
"file_system": "ext2",
"name": "Boot"
},
{
"type": "lvm_meta_pool",
"size": 64
},
{
"size": 0,
"type": "pv",
"lvm_meta_size": 0,
"vg": "os"
},
{
"size": 64971,
"type": "pv",
"lvm_meta_size": 64,
"vg": "image"
}
],
"type": "disk",
"id": "sdb",
"size": 65535
},
{
"name": "sdc",
"extra": [
"disk/by-id/scsi-SATA_VBOX_HARDDISK_VB50ee61eb-"
"84e74fdf",
"disk/by-id/ata-VBOX_HARDDISK_VB50ee61eb-84e74fdf"
],
"free_space": 64907,
"volumes": [
{
"type": "boot",
"size": 300
},
{
"mount": "/boot",
"size": 200,
"type": "raid",
"file_system": "ext2",
"name": "Boot"
},
{
"type": "lvm_meta_pool",
"size": 64
},
{
"size": 0,
"type": "pv",
"lvm_meta_size": 0,
"vg": "os"
},
{
"size": 64971,
"type": "pv",
"lvm_meta_size": 64,
"vg": "image"
}
],
"type": "disk",
"id": "disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0",
"size": 65535
},
{
"_allocate_size": "min",
"label": "Base System",
"min_size": 19374,
"volumes": [
{
"mount": "/",
"size": 15360,
"type": "lv",
"name": "root",
"file_system": "ext4"
},
{
"mount": "swap",
"size": 4014,
"type": "lv",
"name": "swap",
"file_system": "swap"
}
],
"type": "vg",
"id": "os"
},
{
"_allocate_size": "min",
"label": "Zero size volume",
"min_size": 0,
"volumes": [
{
"mount": "none",
"size": 0,
"type": "lv",
"name": "zero_size",
"file_system": "xfs"
}
],
"type": "vg",
"id": "zero_size"
},
{
"_allocate_size": "all",
"label": "Image Storage",
"min_size": 5120,
"volumes": [
{
"mount": "/var/lib/glance",
"size": 175347,
"type": "lv",
"name": "glance",
"file_system": "xfs"
}
],
"type": "vg",
"id": "image"
}
]
},
"mco_connector": "rabbitmq",
"mco_host": "10.20.0.2"
},
"name": "node-1",
"hostname": "node-1.domain.tld",
"slave_name": "node-1",
"power_pass": "/root/.ssh/bootstrap.rsa",
"netboot_enabled": "1"
}
LIST_BLOCK_DEVICES_SAMPLE = [
{'uspec':
{'DEVLINKS': [
'disk/by-id/scsi-SATA_VBOX_HARDDISK_VB69050467-b385c7cd',
'/dev/disk/by-id/ata-VBOX_HARDDISK_VB69050467-b385c7cd',
'/dev/disk/by-id/wwn-fake_wwn_1',
'/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0'],
'ID_SERIAL_SHORT': 'fake_serial_1',
'ID_WWN': 'fake_wwn_1',
'DEVPATH': '/devices/pci0000:00/0000:00:1f.2/ata1/host0/'
'target0:0:0/0:0:0:0/block/sda',
'ID_MODEL': 'fake_id_model',
'DEVNAME': '/dev/sda',
'MAJOR': '8',
'DEVTYPE': 'disk', 'MINOR': '0', 'ID_BUS': 'ata'
},
'startsec': '0',
'device': '/dev/sda',
'espec': {'state': 'running', 'timeout': '30', 'removable': '0'},
'bspec': {
'sz': '976773168', 'iomin': '4096', 'size64': '500107862016',
'ss': '512', 'ioopt': '0', 'alignoff': '0', 'pbsz': '4096',
'ra': '256', 'ro': '0', 'maxsect': '1024'
},
'size': 500107862016},
{'uspec':
{'DEVLINKS': [
'/dev/disk/by-id/ata-VBOX_HARDDISK_VBf2923215-708af674',
'/dev/disk/by-id/scsi-SATA_VBOX_HARDDISK_VBf2923215-708af674',
'/dev/disk/by-id/wwn-fake_wwn_2'],
'ID_SERIAL_SHORT': 'fake_serial_2',
'ID_WWN': 'fake_wwn_2',
'DEVPATH': '/devices/pci0000:00/0000:00:3f.2/ata2/host0/'
'target0:0:0/0:0:0:0/block/sdb',
'ID_MODEL': 'fake_id_model',
'DEVNAME': '/dev/sdb',
'MAJOR': '8',
'DEVTYPE': 'disk', 'MINOR': '0', 'ID_BUS': 'ata'
},
'startsec': '0',
'device': '/dev/sdb',
'espec': {'state': 'running', 'timeout': '30', 'removable': '0'},
'bspec': {
'sz': '976773168', 'iomin': '4096', 'size64': '500107862016',
'ss': '512', 'ioopt': '0', 'alignoff': '0', 'pbsz': '4096',
'ra': '256', 'ro': '0', 'maxsect': '1024'},
'size': 500107862016},
{'uspec':
{'DEVLINKS': [
'/dev/disk/by-id/ata-VBOX_HARDDISK_VB50ee61eb-84e74fdf',
'/dev/disk/by-id/scsi-SATA_VBOX_HARDDISK_VB50ee61eb-84e74fdf',
'/dev/disk/by-id/wwn-fake_wwn_3',
'/dev/disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0'],
'ID_SERIAL_SHORT': 'fake_serial_3',
'ID_WWN': 'fake_wwn_3',
'DEVPATH': '/devices/pci0000:00/0000:00:0d.0/ata4/host0/target0:0:0/'
'0:0:0:0/block/sdc',
'ID_MODEL': 'fake_id_model',
'DEVNAME': '/dev/sdc',
'MAJOR': '8',
'DEVTYPE': 'disk', 'MINOR': '0', 'ID_BUS': 'ata'},
'startsec': '0',
'device': '/dev/sdc',
'espec': {'state': 'running', 'timeout': '30', 'removable': '0'},
'bspec': {
'sz': '976773168', 'iomin': '4096', 'size64': '500107862016',
'ss': '512', 'ioopt': '0', 'alignoff': '0', 'pbsz': '4096',
'ra': '256', 'ro': '0', 'maxsect': '1024'},
'size': 500107862016},
]
class TestNailgun(test_base.BaseTestCase):
def setUp(self):
super(TestNailgun, self).setUp()
self.drv = nailgun.Nailgun(PROVISION_SAMPLE_DATA)
def test_match_device_by_id_matches(self):
fake_ks_disk = {
"extra": [
"disk/by-id/fake_scsi_matches",
"disk/by-id/fake_ata_dont_matches"
]
}
fake_hu_disk = {
"uspec": {
"DEVLINKS": [
"/dev/disk/by-id/fake_scsi_matches",
"/dev/disk/by-path/fake_path"
]
}
}
self.assertTrue(nailgun.match_device(fake_hu_disk, fake_ks_disk))
def test_match_device_id_matches(self):
fake_ks_disk = {
"extra": [
"disk/by-id/fake_scsi_dont_matches",
"disk/by-id/fake_ata_dont_matches"
],
"id": "sdd"
}
fake_hu_disk = {
"uspec": {
"DEVLINKS": [
"/dev/disk/by-id/fake_scsi_matches",
"/dev/disk/by-path/fake_path",
"/dev/sdd"
]
}
}
self.assertTrue(nailgun.match_device(fake_hu_disk, fake_ks_disk))
def test_match_device_dont_macthes(self):
fake_ks_disk = {
"extra": [
"disk/by-id/fake_scsi_dont_matches",
"disk/by-id/fake_ata_dont_matches"
],
"id": "sda"
}
fake_hu_disk = {
"uspec": {
"DEVLINKS": [
"/dev/disk/by-id/fake_scsi_matches",
"/dev/disk/by-path/fake_path",
"/dev/sdd"
]
}
}
self.assertFalse(nailgun.match_device(fake_hu_disk, fake_ks_disk))
def test_configdrive_scheme(self):
cd_scheme = self.drv.configdrive_scheme()
self.assertEqual('fake_auth_key', cd_scheme.common.ssh_auth_key)
self.assertEqual('node-1.domain.tld', cd_scheme.common.hostname)
self.assertEqual('node-1.domain.tld', cd_scheme.common.fqdn)
self.assertEqual('node-1.domain.tld', cd_scheme.common.fqdn)
self.assertEqual('"10.20.0.2"', cd_scheme.common.name_servers)
self.assertEqual('"domain.tld"', cd_scheme.common.search_domain)
self.assertEqual('10.20.0.2', cd_scheme.common.master_ip)
self.assertEqual('http://10.20.0.2:8000/api',
cd_scheme.common.master_url)
self.assertEqual('08:00:27:79:da:80_eth0,08:00:27:46:43:60_eth1,'
'08:00:27:b1:d7:15_eth2', cd_scheme.common.udevrules)
self.assertEqual('08:00:27:79:da:80', cd_scheme.common.admin_mac)
self.assertEqual('10.20.0.3', cd_scheme.common.admin_ip)
self.assertEqual('255.255.255.0', cd_scheme.common.admin_mask)
self.assertEqual('eth0', cd_scheme.common.admin_iface_name)
self.assertEqual('America/Los_Angeles', cd_scheme.common.timezone)
self.assertEqual('fuel.domain.tld', cd_scheme.puppet.master)
self.assertEqual('unset', cd_scheme.mcollective.pskey)
self.assertEqual('mcollective', cd_scheme.mcollective.vhost)
self.assertEqual('10.20.0.2', cd_scheme.mcollective.host)
self.assertEqual('mcollective', cd_scheme.mcollective.user)
self.assertEqual('marionette', cd_scheme.mcollective.password)
self.assertEqual('rabbitmq', cd_scheme.mcollective.connector)
self.assertEqual('ubuntu', cd_scheme.profile)
@mock.patch.object(hu, 'list_block_devices')
def test_partition_scheme(self, mock_lbd):
mock_lbd.return_value = LIST_BLOCK_DEVICES_SAMPLE
p_scheme = self.drv.partition_scheme()
self.assertEqual(5, len(p_scheme.fss))
self.assertEqual(4, len(p_scheme.pvs))
self.assertEqual(3, len(p_scheme.lvs))
self.assertEqual(2, len(p_scheme.vgs))
self.assertEqual(1, len(p_scheme.mds))
self.assertEqual(3, len(p_scheme.parteds))
@mock.patch.object(hu, 'list_block_devices')
def test_image_scheme(self, mock_lbd):
mock_lbd.return_value = LIST_BLOCK_DEVICES_SAMPLE
p_scheme = self.drv.partition_scheme()
i_scheme = self.drv.image_scheme(p_scheme)
self.assertEqual(1, len(i_scheme.images))
img = i_scheme.images[0]
self.assertEqual('raw', img.container)
self.assertEqual('fake_image_format', img.image_format)
self.assertEqual('/dev/mapper/os-root', img.target_device)
self.assertEqual('proto://fake_image_uri', img.uri)
self.assertEqual(None, img.size)
def test_getlabel(self):
self.assertEqual('', self.drv._getlabel(None))
long_label = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'
self.assertEqual(' -L %s ' % long_label[:12],
self.drv._getlabel(long_label))
@mock.patch.object(hu, 'list_block_devices')
def test_disk_dev_not_found(self, mock_lbd):
mock_lbd.return_value = LIST_BLOCK_DEVICES_SAMPLE
fake_ks_disk = {
"name": "fake",
"extra": [
"disk/by-id/fake_scsi_matches",
"disk/by-id/fake_ata_dont_matches"
]
}
self.assertRaises(errors.DiskNotFoundError, self.drv._disk_dev,
fake_ks_disk)

View File

@ -0,0 +1,250 @@
# Copyright 2014 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 oslotest import base as test_base
from fuel_agent import errors
from fuel_agent.objects import partition
class TestMD(test_base.BaseTestCase):
def setUp(self):
super(TestMD, self).setUp()
self.md = partition.Md('name', 'level')
def test_add_device_ok(self):
self.assertEqual(0, len(self.md.devices))
self.md.add_device('device')
self.assertEqual(1, len(self.md.devices))
self.assertEqual('device', self.md.devices[0])
def test_add_device_in_spares_fail(self):
self.assertEqual(0, len(self.md.devices))
self.assertEqual(0, len(self.md.spares))
self.md.add_spare('device')
self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_device,
'device')
def test_add_device_in_devices_fail(self):
self.assertEqual(0, len(self.md.devices))
self.assertEqual(0, len(self.md.spares))
self.md.add_device('device')
self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_device,
'device')
def test_add_spare_in_spares_fail(self):
self.assertEqual(0, len(self.md.devices))
self.assertEqual(0, len(self.md.spares))
self.md.add_spare('device')
self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_spare,
'device')
def test_add_spare_in_devices_fail(self):
self.assertEqual(0, len(self.md.devices))
self.assertEqual(0, len(self.md.spares))
self.md.add_device('device')
self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_spare,
'device')
class TestPartition(test_base.BaseTestCase):
def setUp(self):
super(TestPartition, self).setUp()
self.pt = partition.Partition('name', 'count', 'device', 'begin',
'end', 'partition_type')
def test_set_flag(self):
self.assertEqual(0, len(self.pt.flags))
self.pt.set_flag('fake_flag')
self.assertEqual(1, len(self.pt.flags))
self.assertIn('fake_flag', self.pt.flags)
class TestPartitionScheme(test_base.BaseTestCase):
def setUp(self):
super(TestPartitionScheme, self).setUp()
self.p_scheme = partition.PartitionScheme()
def test_root_device_not_found(self):
self.assertRaises(errors.WrongPartitionSchemeError,
self.p_scheme.root_device)
def test_fs_by_device(self):
expected_fs = partition.Fs('device')
self.p_scheme.fss.append(expected_fs)
self.p_scheme.fss.append(partition.Fs('wrong_device'))
actual_fs = self.p_scheme.fs_by_device('device')
self.assertEqual(expected_fs, actual_fs)
def test_fs_by_mount(self):
expected_fs = partition.Fs('d', mount='mount')
self.p_scheme.fss.append(expected_fs)
self.p_scheme.fss.append(partition.Fs('w_d', mount='wrong_mount'))
actual_fs = self.p_scheme.fs_by_mount('mount')
self.assertEqual(expected_fs, actual_fs)
def test_pv_by_name(self):
expected_pv = partition.Pv('pv')
self.p_scheme.pvs.append(expected_pv)
self.p_scheme.pvs.append(partition.Pv('wrong_pv'))
actual_pv = self.p_scheme.pv_by_name('pv')
self.assertEqual(expected_pv, actual_pv)
def test_vg_by_name(self):
expected_vg = partition.Vg('vg')
self.p_scheme.vgs.append(expected_vg)
self.p_scheme.vgs.append(partition.Vg('wrong_vg'))
actual_vg = self.p_scheme.vg_by_name('vg')
self.assertEqual(expected_vg, actual_vg)
def test_vg_attach_by_name(self):
self.p_scheme.vg_attach_by_name('pvname', 'vgname')
self.assertEqual(1, len(self.p_scheme.pvs))
self.assertEqual(1, len(self.p_scheme.vgs))
self.assertIn('pvname', self.p_scheme.vgs[0].pvnames)
self.assertIn('vgname', self.p_scheme.vgs[0].name)
def test_md_next_name_ok(self):
expected_name = '/dev/md0'
self.assertEqual(expected_name, self.p_scheme.md_next_name())
def test_md_next_name_fail(self):
self.p_scheme.mds = [
partition.Md('/dev/md%s' % x, 'level') for x in range(0, 128)]
self.assertRaises(errors.MDAlreadyExistsError,
self.p_scheme.md_next_name)
def test_md_by_name(self):
self.assertEqual(0, len(self.p_scheme.mds))
expected_md = partition.Md('name', 'level')
self.p_scheme.mds.append(expected_md)
self.p_scheme.mds.append(partition.Md('wrong_name', 'level'))
self.assertEqual(expected_md, self.p_scheme.md_by_name('name'))
def test_md_by_mount(self):
self.assertEqual(0, len(self.p_scheme.mds))
self.assertEqual(0, len(self.p_scheme.fss))
expected_md = partition.Md('name', 'level')
expected_fs = partition.Fs('name', mount='mount')
self.p_scheme.mds.append(expected_md)
self.p_scheme.fss.append(expected_fs)
self.p_scheme.fss.append(partition.Fs('wrong_name',
mount='wrong_mount'))
self.assertEqual(expected_md, self.p_scheme.md_by_mount('mount'))
def test_md_attach_by_mount_md_exists(self):
self.assertEqual(0, len(self.p_scheme.mds))
self.assertEqual(0, len(self.p_scheme.fss))
expected_md = partition.Md('name', 'level')
expected_fs = partition.Fs('name', mount='mount')
self.p_scheme.mds.append(expected_md)
self.p_scheme.fss.append(expected_fs)
actual_md = self.p_scheme.md_attach_by_mount('device', 'mount')
self.assertIn('device', actual_md.devices)
self.assertEqual(expected_md, actual_md)
def test_md_attach_by_mount_no_md(self):
self.assertEqual(0, len(self.p_scheme.mds))
self.assertEqual(0, len(self.p_scheme.fss))
actual_md = self.p_scheme.md_attach_by_mount(
'device', 'mount', fs_type='fs_type', fs_options='-F',
fs_label='fs_label', name='name', level='level')
self.assertIn('device', actual_md.devices)
self.assertEqual(1, len(self.p_scheme.fss))
self.assertEqual('name', self.p_scheme.fss[0].device)
self.assertEqual('mount', self.p_scheme.fss[0].mount)
self.assertEqual('fs_type', self.p_scheme.fss[0].type)
self.assertEqual('fs_label', self.p_scheme.fss[0].label)
self.assertEqual('-F', self.p_scheme.fss[0].options)
class TestParted(test_base.BaseTestCase):
def setUp(self):
super(TestParted, self).setUp()
self.prtd = partition.Parted('name', 'label')
@mock.patch.object(partition.Parted, 'next_count')
@mock.patch.object(partition.Parted, 'next_type')
def test_next_name_none(self, nt_mock, nc_mock):
nc_mock.return_value = 1
nt_mock.return_value = 'extended'
self.assertEqual(None, self.prtd.next_name())
@mock.patch.object(partition.Parted, 'next_count')
@mock.patch.object(partition.Parted, 'next_type')
def test_next_name_no_separator(self, nt_mock, nc_mock):
nc_mock.return_value = 1
nt_mock.return_value = 'not_extended'
expected_name = '%s%s' % (self.prtd.name, 1)
self.assertEqual(expected_name, self.prtd.next_name())
@mock.patch.object(partition.Parted, 'next_count')
@mock.patch.object(partition.Parted, 'next_type')
def test_next_name_with_separator(self, nt_mock, nc_mock):
nc_mock.return_value = 1
nt_mock.return_value = 'not_extended'
self.prtd.name = 'cciss or loop'
expected_name = '%sp%s' % (self.prtd.name, 1)
self.assertEqual(expected_name, self.prtd.next_name())
def test_next_begin_empty_partitions(self):
self.assertEqual(0, self.prtd.next_begin())
def test_next_begin_last_extended_partition(self):
self.prtd.partitions.append(
partition.Partition('name', 'count', 'device', 'begin', 'end',
'extended'))
self.assertEqual('begin', self.prtd.next_begin())
def test_next_begin_no_last_extended_partition(self):
self.prtd.partitions.append(
partition.Partition('name', 'count', 'device', 'begin', 'end',
'primary'))
self.assertEqual('end', self.prtd.next_begin())
def test_next_count_no_logical(self):
self.assertEqual(1, self.prtd.next_count('primary'))
def test_next_count_has_logical(self):
self.prtd.partitions.append(
partition.Partition('name', 'count', 'device', 'begin', 'end',
'logical'))
self.assertEqual(6, self.prtd.next_count('logical'))
def test_next_type_gpt(self):
self.prtd.label = 'gpt'
self.assertEqual('primary', self.prtd.next_type())
def test_next_type_no_extended(self):
self.prtd.label = 'msdos'
self.assertEqual('primary', self.prtd.next_type())
self.prtd.partitions.extend(
3 * [partition.Partition('name', 'count', 'device', 'begin',
'end', 'primary')])
self.assertEqual('extended', self.prtd.next_type())
def test_next_type_has_extended(self):
self.prtd.label = 'msdos'
self.prtd.partitions.append(
partition.Partition('name', 'count', 'device', 'begin', 'end',
'extended'))
self.assertEqual('logical', self.prtd.next_type())
def test_primary(self):
expected_partitions = [partition.Partition('name', 'count', 'device',
'begin', 'end', 'primary')]
self.prtd.partitions.extend(expected_partitions)
self.assertEqual(expected_partitions, self.prtd.primary)

View File

@ -193,3 +193,41 @@ class TestPartitionUtils(test_base.BaseTestCase):
}
self.assertRaises(errors.PartitionNotFoundError, pu.remove_partition,
'/dev/fake', 3)
@mock.patch.object(utils, 'execute')
def test_set_gpt_type(self, mock_exec):
pu.set_gpt_type('dev', 'num', 'type')
mock_exec.assert_called_once_with('sgdisk',
'--typecode=%s:%s' % ('num', 'type'),
'dev', check_exit_code=[0])
@mock.patch.object(utils, 'execute')
def test_info(self, mock_exec):
mock_exec.return_value = [
'BYT;\n'
'/dev/fake:476940MiB:scsi:512:4096:msdos:ATA 1BD14;\n'
'1:0.03MiB:1.00MiB:0.97MiB:free;\n'
'1:1.00MiB:191MiB:190MiB:ext3::boot;\n'
'2:191MiB:476939MiB:476748MiB:::lvm;\n'
'1:476939MiB:476940MiB:1.02MiB:free;\n'
]
expected = {'generic': {'dev': '/dev/fake',
'logical_block': 512,
'model': 'ATA 1BD14',
'physical_block': 4096,
'size': 476940,
'table': 'msdos'},
'parts': [{'begin': 1, 'end': 1, 'fstype': 'free',
'num': 1, 'size': 1},
{'begin': 1, 'end': 191, 'fstype': 'ext3',
'num': 1, 'size': 190},
{'begin': 191, 'end': 476939, 'fstype': None,
'num': 2, 'size': 476748},
{'begin': 476939, 'end': 476940,
'fstype': 'free', 'num': 1, 'size': 2}]}
actual = pu.info('/dev/fake')
self.assertEqual(expected, actual)
mock_exec.assert_called_once_with('parted', '-s', '/dev/fake', '-m',
'unit', 'MiB', 'print', 'free',
check_exit_code=[0, 1])

View File

@ -17,6 +17,10 @@ import os
import tempfile
import testtools
import mock
import stevedore
from fuel_agent import errors
from fuel_agent.openstack.common import processutils
from fuel_agent.utils import utils
@ -24,6 +28,13 @@ from fuel_agent.utils import utils
class ExecuteTestCase(testtools.TestCase):
"""This class is partly based on the same class in openstack/ironic."""
def setUp(self):
super(ExecuteTestCase, self).setUp()
fake_driver = stevedore.extension.Extension('fake_driver', None, None,
'fake_obj')
self.drv_manager = stevedore.driver.DriverManager.make_test_instance(
fake_driver)
def test_parse_unit(self):
self.assertEqual(utils.parse_unit('1.00m', 'm', ceil=True), 1)
self.assertEqual(utils.parse_unit('1.00m', 'm', ceil=False), 1)
@ -120,3 +131,28 @@ grep foo
finally:
os.unlink(tmpfilename)
os.unlink(tmpfilename2)
@mock.patch('stevedore.driver.DriverManager')
def test_get_driver(self, mock_drv_manager):
mock_drv_manager.return_value = self.drv_manager
self.assertEqual('fake_obj', utils.get_driver('fake_driver'))
@mock.patch('jinja2.Environment')
@mock.patch('jinja2.FileSystemLoader')
@mock.patch('six.moves.builtins.open')
def test_render_and_save_fail(self, mock_open, mock_j_lo, mock_j_env):
mock_open.side_effect = Exception('foo')
self.assertRaises(errors.TemplateWriteError, utils.render_and_save,
'fake_dir', 'fake_tmpl_name', 'fake_data',
'fake_file_name')
@mock.patch('jinja2.Environment')
@mock.patch('jinja2.FileSystemLoader')
@mock.patch('six.moves.builtins.open')
def test_render_and_save_ok(self, mock_open, mock_j_lo, mock_j_env):
mock_render = mock.Mock()
mock_render.render.return_value = 'fake_data'
mock_j_env.get_template.return_value = mock_render
utils.render_and_save('fake_dir', 'fake_tmpl_name', 'fake_data',
'fake_file_name')
mock_open.assert_called_once_with('fake_file_name', 'w')

View File

@ -136,7 +136,7 @@ def vgreduce(vgname, pvname, *args):
# check if vg exists
if not filter(lambda x: x['name'] == vgname, vgdisplay()):
raise errors.VGNotFoundError(
'Error while extending vg: vg %s not found' % vgname)
'Error while reducing vg: vg %s not found' % vgname)
pvnames = [pvname] + list(args)
# check if all necessary pv are attached to vg
if not set(pvnames).issubset(
@ -151,7 +151,7 @@ def vgremove(vgname):
# check if vg exists
if not filter(lambda x: x['name'] == vgname, vgdisplay()):
raise errors.VGNotFoundError(
'Error while extending vg: vg %s not found' % vgname)
'Error while removing vg: vg %s not found' % vgname)
utils.execute('vgremove', '-f', vgname, check_exit_code=[0])
@ -161,7 +161,9 @@ def lvdisplay():
'-C',
'--noheading',
'--units', 'm',
'--options', 'lv_name,lv_size,vg_name,lv_uuid,lv_path',
#NOTE(agordeev): lv_path had been removed from options
# since versions of lvdisplay prior 2.02.68 don't have it.
'--options', 'lv_name,lv_size,vg_name,lv_uuid',
'--separator', ';',
check_exit_code=[0])
@ -176,7 +178,8 @@ def lvdisplay():
'size': utils.parse_unit(lv_params[1], 'm'),
'vg': lv_params[2],
'uuid': lv_params[3],
'path': lv_params[4]
#NOTE(agordeev): simulate lv_path with '/dev/$vg_name/$lv_name'
'path': '/dev/%s/%s' % (lv_params[2], lv_params[0])
})
LOG.debug('Found logical volumes: {0}'.format(lvs))
return lvs
@ -188,7 +191,7 @@ def lvcreate(vgname, lvname, size):
# check if vg exists
if not vg:
raise errors.VGNotFoundError(
'Error while extending vg: vg %s not found' % vgname)
'Error while creating vg: vg %s not found' % vgname)
# check if enough space is available
if vg[0]['free'] < size:
raise errors.NotEnoughSpaceError(

View File

@ -18,6 +18,7 @@ import math
import jinja2
import stevedore.driver
from fuel_agent import errors
from fuel_agent.openstack.common import gettextutils as gtu
from fuel_agent.openstack.common import log as logging
from fuel_agent.openstack.common import processutils
@ -67,5 +68,6 @@ def render_and_save(tmpl_dir, tmpl_name, tmpl_data, file_name):
with open(file_name, 'w') as f:
f.write(output)
except Exception:
raise Exception('Something goes wrong while trying to save'
'templated data to {0}'.format(file_name))
raise errors.TemplateWriteError(
'Something goes wrong while trying to save'
'templated data to {0}'.format(file_name))