From 9e22866f3e451b21e706661cd754f8b7b5eae3cb Mon Sep 17 00:00:00 2001 From: Sebastian Kalinowski Date: Tue, 14 Jul 2015 14:31:52 +0200 Subject: [PATCH] Add simple data driver for partitioning info This new "simple" data driver takes serialized partitioning info that is provided before provisioning from external tool. It does not make any calculations for that data and expect it to have all required informations about partitioning. Other changes: * added unittest2 to use some of it features * added requests_mock to mock http requests * added objects conversion to/from dictionary to make serialization easier * added a common "interface" for data drivers * fixed test for "do_build_image" - now a correct data driver is used Change-Id: I673cde6f0ead9945919a87cd1cfce7ed09c6e593 Implements: blueprint volume-manager-refactoring --- fuel_agent/drivers/base.py | 20 ++ fuel_agent/drivers/nailgun.py | 69 +++- fuel_agent/drivers/simple.py | 67 ++++ fuel_agent/objects/__init__.py | 13 +- fuel_agent/objects/base.py | 32 ++ fuel_agent/objects/partition.py | 156 ++++++++- fuel_agent/tests/base.py | 27 ++ .../tests/fixtures/simple_nailgun_driver.json | 326 ++++++++++++++++++ fuel_agent/tests/test_manager.py | 45 ++- fuel_agent/tests/test_nailgun_build_image.py | 63 ++-- fuel_agent/tests/test_partition.py | 176 ++++++++-- .../tests/test_simple_nailgun_driver.py | 224 ++++++++++++ fuel_agent/utils/decorators.py | 39 +++ setup.cfg | 1 + test-requirements.txt | 3 + 15 files changed, 1160 insertions(+), 101 deletions(-) create mode 100644 fuel_agent/drivers/simple.py create mode 100644 fuel_agent/objects/base.py create mode 100644 fuel_agent/tests/base.py create mode 100644 fuel_agent/tests/fixtures/simple_nailgun_driver.json create mode 100644 fuel_agent/tests/test_simple_nailgun_driver.py create mode 100644 fuel_agent/utils/decorators.py diff --git a/fuel_agent/drivers/base.py b/fuel_agent/drivers/base.py index 77003f9..7fb7f10 100644 --- a/fuel_agent/drivers/base.py +++ b/fuel_agent/drivers/base.py @@ -28,3 +28,23 @@ class BaseDataDriver(object): def __init__(self, data): self.data = copy.deepcopy(data) + + @abc.abstractproperty + def partition_scheme(self): + """Retruns instance of PartionScheme object""" + + @abc.abstractproperty + def image_scheme(self): + """Returns instance of ImageScheme object""" + + @abc.abstractproperty + def grub(self): + """Returns instance of Grub object""" + + @abc.abstractproperty + def operating_system(self): + """Returns instance of OperatingSystem object""" + + @abc.abstractproperty + def configdrive_scheme(self): + """Returns instance of ConfigDriveScheme object""" diff --git a/fuel_agent/drivers/nailgun.py b/fuel_agent/drivers/nailgun.py index 63befa3..155e9f5 100644 --- a/fuel_agent/drivers/nailgun.py +++ b/fuel_agent/drivers/nailgun.py @@ -15,12 +15,12 @@ import itertools import math import os -import six -import yaml +import six from six.moves.urllib.parse import urljoin from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlsplit +import yaml from fuel_agent.drivers.base import BaseDataDriver from fuel_agent.drivers import ks_spaces_validator @@ -67,6 +67,8 @@ def match_device(hu_disk, ks_disk): class Nailgun(BaseDataDriver): + """Driver for parsing regular volumes metadata from Nailgun.""" + def __init__(self, data): super(Nailgun, self).__init__(data) @@ -79,11 +81,31 @@ class Nailgun(BaseDataDriver): # get rid of md over all disks for /boot partition. self._boot_done = False - self.partition_scheme = self.parse_partition_scheme() - self.grub = self.parse_grub() - self.configdrive_scheme = self.parse_configdrive_scheme() + self._partition_scheme = self.parse_partition_scheme() + self._grub = self.parse_grub() + self._configdrive_scheme = self.parse_configdrive_scheme() # parsing image scheme needs partition scheme has been parsed - self.image_scheme = self.parse_image_scheme() + self._image_scheme = self.parse_image_scheme() + + @property + def partition_scheme(self): + return self._partition_scheme + + @property + def image_scheme(self): + return self._image_scheme + + @property + def grub(self): + return self._grub + + @property + def operating_system(self): + return None + + @property + def configdrive_scheme(self): + return self._configdrive_scheme def partition_data(self): return self.data['ks_meta']['pm_data']['ks_spaces'] @@ -346,7 +368,7 @@ class Nailgun(BaseDataDriver): for volume in vg['volumes']: LOG.debug('Processing lv %s' % volume['name']) if volume['size'] <= 0: - LOG.debug('Lv size is zero. Skipping.') + LOG.debug('LogicalVolume size is zero. Skipping.') continue if volume['type'] == 'lv': @@ -543,8 +565,31 @@ class NailgunBuildImage(BaseDataDriver): def __init__(self, data): super(NailgunBuildImage, self).__init__(data) + self._image_scheme = objects.ImageScheme() + self._partition_scheme = objects.PartitionScheme() + self.parse_schemes() - self.parse_operating_system() + self._operating_system = self.parse_operating_system() + + @property + def partition_scheme(self): + return self._partition_scheme + + @property + def image_scheme(self): + return self._image_scheme + + @property + def grub(self): + return None + + @property + def operating_system(self): + return self._operating_system + + @property + def configdrive_scheme(self): + return None def parse_operating_system(self): if self.data.get('codename').lower() != 'trusty': @@ -563,11 +608,9 @@ class NailgunBuildImage(BaseDataDriver): section=repo['section'], priority=repo['priority'])) - self.operating_system = objects.Ubuntu(repos=repos, packages=packages) + return objects.Ubuntu(repos=repos, packages=packages) def parse_schemes(self): - self.image_scheme = objects.ImageScheme() - self.partition_scheme = objects.PartitionScheme() for mount, image in six.iteritems(self.data['image_data']): filename = os.path.basename(urlsplit(image['uri']).path) @@ -575,13 +618,13 @@ class NailgunBuildImage(BaseDataDriver): # during initialization. device = objects.Loop() - self.image_scheme.add_image( + self._image_scheme.add_image( uri='file://' + os.path.join(self.data['output'], filename), format=image['format'], container=image['container'], target_device=device) - self.partition_scheme.add_fs( + self._partition_scheme.add_fs( device=device, mount=mount, fs_type=image['format']) diff --git a/fuel_agent/drivers/simple.py b/fuel_agent/drivers/simple.py new file mode 100644 index 0000000..6c617bc --- /dev/null +++ b/fuel_agent/drivers/simple.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 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 fuel_agent.drivers import nailgun +from fuel_agent import objects + + +class NailgunSimpleDriver(nailgun.Nailgun): + """Simple driver that do not make any computations. + + This driver digest information that already has all required + information how to perform partitioning. + Service that sends data to fuel_agent is responsible for preparing + it in correct format. + """ + + @property + def partition_data(self): + return self.data.get('partitioning', {}) + + @classmethod + def parse_lv_data(cls, raw_lvs): + return [objects.LV.from_dict(lv) for lv in raw_lvs] + + @classmethod + def parse_pv_data(cls, raw_pvs): + return [objects.PV.from_dict(pv) for pv in raw_pvs] + + @classmethod + def parse_fs_data(cls, raw_fss): + return [objects.FS.from_dict(fs) for fs in raw_fss] + + @classmethod + def parse_vg_data(cls, raw_vgs): + return [objects.VG.from_dict(vg) for vg in raw_vgs] + + @classmethod + def parse_md_data(cls, raw_mds): + return [objects.MD.from_dict(md) for md in raw_mds] + + @classmethod + def parse_parted_data(cls, raw_parteds): + return [objects.Parted.from_dict(parted) for parted in raw_parteds] + + def parse_partition_scheme(self): + partition_scheme = objects.PartitionScheme() + + for obj in ('lv', 'pv', 'fs', 'vg', 'md', 'parted'): + attr = '{0}s'.format(obj) + parse_method = getattr(self, 'parse_{0}_data'.format(obj)) + raw = self.partition_data.get(attr, {}) + setattr(partition_scheme, attr, parse_method(raw)) + + return partition_scheme diff --git a/fuel_agent/objects/__init__.py b/fuel_agent/objects/__init__.py index fd7812b..213f402 100644 --- a/fuel_agent/objects/__init__.py +++ b/fuel_agent/objects/__init__.py @@ -22,18 +22,19 @@ from fuel_agent.objects.image import Image from fuel_agent.objects.image import ImageScheme from fuel_agent.objects.operating_system import OperatingSystem from fuel_agent.objects.operating_system import Ubuntu -from fuel_agent.objects.partition import Fs -from fuel_agent.objects.partition import Lv -from fuel_agent.objects.partition import Md +from fuel_agent.objects.partition import FS +from fuel_agent.objects.partition import LV +from fuel_agent.objects.partition import MD +from fuel_agent.objects.partition import Parted from fuel_agent.objects.partition import Partition from fuel_agent.objects.partition import PartitionScheme -from fuel_agent.objects.partition import Pv -from fuel_agent.objects.partition import Vg +from fuel_agent.objects.partition import PV +from fuel_agent.objects.partition import VG from fuel_agent.objects.repo import DEBRepo from fuel_agent.objects.repo import Repo __all__ = [ - 'Partition', 'Pv', 'Vg', 'Lv', 'Md', 'Fs', 'PartitionScheme', + 'Partition', 'Parted', 'PV', 'VG', 'LV', 'MD', 'FS', 'PartitionScheme', 'ConfigDriveCommon', 'ConfigDrivePuppet', 'ConfigDriveMcollective', 'ConfigDriveScheme', 'Image', 'ImageScheme', 'Grub', 'OperatingSystem', 'Ubuntu', diff --git a/fuel_agent/objects/base.py b/fuel_agent/objects/base.py new file mode 100644 index 0000000..ff78d46 --- /dev/null +++ b/fuel_agent/objects/base.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 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 abc +import six + +from fuel_agent.utils.decorators import abstractclassmethod + + +@six.add_metaclass(abc.ABCMeta) +class Serializable(object): + + @abc.abstractmethod + def to_dict(self): + pass + + @abstractclassmethod + def from_dict(cls, data): + pass diff --git a/fuel_agent/objects/partition.py b/fuel_agent/objects/partition.py index c4638b1..acd1467 100644 --- a/fuel_agent/objects/partition.py +++ b/fuel_agent/objects/partition.py @@ -12,20 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import os from fuel_agent import errors +from fuel_agent.objects import base from fuel_agent.openstack.common import log as logging LOG = logging.getLogger(__name__) -class Parted(object): - def __init__(self, name, label): +class Parted(base.Serializable): + + def __init__(self, name, label, partitions=None, install_bootloader=False): self.name = name self.label = label - self.partitions = [] - self.install_bootloader = False + self.partitions = partitions or [] + self.install_bootloader = install_bootloader def add_partition(self, **kwargs): # TODO(kozhukalov): validate before appending @@ -99,14 +102,31 @@ class Parted(object): separator = 'p' return '%s%s%s' % (self.name, separator, self.next_count()) + def to_dict(self): + partitions = [partition.to_dict() for partition in self.partitions] + return { + 'name': self.name, + 'label': self.label, + 'partitions': partitions, + 'install_bootloader': self.install_bootloader, + } + + @classmethod + def from_dict(cls, data): + data = copy.deepcopy(data) + raw_partitions = data.pop('partitions') + partitions = [Partition.from_dict(partition) + for partition in raw_partitions] + return cls(partitions=partitions, **data) + + +class Partition(base.Serializable): -class Partition(object): def __init__(self, name, count, device, begin, end, partition_type, flags=None, guid=None, configdrive=False): self.name = name self.count = count self.device = device - self.name = name self.begin = begin self.end = end self.type = partition_type @@ -121,15 +141,48 @@ class Partition(object): def set_guid(self, guid): self.guid = guid + def to_dict(self): + return { + 'name': self.name, + 'count': self.count, + 'device': self.device, + 'begin': self.begin, + 'end': self.end, + 'partition_type': self.type, + 'flags': self.flags, + 'guid': self.guid, + 'configdrive': self.configdrive, + } + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +class PhysicalVolume(base.Serializable): -class Pv(object): def __init__(self, name, metadatasize=16, metadatacopies=2): self.name = name self.metadatasize = metadatasize self.metadatacopies = metadatacopies + def to_dict(self): + return { + 'name': self.name, + 'metadatasize': self.metadatasize, + 'metadatacopies': self.metadatacopies, + } + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +PV = PhysicalVolume + + +class VolumeGroup(base.Serializable): -class Vg(object): def __init__(self, name, pvnames=None): self.name = name self.pvnames = pvnames or [] @@ -138,8 +191,22 @@ class Vg(object): if pvname not in self.pvnames: self.pvnames.append(pvname) + def to_dict(self): + return { + 'name': self.name, + 'pvnames': self.pvnames + } + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +VG = VolumeGroup + + +class LogicalVolume(base.Serializable): -class Lv(object): def __init__(self, name, vgname, size): self.name = name self.vgname = vgname @@ -150,8 +217,23 @@ class Lv(object): return '/dev/mapper/%s-%s' % (self.vgname.replace('-', '--'), self.name.replace('-', '--')) + def to_dict(self): + return { + 'name': self.name, + 'vgname': self.vgname, + 'size': self.size, + } + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +LV = LogicalVolume + + +class MultipleDevice(base.Serializable): -class Md(object): def __init__(self, name, level, devices=None, spares=None): self.name = name @@ -173,8 +255,24 @@ class Md(object): 'device %s is already attached' % device) self.spares.append(device) + def to_dict(self): + return { + 'name': self.name, + 'level': self.level, + 'devices': self.devices, + 'spares': self.spares, + } + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +MD = MultipleDevice + + +class FileSystem(base.Serializable): -class Fs(object): def __init__(self, device, mount=None, fs_type=None, fs_options=None, fs_label=None): self.device = device @@ -183,6 +281,22 @@ class Fs(object): self.options = fs_options or '' self.label = fs_label or '' + def to_dict(self): + return { + 'device': self.device, + 'mount': self.mount, + 'fs_type': self.type, + 'fs_options': self.options, + 'fs_label': self.label, + } + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +FS = FileSystem + class PartitionScheme(object): def __init__(self): @@ -199,22 +313,22 @@ class PartitionScheme(object): return parted def add_pv(self, **kwargs): - pv = Pv(**kwargs) + pv = PV(**kwargs) self.pvs.append(pv) return pv def add_vg(self, **kwargs): - vg = Vg(**kwargs) + vg = VG(**kwargs) self.vgs.append(vg) return vg def add_lv(self, **kwargs): - lv = Lv(**kwargs) + lv = LV(**kwargs) self.lvs.append(lv) return lv def add_fs(self, **kwargs): - fs = Fs(**kwargs) + fs = FS(**kwargs) self.fss.append(fs) return fs @@ -222,7 +336,7 @@ class PartitionScheme(object): mdkwargs = {} mdkwargs['name'] = kwargs.get('name') or self.md_next_name() mdkwargs['level'] = kwargs.get('level') or 'mirror' - md = Md(**mdkwargs) + md = MD(**mdkwargs) self.mds.append(md) return md @@ -357,3 +471,13 @@ class PartitionScheme(object): for prt in parted.partitions: if prt.configdrive: return prt.name + + def to_dict(self): + return { + 'parteds': [parted.to_dict() for parted in self.parteds], + 'mds': [md.to_dict() for md in self.mds], + 'pvs': [pv.to_dict() for pv in self.pvs], + 'vgs': [vg.to_dict() for vg in self.vgs], + 'lvs': [lv.to_dict() for lv in self.lvs], + 'fss': [fs.to_dict() for fs in self.fss], + } diff --git a/fuel_agent/tests/base.py b/fuel_agent/tests/base.py new file mode 100644 index 0000000..c6bd273 --- /dev/null +++ b/fuel_agent/tests/base.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 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 json +import os + + +FIXTURE_PATH = os.path.join(os.path.dirname(__file__), 'fixtures') + + +def load_fixture(filename): + path = os.path.join(FIXTURE_PATH, filename) + with open(path) as f: + return json.load(f) diff --git a/fuel_agent/tests/fixtures/simple_nailgun_driver.json b/fuel_agent/tests/fixtures/simple_nailgun_driver.json new file mode 100644 index 0000000..c3aeb63 --- /dev/null +++ b/fuel_agent/tests/fixtures/simple_nailgun_driver.json @@ -0,0 +1,326 @@ +{ + "hostname": "node-1.domain.tld", + "interfaces": { + "eth0": { + "dns_name": "node-1.domain.tld", + "ip_address": "10.20.0.3", + "mac_address": "08:00:27:79:da:80", + "netmask": "255.255.255.0", + "static": "0" + }, + "eth1": { + "mac_address": "08:00:27:46:43:60", + "static": "0" + }, + "eth2": { + "mac_address": "08:00:27:b1:d7:15", + "static": "0" + } + }, + "interfaces_extra": { + "eth0": { + "onboot": "yes", + "peerdns": "no" + }, + "eth1": { + "onboot": "no", + "peerdns": "no" + }, + "eth2": { + "onboot": "no", + "peerdns": "no" + } + }, + "kernel_options": { + "netcfg/choose_interface": "08:00:27:79:da:80", + "udevrules": "08:00:27:79:da:80_eth0,08:00:27:46:43:60_eth1,08:00:27:b1:d7:15_eth2" + }, + "ks_meta": { + "auth_key": "fake_auth_key", + "authorized_keys": [ + "fake_authorized_key1", + "fake_authorized_key2" + ], + "fuel_version": "5.0.1", + "gw": "10.20.0.1", + "image_data": { + "/": { + "container": "gzip", + "format": "ext4", + "uri": "http://fake.host.org:123/imgs/fake_image.img.gz" + } + }, + "install_log_2_syslog": 1, + "master_ip": "10.20.0.2", + "mco_auto_setup": 1, + "mco_connector": "rabbitmq", + "mco_enable": 1, + "mco_host": "10.20.0.2", + "mco_password": "marionette", + "mco_pskey": "unset", + "mco_user": "mcollective", + "mco_vhost": "mcollective", + "pm_data": { + "kernel_params": "console=ttyS0,9600 console=tty0 rootdelay=90 nomodeset", + "ks_spaces": [] + }, + "puppet_auto_setup": 1, + "puppet_enable": 0, + "puppet_master": "fuel.domain.tld", + "repo_setup": { + "repos": [ + { + "name": "repo1", + "priority": 1001, + "section": "section", + "suite": "suite", + "type": "deb", + "uri": "uri1" + }, + { + "name": "repo2", + "priority": 1001, + "section": "section", + "suite": "suite", + "type": "deb", + "uri": "uri2" + } + ] + }, + "timezone": "America/Los_Angeles" + }, + "name": "node-1", + "name_servers": "\"10.20.0.2\"", + "name_servers_search": "\"domain.tld\"", + "netboot_enabled": "1", + "power_address": "10.20.0.253", + "power_pass": "/root/.ssh/bootstrap.rsa", + "power_type": "ssh", + "power_user": "root", + "profile": "pro_fi-le", + "slave_name": "node-1", + "uid": "1", + "partitioning": { + "fss": [ + { + "device": "/dev/sda3", + "fs_label": "", + "fs_options": "", + "fs_type": "ext2", + "mount": "/boot" + }, + { + "device": "/dev/sda4", + "fs_label": "", + "fs_options": "", + "fs_type": "ext2", + "mount": "/tmp" + }, + { + "device": "/dev/mapper/os-root", + "fs_label": "", + "fs_options": "", + "fs_type": "ext4", + "mount": "/" + }, + { + "device": "/dev/mapper/os-swap", + "fs_label": "", + "fs_options": "", + "fs_type": "swap", + "mount": "swap" + }, + { + "device": "/dev/mapper/image-glance", + "fs_label": "", + "fs_options": "", + "fs_type": "xfs", + "mount": "/var/lib/glance" + } + ], + "lvs": [ + { + "name": "root", + "size": 15360, + "vgname": "os" + }, + { + "name": "swap", + "size": 4014, + "vgname": "os" + }, + { + "name": "glance", + "size": 175347, + "vgname": "image" + } + ], + "mds": [], + "parteds": [ + { + "label": "gpt", + "name": "/dev/sdb", + "partitions": [ + { + "begin": 1, + "configdrive": false, + "count": 1, + "device": "/dev/sdb", + "end": 25, + "flags": [ + "bios_grub" + ], + "guid": null, + "name": "/dev/sdb1", + "partition_type": "primary" + }, + { + "begin": 25, + "configdrive": false, + "count": 2, + "device": "/dev/sdb", + "end": 225, + "flags": [], + "guid": null, + "name": "/dev/sdb2", + "partition_type": "primary" + }, + { + "begin": 225, + "configdrive": false, + "count": 3, + "device": "/dev/sdb", + "end": 65196, + "flags": [], + "guid": null, + "name": "/dev/sdb3", + "partition_type": "primary" + } + ] + }, + { + "label": "gpt", + "name": "/dev/sda", + "partitions": [ + { + "begin": 1, + "configdrive": false, + "count": 1, + "device": "/dev/sda", + "end": 25, + "flags": [ + "bios_grub" + ], + "guid": null, + "name": "/dev/sda1", + "partition_type": "primary" + }, + { + "begin": 25, + "configdrive": false, + "count": 2, + "device": "/dev/sda", + "end": 225, + "flags": [], + "guid": null, + "name": "/dev/sda2", + "partition_type": "primary" + }, + { + "begin": 225, + "configdrive": false, + "count": 3, + "device": "/dev/sda", + "end": 425, + "flags": [], + "guid": null, + "name": "/dev/sda3", + "partition_type": "primary" + }, + { + "begin": 425, + "configdrive": false, + "count": 4, + "device": "/dev/sda", + "end": 625, + "flags": [], + "guid": "fake_guid", + "name": "/dev/sda4", + "partition_type": "primary" + }, + { + "begin": 625, + "configdrive": false, + "count": 5, + "device": "/dev/sda", + "end": 20063, + "flags": [], + "guid": null, + "name": "/dev/sda5", + "partition_type": "primary" + }, + { + "begin": 20063, + "configdrive": false, + "count": 6, + "device": "/dev/sda", + "end": 65660, + "flags": [], + "guid": null, + "name": "/dev/sda6", + "partition_type": "primary" + }, + { + "begin": 65660, + "configdrive": true, + "count": 7, + "device": "/dev/sda", + "end": 65680, + "flags": [], + "guid": null, + "name": "/dev/sda7", + "partition_type": "primary" + } + ] + } + ], + "pvs": [ + { + "metadatacopies": 2, + "metadatasize": 28, + "name": "/dev/sda5" + }, + { + "metadatacopies": 2, + "metadatasize": 28, + "name": "/dev/sda6" + }, + { + "metadatacopies": 2, + "metadatasize": 28, + "name": "/dev/sdb3" + }, + { + "metadatacopies": 2, + "metadatasize": 28, + "name": "/dev/sdc3" + } + ], + "vgs": [ + { + "name": "image", + "pvnames": [ + "/dev/sda6", + "/dev/sdb3", + "/dev/sdc3" + ] + }, + { + "name": "os", + "pvnames": [ + "/dev/sda5" + ] + } + ] + } +} diff --git a/fuel_agent/tests/test_manager.py b/fuel_agent/tests/test_manager.py index 62d26aa..16d3d0b 100644 --- a/fuel_agent/tests/test_manager.py +++ b/fuel_agent/tests/test_manager.py @@ -19,6 +19,7 @@ import signal from oslo.config import cfg from oslotest import base as test_base +from fuel_agent.drivers import nailgun from fuel_agent import errors from fuel_agent import manager from fuel_agent import objects @@ -438,7 +439,7 @@ class TestManager(test_base.BaseTestCase): mock_fu): mock_os.path.islink.return_value = True mock_utils.execute.return_value = (None, None) - self.mgr.driver.partition_scheme = objects.PartitionScheme() + self.mgr.driver._partition_scheme = objects.PartitionScheme() self.mgr.mount_target('fake_chroot') mock_open.assert_called_once_with('fake_chroot/etc/mtab', 'wb') mock_os.path.islink.assert_called_once_with('fake_chroot/etc/mtab') @@ -451,7 +452,7 @@ class TestManager(test_base.BaseTestCase): @mock.patch('fuel_agent.manager.os', create=True) def test_mount_target(self, mock_os, mock_open, mock_utils, mock_fu): mock_os.path.islink.return_value = False - self.mgr.driver.partition_scheme = objects.PartitionScheme() + self.mgr.driver._partition_scheme = objects.PartitionScheme() self.mgr.driver.partition_scheme.add_fs( device='fake', mount='/var/lib', fs_type='xfs') self.mgr.driver.partition_scheme.add_fs( @@ -500,7 +501,7 @@ none /run/shm tmpfs rw,nosuid,nodev 0 0""" @mock.patch('fuel_agent.manager.fu', create=True) def test_umount_target(self, mock_fu): - self.mgr.driver.partition_scheme = objects.PartitionScheme() + self.mgr.driver._partition_scheme = objects.PartitionScheme() self.mgr.driver.partition_scheme.add_fs( device='fake', mount='/var/lib', fs_type='xfs') self.mgr.driver.partition_scheme.add_fs( @@ -522,6 +523,38 @@ none /run/shm tmpfs rw,nosuid,nodev 0 0""" mock.call('fake_chroot/', try_lazy_umount=True)], mock_fu.umount_fs.call_args_list) + +class TestImageBuild(test_base.BaseTestCase): + + @mock.patch('yaml.load') + @mock.patch.object(utils, 'init_http_request') + @mock.patch.object(utils, 'get_driver') + def setUp(self, mock_driver, mock_http, mock_yaml): + super(self.__class__, self).setUp() + mock_driver.return_value = nailgun.NailgunBuildImage + image_conf = { + "image_data": { + "/": { + "container": "gzip", + "format": "ext4", + "uri": "http:///centos_65_x86_64.img.gz", + }, + }, + "output": "/var/www/nailgun/targetimages", + "repos": [ + { + "name": "repo", + "uri": "http://some", + 'type': 'deb', + 'suite': '/', + 'section': '', + 'priority': 1001 + } + ], + "codename": "trusty" + } + self.mgr = manager.Manager(image_conf) + @mock.patch('fuel_agent.manager.bu', create=True) @mock.patch('fuel_agent.manager.fu', create=True) @mock.patch('fuel_agent.manager.utils', create=True) @@ -540,17 +573,17 @@ none /run/shm tmpfs rw,nosuid,nodev 0 0""" loops = [objects.Loop(), objects.Loop()] - self.mgr.driver.image_scheme = objects.ImageScheme([ + self.mgr.driver._image_scheme = objects.ImageScheme([ objects.Image('file:///fake/img.img.gz', loops[0], 'ext4', 'gzip'), objects.Image('file:///fake/img-boot.img.gz', loops[1], 'ext2', 'gzip')]) - self.mgr.driver.partition_scheme = objects.PartitionScheme() + self.mgr.driver._partition_scheme = objects.PartitionScheme() self.mgr.driver.partition_scheme.add_fs( device=loops[0], mount='/', fs_type='ext4') self.mgr.driver.partition_scheme.add_fs( device=loops[1], mount='/boot', fs_type='ext2') self.mgr.driver.metadata_uri = 'file:///fake/img.yaml' - self.mgr.driver.operating_system = objects.Ubuntu( + self.mgr.driver._operating_system = objects.Ubuntu( repos=[ objects.DEBRepo('ubuntu', 'http://fakeubuntu', 'trusty', 'fakesection', priority=900), diff --git a/fuel_agent/tests/test_nailgun_build_image.py b/fuel_agent/tests/test_nailgun_build_image.py index 635cbd4..1dfbb09 100644 --- a/fuel_agent/tests/test_nailgun_build_image.py +++ b/fuel_agent/tests/test_nailgun_build_image.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock import os + +import mock import six from six.moves.urllib.parse import urlsplit - -from oslotest import base as test_base +import unittest2 from fuel_agent.drivers.nailgun import NailgunBuildImage from fuel_agent import errors @@ -100,51 +100,45 @@ IMAGE_DATA_SAMPLE = { } -class TestNailgunBuildImage(test_base.BaseTestCase): +class TestNailgunBuildImage(unittest2.TestCase): def test_default_trusty_packages(self): self.assertEqual(NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES, DEFAULT_TRUSTY_PACKAGES) - @mock.patch.object(NailgunBuildImage, '__init__') - def test_parse_operating_system_error_bad_codename(self, mock_init): - mock_init.return_value = None - driver = NailgunBuildImage() - driver.data = {'codename': 'not-trusty'} - self.assertRaises(errors.WrongInputDataError, - driver.parse_operating_system) + @mock.patch.object(NailgunBuildImage, 'parse_schemes') + def test_parse_operating_system_error_bad_codename(self, + mock_parse_schemes): + with self.assertRaises(errors.WrongInputDataError): + data = {'codename': 'not-trusty'} + NailgunBuildImage(data) @mock.patch('fuel_agent.objects.Ubuntu') - @mock.patch.object(NailgunBuildImage, '__init__') - def test_parse_operating_system_packages_given(self, mock_init, mock_ub): - mock_init.return_value = None + @mock.patch.object(NailgunBuildImage, 'parse_schemes') + def test_parse_operating_system_packages_given(self, mock_parse_schemes, + mock_ub): data = { 'repos': [], 'codename': 'trusty', 'packages': ['pack'] } - driver = NailgunBuildImage() - driver.data = data mock_ub_instance = mock_ub.return_value mock_ub_instance.packages = data['packages'] - driver.parse_operating_system() + driver = NailgunBuildImage(data) mock_ub.assert_called_once_with(repos=[], packages=data['packages']) self.assertEqual(driver.operating_system.packages, data['packages']) @mock.patch('fuel_agent.objects.Ubuntu') - @mock.patch.object(NailgunBuildImage, '__init__') + @mock.patch.object(NailgunBuildImage, 'parse_schemes') def test_parse_operating_system_packages_not_given( - self, mock_init, mock_ub): - mock_init.return_value = None + self, mock_parse_schemes, mock_ub): data = { 'repos': [], 'codename': 'trusty' } - driver = NailgunBuildImage() - driver.data = data mock_ub_instance = mock_ub.return_value mock_ub_instance.packages = NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES - driver.parse_operating_system() + driver = NailgunBuildImage(data) mock_ub.assert_called_once_with( repos=[], packages=NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES) self.assertEqual(driver.operating_system.packages, @@ -152,15 +146,13 @@ class TestNailgunBuildImage(test_base.BaseTestCase): @mock.patch('fuel_agent.objects.DEBRepo') @mock.patch('fuel_agent.objects.Ubuntu') - @mock.patch.object(NailgunBuildImage, '__init__') - def test_parse_operating_system_repos(self, mock_init, mock_ub, mock_deb): - mock_init.return_value = None + @mock.patch.object(NailgunBuildImage, 'parse_schemes') + def test_parse_operating_system_repos(self, mock_parse_schemes, mock_ub, + mock_deb): data = { 'repos': REPOS_SAMPLE, 'codename': 'trusty' } - driver = NailgunBuildImage() - driver.data = data mock_deb_expected_calls = [] repos = [] @@ -174,7 +166,7 @@ class TestNailgunBuildImage(test_base.BaseTestCase): } mock_deb_expected_calls.append(mock.call(**kwargs)) repos.append(objects.DEBRepo(**kwargs)) - driver.parse_operating_system() + driver = NailgunBuildImage(data) mock_ub_instance = mock_ub.return_value mock_ub_instance.repos = repos mock_ub.assert_called_once_with( @@ -185,21 +177,18 @@ class TestNailgunBuildImage(test_base.BaseTestCase): @mock.patch('fuel_agent.drivers.nailgun.objects.Loop') @mock.patch('fuel_agent.objects.Image') - @mock.patch('fuel_agent.objects.Fs') + @mock.patch('fuel_agent.objects.FS') @mock.patch('fuel_agent.objects.PartitionScheme') @mock.patch('fuel_agent.objects.ImageScheme') - @mock.patch.object(NailgunBuildImage, '__init__') + @mock.patch.object(NailgunBuildImage, 'parse_operating_system') def test_parse_schemes( - self, mock_init, mock_imgsch, mock_partsch, + self, mock_parse_os, mock_imgsch, mock_partsch, mock_fs, mock_img, mock_loop): - mock_init.return_value = None data = { 'image_data': IMAGE_DATA_SAMPLE, 'output': '/some/local/path', } - driver = NailgunBuildImage() - driver.data = data - driver.parse_schemes() + driver = NailgunBuildImage(data) mock_fs_expected_calls = [] mock_img_expected_calls = [] @@ -223,7 +212,7 @@ class TestNailgunBuildImage(test_base.BaseTestCase): 'fs_type': image['format'] } mock_fs_expected_calls.append(mock.call(**fs_kwargs)) - fss.append(objects.Fs(**fs_kwargs)) + fss.append(objects.FS(**fs_kwargs)) if mount == '/': metadata_filename = filename.split('.', 1)[0] + '.yaml' diff --git a/fuel_agent/tests/test_partition.py b/fuel_agent/tests/test_partition.py index 46a8403..72d9d3a 100644 --- a/fuel_agent/tests/test_partition.py +++ b/fuel_agent/tests/test_partition.py @@ -14,16 +14,17 @@ import mock -from oslotest import base as test_base +import unittest2 from fuel_agent import errors from fuel_agent.objects import partition -class TestMD(test_base.BaseTestCase): +class TestMultipleDevice(unittest2.TestCase): + def setUp(self): - super(TestMD, self).setUp() - self.md = partition.Md('name', 'level') + super(self.__class__, self).setUp() + self.md = partition.MD(name='name', level='level') def test_add_device_ok(self): self.assertEqual(0, len(self.md.devices)) @@ -59,8 +60,22 @@ class TestMD(test_base.BaseTestCase): self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_spare, 'device') + def test_conversion(self): + self.md.add_device('device_a') + self.md.add_spare('device_b') + serialized = self.md.to_dict() + assert serialized == { + 'name': 'name', + 'level': 'level', + 'devices': ['device_a', ], + 'spares': ['device_b', ], + } + new_md = partition.MD.from_dict(serialized) + assert serialized == new_md.to_dict() + + +class TestPartition(unittest2.TestCase): -class TestPartition(test_base.BaseTestCase): def setUp(self): super(TestPartition, self).setUp() self.pt = partition.Partition('name', 'count', 'device', 'begin', @@ -72,8 +87,27 @@ class TestPartition(test_base.BaseTestCase): self.assertEqual(1, len(self.pt.flags)) self.assertIn('fake_flag', self.pt.flags) + def test_conversion(self): + self.pt.flags.append('some_flag') + self.pt.guid = 'some_guid' + serialized = self.pt.to_dict() + assert serialized == { + 'begin': 'begin', + 'configdrive': False, + 'count': 'count', + 'device': 'device', + 'end': 'end', + 'flags': ['some_flag', ], + 'guid': 'some_guid', + 'name': 'name', + 'partition_type': 'partition_type', + } + new_pt = partition.Partition.from_dict(serialized) + assert serialized == new_pt.to_dict() + + +class TestPartitionScheme(unittest2.TestCase): -class TestPartitionScheme(test_base.BaseTestCase): def setUp(self): super(TestPartitionScheme, self).setUp() self.p_scheme = partition.PartitionScheme() @@ -83,30 +117,30 @@ class TestPartitionScheme(test_base.BaseTestCase): self.p_scheme.root_device) def test_fs_by_device(self): - expected_fs = partition.Fs('device') + expected_fs = partition.FS('device') self.p_scheme.fss.append(expected_fs) - self.p_scheme.fss.append(partition.Fs('wrong_device')) + 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') + 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')) + 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') + expected_pv = partition.PV('pv') self.p_scheme.pvs.append(expected_pv) - self.p_scheme.pvs.append(partition.Pv('wrong_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') + expected_vg = partition.VG('vg') self.p_scheme.vgs.append(expected_vg) - self.p_scheme.vgs.append(partition.Vg('wrong_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) @@ -123,33 +157,33 @@ class TestPartitionScheme(test_base.BaseTestCase): def test_md_next_name_fail(self): self.p_scheme.mds = [ - partition.Md('/dev/md%s' % x, 'level') for x in range(0, 128)] + 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') + expected_md = partition.MD('name', 'level') self.p_scheme.mds.append(expected_md) - self.p_scheme.mds.append(partition.Md('wrong_name', 'level')) + 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') + 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', + 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') + 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') @@ -171,7 +205,7 @@ class TestPartitionScheme(test_base.BaseTestCase): self.assertEqual('-F', self.p_scheme.fss[0].options) -class TestParted(test_base.BaseTestCase): +class TestParted(unittest2.TestCase): def setUp(self): super(TestParted, self).setUp() self.prtd = partition.Parted('name', 'label') @@ -254,3 +288,99 @@ class TestParted(test_base.BaseTestCase): 'begin', 'end', 'primary')] self.prtd.partitions.extend(expected_partitions) self.assertEqual(expected_partitions, self.prtd.primary) + + def test_conversion(self): + prt = partition.Partition( + name='name', + count='count', + device='device', + begin='begin', + end='end', + partition_type='primary' + ) + self.prtd.partitions.append(prt) + serialized = self.prtd.to_dict() + assert serialized == { + 'label': 'label', + 'name': 'name', + 'partitions': [ + prt.to_dict(), + ], + 'install_bootloader': False, + } + new_prtd = partition.Parted.from_dict(serialized) + assert serialized == new_prtd.to_dict() + + +class TestLogicalVolume(unittest2.TestCase): + + def test_conversion(self): + lv = partition.LV( + name='lv-name', + vgname='vg-name', + size=1234 + ) + serialized = lv.to_dict() + assert serialized == { + 'name': 'lv-name', + 'vgname': 'vg-name', + 'size': 1234, + } + new_lv = partition.LV.from_dict(serialized) + assert serialized == new_lv.to_dict() + + +class TestPhisicalVolume(unittest2.TestCase): + + def test_conversion(self): + pv = partition.PV( + name='pv-name', + metadatasize=987, + metadatacopies=112, + ) + serialized = pv.to_dict() + assert serialized == { + 'name': 'pv-name', + 'metadatasize': 987, + 'metadatacopies': 112, + } + new_pv = partition.PV.from_dict(serialized) + assert serialized == new_pv.to_dict() + + +class TestVolumesGroup(unittest2.TestCase): + + def test_conversion(self): + vg = partition.VG( + name='vg-name', + pvnames=['pv-name-a', ] + ) + serialized = vg.to_dict() + assert serialized == { + 'name': 'vg-name', + 'pvnames': ['pv-name-a', ] + } + new_vg = partition.VG.from_dict(serialized) + assert serialized == new_vg.to_dict() + + +class TestFileSystem(unittest2.TestCase): + + def test_conversion(self): + fs = partition.FS( + device='some-device', + mount='/mount', + fs_type='type', + fs_options='some-option', + fs_label='some-label', + ) + serialized = fs.to_dict() + assert serialized == { + 'device': 'some-device', + 'mount': '/mount', + 'fs_type': 'type', + 'fs_options': 'some-option', + 'fs_label': 'some-label', + } + new_fs = partition.FS.from_dict(serialized) + assert serialized == new_fs.to_dict() diff --git a/fuel_agent/tests/test_simple_nailgun_driver.py b/fuel_agent/tests/test_simple_nailgun_driver.py new file mode 100644 index 0000000..94e1838 --- /dev/null +++ b/fuel_agent/tests/test_simple_nailgun_driver.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 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 +import requests_mock +import unittest2 + +from fuel_agent.drivers import simple +from fuel_agent import objects +from fuel_agent.tests import base + + +@mock.patch.multiple( + simple.NailgunSimpleDriver, + parse_grub=lambda x: objects.Grub(), + parse_configdrive_scheme=lambda x: objects.ConfigDriveScheme(), + parse_image_scheme=lambda x: objects.ImageScheme()) +class TestObjectDeserialization(unittest2.TestCase): + + def test_driver_always_has_correct_objects(self): + driver = simple.NailgunSimpleDriver({}) + assert isinstance(driver.partition_scheme, objects.PartitionScheme) + + def test_lv_data_is_loaded(self): + lv_data = { + 'partitioning': { + 'lvs': [ + { + 'name': 'lv-name', + 'size': 12345, + 'vgname': 'vg-name', + }, + ] + } + } + + driver = simple.NailgunSimpleDriver(lv_data) + lv = driver.partition_scheme.lvs[0] + assert len(driver.partition_scheme.lvs) == 1 + assert isinstance(lv, objects.LV) + assert lv.name == 'lv-name' + assert lv.size == 12345 + assert lv.vgname == 'vg-name' + + def test_pv_data_is_loaded(self): + pv_data = { + 'partitioning': { + 'pvs': [ + { + 'metadatacopies': 2, + 'metadatasize': 28, + 'name': '/dev/sda5' + }, + ] + } + } + + driver = simple.NailgunSimpleDriver(pv_data) + pv = driver.partition_scheme.pvs[0] + assert len(driver.partition_scheme.pvs) == 1 + assert isinstance(pv, objects.PV) + assert pv.name == '/dev/sda5' + assert pv.metadatacopies == 2 + assert pv.metadatasize == 28 + + def test_vg_data_is_loaded(self): + vg_data = { + 'partitioning': { + 'vgs': [ + { + 'name': 'image', + 'pvnames': [ + '/dev/sda6', + '/dev/sdb3', + '/dev/sdc3', + ] + }, + ] + } + } + + driver = simple.NailgunSimpleDriver(vg_data) + vg = driver.partition_scheme.vgs[0] + assert len(driver.partition_scheme.vgs) == 1 + assert isinstance(vg, objects.VG) + assert vg.name == 'image' + self.assertItemsEqual( + vg.pvnames, + ( + '/dev/sda6', + '/dev/sdb3', + '/dev/sdc3', + ) + ) + + def test_fs_data_is_loaded(self): + fs_data = { + 'partitioning': { + 'fss': [ + { + 'device': '/dev/sda3', + 'fs_label': 'some-label', + 'fs_options': 'some-options', + 'fs_type': 'ext2', + 'mount': '/boot' + }, + ] + } + } + + driver = simple.NailgunSimpleDriver(fs_data) + fs = driver.partition_scheme.fss[0] + assert len(driver.partition_scheme.fss) == 1 + assert isinstance(fs, objects.FS) + assert fs.device == '/dev/sda3' + assert fs.label == 'some-label' + assert fs.options == 'some-options' + assert fs.type == 'ext2' + assert fs.mount == '/boot' + + def test_parted_data_is_loaded(self): + parted_data = { + 'partitioning': { + 'parteds': [ + { + 'label': 'gpt', + 'name': '/dev/sdb', + 'partitions': [ + { + 'begin': 1, + 'configdrive': False, + 'count': 1, + 'device': '/dev/sdb', + 'end': 25, + 'flags': [ + 'bios_grub', + 'xyz', + ], + 'guid': None, + 'name': '/dev/sdb1', + 'partition_type': 'primary' + }, + ] + }, + ] + } + } + + driver = simple.NailgunSimpleDriver(parted_data) + parted = driver.partition_scheme.parteds[0] + partition = parted.partitions[0] + assert len(driver.partition_scheme.parteds) == 1 + assert isinstance(parted, objects.Parted) + assert parted.label == 'gpt' + assert parted.name == '/dev/sdb' + assert len(parted.partitions) == 1 + assert partition.begin == 1 + assert partition.configdrive is False + assert partition.count == 1 + assert partition.device == '/dev/sdb' + assert partition.end == 25 + self.assertItemsEqual(partition.flags, ['bios_grub', 'xyz']) + assert partition.guid is None + assert partition.name == '/dev/sdb1' + assert partition.type == 'primary' + + def test_md_data_is_loaded(self): + md_data = { + 'partitioning': { + 'mds': [ + { + 'name': 'some-raid', + 'level': 1, + 'devices': [ + '/dev/sda', + '/dev/sdc', + ], + 'spares': [ + '/dev/sdb', + '/dev/sdd', + ] + }, + ] + } + } + + driver = simple.NailgunSimpleDriver(md_data) + md = driver.partition_scheme.mds[0] + assert len(driver.partition_scheme.mds) == 1 + assert isinstance(md, objects.MD) + assert md.name == 'some-raid' + assert md.level == 1 + self.assertItemsEqual(md.devices, ['/dev/sda', '/dev/sdc']) + self.assertItemsEqual(md.spares, ['/dev/sdb', '/dev/sdd']) + + +@requests_mock.mock() +class TestFullDataRead(unittest2.TestCase): + + PROVISION_DATA = base.load_fixture('simple_nailgun_driver.json') + + def test_read_with_no_error(self, mock_requests): + mock_requests.get('http://fake.host.org:123/imgs/fake_image.img.gz', + text='{}') + driver = simple.NailgunSimpleDriver(self.PROVISION_DATA) + scheme = driver.partition_scheme + assert len(scheme.fss) == 5 + assert len(scheme.lvs) == 3 + assert len(scheme.mds) == 0 + assert len(scheme.parteds) == 2 + assert len(scheme.pvs) == 4 + assert len(scheme.vgs) == 2 diff --git a/fuel_agent/utils/decorators.py b/fuel_agent/utils/decorators.py new file mode 100644 index 0000000..591c539 --- /dev/null +++ b/fuel_agent/utils/decorators.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 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. + + +class abstractclassmethod(classmethod): + """A decorator indicating abstract classmethods. + + Similar to abstractmethod. + + Usage: + + class C(object): + __metaclass__ = abc.ABCMeta + + @abstractclassmethod + def my_abstract_classmethod(cls, ...): + ... + + Copied from Python 3.2 + """ + + __isabstractmethod__ = True + + def __init__(self, callable): + callable.__isabstractmethod__ = True + super(abstractclassmethod, self).__init__(callable) diff --git a/setup.cfg b/setup.cfg index e05bba6..f2c4338 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ console_scripts = fuel_agent.drivers = nailgun = fuel_agent.drivers.nailgun:Nailgun + nailgun_simple = fuel_agent.drivers.simple:NailgunSimpleDriver nailgun_build_image = fuel_agent.drivers.nailgun:NailgunBuildImage [pbr] diff --git a/test-requirements.txt b/test-requirements.txt index dc03284..2659664 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,9 @@ hacking>=0.8.0,<0.9 mock==1.0.1 +# TODO(prmtl): remove oslotest and (probably) testools in favor of unittest2 oslotest==1.0 testtools>=0.9.34 +unittest2==1.0.1 pytest>=2.7.2 pytest-cov>=1.8.1 +requests-mock>=0.6