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
This commit is contained in:
Sebastian Kalinowski 2015-07-14 14:31:52 +02:00
parent ce4bfd0037
commit 9e22866f3e
15 changed files with 1160 additions and 101 deletions

View File

@ -28,3 +28,23 @@ class BaseDataDriver(object):
def __init__(self, data): def __init__(self, data):
self.data = copy.deepcopy(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"""

View File

@ -15,12 +15,12 @@
import itertools import itertools
import math import math
import os import os
import six
import yaml
import six
from six.moves.urllib.parse import urljoin from six.moves.urllib.parse import urljoin
from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlparse
from six.moves.urllib.parse import urlsplit from six.moves.urllib.parse import urlsplit
import yaml
from fuel_agent.drivers.base import BaseDataDriver from fuel_agent.drivers.base import BaseDataDriver
from fuel_agent.drivers import ks_spaces_validator from fuel_agent.drivers import ks_spaces_validator
@ -67,6 +67,8 @@ def match_device(hu_disk, ks_disk):
class Nailgun(BaseDataDriver): class Nailgun(BaseDataDriver):
"""Driver for parsing regular volumes metadata from Nailgun."""
def __init__(self, data): def __init__(self, data):
super(Nailgun, self).__init__(data) super(Nailgun, self).__init__(data)
@ -79,11 +81,31 @@ class Nailgun(BaseDataDriver):
# get rid of md over all disks for /boot partition. # get rid of md over all disks for /boot partition.
self._boot_done = False self._boot_done = False
self.partition_scheme = self.parse_partition_scheme() self._partition_scheme = self.parse_partition_scheme()
self.grub = self.parse_grub() self._grub = self.parse_grub()
self.configdrive_scheme = self.parse_configdrive_scheme() self._configdrive_scheme = self.parse_configdrive_scheme()
# parsing image scheme needs partition scheme has been parsed # 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): def partition_data(self):
return self.data['ks_meta']['pm_data']['ks_spaces'] return self.data['ks_meta']['pm_data']['ks_spaces']
@ -346,7 +368,7 @@ class Nailgun(BaseDataDriver):
for volume in vg['volumes']: for volume in vg['volumes']:
LOG.debug('Processing lv %s' % volume['name']) LOG.debug('Processing lv %s' % volume['name'])
if volume['size'] <= 0: if volume['size'] <= 0:
LOG.debug('Lv size is zero. Skipping.') LOG.debug('LogicalVolume size is zero. Skipping.')
continue continue
if volume['type'] == 'lv': if volume['type'] == 'lv':
@ -543,8 +565,31 @@ class NailgunBuildImage(BaseDataDriver):
def __init__(self, data): def __init__(self, data):
super(NailgunBuildImage, self).__init__(data) super(NailgunBuildImage, self).__init__(data)
self._image_scheme = objects.ImageScheme()
self._partition_scheme = objects.PartitionScheme()
self.parse_schemes() 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): def parse_operating_system(self):
if self.data.get('codename').lower() != 'trusty': if self.data.get('codename').lower() != 'trusty':
@ -563,11 +608,9 @@ class NailgunBuildImage(BaseDataDriver):
section=repo['section'], section=repo['section'],
priority=repo['priority'])) priority=repo['priority']))
self.operating_system = objects.Ubuntu(repos=repos, packages=packages) return objects.Ubuntu(repos=repos, packages=packages)
def parse_schemes(self): def parse_schemes(self):
self.image_scheme = objects.ImageScheme()
self.partition_scheme = objects.PartitionScheme()
for mount, image in six.iteritems(self.data['image_data']): for mount, image in six.iteritems(self.data['image_data']):
filename = os.path.basename(urlsplit(image['uri']).path) filename = os.path.basename(urlsplit(image['uri']).path)
@ -575,13 +618,13 @@ class NailgunBuildImage(BaseDataDriver):
# during initialization. # during initialization.
device = objects.Loop() device = objects.Loop()
self.image_scheme.add_image( self._image_scheme.add_image(
uri='file://' + os.path.join(self.data['output'], filename), uri='file://' + os.path.join(self.data['output'], filename),
format=image['format'], format=image['format'],
container=image['container'], container=image['container'],
target_device=device) target_device=device)
self.partition_scheme.add_fs( self._partition_scheme.add_fs(
device=device, device=device,
mount=mount, mount=mount,
fs_type=image['format']) fs_type=image['format'])

View File

@ -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

View File

@ -22,18 +22,19 @@ from fuel_agent.objects.image import Image
from fuel_agent.objects.image import ImageScheme from fuel_agent.objects.image import ImageScheme
from fuel_agent.objects.operating_system import OperatingSystem from fuel_agent.objects.operating_system import OperatingSystem
from fuel_agent.objects.operating_system import Ubuntu from fuel_agent.objects.operating_system import Ubuntu
from fuel_agent.objects.partition import Fs from fuel_agent.objects.partition import FS
from fuel_agent.objects.partition import Lv from fuel_agent.objects.partition import LV
from fuel_agent.objects.partition import Md 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 Partition
from fuel_agent.objects.partition import PartitionScheme from fuel_agent.objects.partition import PartitionScheme
from fuel_agent.objects.partition import Pv from fuel_agent.objects.partition import PV
from fuel_agent.objects.partition import Vg from fuel_agent.objects.partition import VG
from fuel_agent.objects.repo import DEBRepo from fuel_agent.objects.repo import DEBRepo
from fuel_agent.objects.repo import Repo from fuel_agent.objects.repo import Repo
__all__ = [ __all__ = [
'Partition', 'Pv', 'Vg', 'Lv', 'Md', 'Fs', 'PartitionScheme', 'Partition', 'Parted', 'PV', 'VG', 'LV', 'MD', 'FS', 'PartitionScheme',
'ConfigDriveCommon', 'ConfigDrivePuppet', 'ConfigDriveMcollective', 'ConfigDriveCommon', 'ConfigDrivePuppet', 'ConfigDriveMcollective',
'ConfigDriveScheme', 'Image', 'ImageScheme', 'Grub', 'ConfigDriveScheme', 'Image', 'ImageScheme', 'Grub',
'OperatingSystem', 'Ubuntu', 'OperatingSystem', 'Ubuntu',

View File

@ -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

View File

@ -12,20 +12,23 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import os import os
from fuel_agent import errors from fuel_agent import errors
from fuel_agent.objects import base
from fuel_agent.openstack.common import log as logging from fuel_agent.openstack.common import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class Parted(object): class Parted(base.Serializable):
def __init__(self, name, label):
def __init__(self, name, label, partitions=None, install_bootloader=False):
self.name = name self.name = name
self.label = label self.label = label
self.partitions = [] self.partitions = partitions or []
self.install_bootloader = False self.install_bootloader = install_bootloader
def add_partition(self, **kwargs): def add_partition(self, **kwargs):
# TODO(kozhukalov): validate before appending # TODO(kozhukalov): validate before appending
@ -99,14 +102,31 @@ class Parted(object):
separator = 'p' separator = 'p'
return '%s%s%s' % (self.name, separator, self.next_count()) 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, def __init__(self, name, count, device, begin, end, partition_type,
flags=None, guid=None, configdrive=False): flags=None, guid=None, configdrive=False):
self.name = name self.name = name
self.count = count self.count = count
self.device = device self.device = device
self.name = name
self.begin = begin self.begin = begin
self.end = end self.end = end
self.type = partition_type self.type = partition_type
@ -121,15 +141,48 @@ class Partition(object):
def set_guid(self, guid): def set_guid(self, guid):
self.guid = 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): def __init__(self, name, metadatasize=16, metadatacopies=2):
self.name = name self.name = name
self.metadatasize = metadatasize self.metadatasize = metadatasize
self.metadatacopies = metadatacopies 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): def __init__(self, name, pvnames=None):
self.name = name self.name = name
self.pvnames = pvnames or [] self.pvnames = pvnames or []
@ -138,8 +191,22 @@ class Vg(object):
if pvname not in self.pvnames: if pvname not in self.pvnames:
self.pvnames.append(pvname) 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): def __init__(self, name, vgname, size):
self.name = name self.name = name
self.vgname = vgname self.vgname = vgname
@ -150,8 +217,23 @@ class Lv(object):
return '/dev/mapper/%s-%s' % (self.vgname.replace('-', '--'), return '/dev/mapper/%s-%s' % (self.vgname.replace('-', '--'),
self.name.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, def __init__(self, name, level,
devices=None, spares=None): devices=None, spares=None):
self.name = name self.name = name
@ -173,8 +255,24 @@ class Md(object):
'device %s is already attached' % device) 'device %s is already attached' % device)
self.spares.append(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, def __init__(self, device, mount=None,
fs_type=None, fs_options=None, fs_label=None): fs_type=None, fs_options=None, fs_label=None):
self.device = device self.device = device
@ -183,6 +281,22 @@ class Fs(object):
self.options = fs_options or '' self.options = fs_options or ''
self.label = fs_label 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): class PartitionScheme(object):
def __init__(self): def __init__(self):
@ -199,22 +313,22 @@ class PartitionScheme(object):
return parted return parted
def add_pv(self, **kwargs): def add_pv(self, **kwargs):
pv = Pv(**kwargs) pv = PV(**kwargs)
self.pvs.append(pv) self.pvs.append(pv)
return pv return pv
def add_vg(self, **kwargs): def add_vg(self, **kwargs):
vg = Vg(**kwargs) vg = VG(**kwargs)
self.vgs.append(vg) self.vgs.append(vg)
return vg return vg
def add_lv(self, **kwargs): def add_lv(self, **kwargs):
lv = Lv(**kwargs) lv = LV(**kwargs)
self.lvs.append(lv) self.lvs.append(lv)
return lv return lv
def add_fs(self, **kwargs): def add_fs(self, **kwargs):
fs = Fs(**kwargs) fs = FS(**kwargs)
self.fss.append(fs) self.fss.append(fs)
return fs return fs
@ -222,7 +336,7 @@ class PartitionScheme(object):
mdkwargs = {} mdkwargs = {}
mdkwargs['name'] = kwargs.get('name') or self.md_next_name() mdkwargs['name'] = kwargs.get('name') or self.md_next_name()
mdkwargs['level'] = kwargs.get('level') or 'mirror' mdkwargs['level'] = kwargs.get('level') or 'mirror'
md = Md(**mdkwargs) md = MD(**mdkwargs)
self.mds.append(md) self.mds.append(md)
return md return md
@ -357,3 +471,13 @@ class PartitionScheme(object):
for prt in parted.partitions: for prt in parted.partitions:
if prt.configdrive: if prt.configdrive:
return prt.name 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],
}

27
fuel_agent/tests/base.py Normal file
View File

@ -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)

View File

@ -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"
]
}
]
}
}

View File

@ -19,6 +19,7 @@ import signal
from oslo.config import cfg from oslo.config import cfg
from oslotest import base as test_base from oslotest import base as test_base
from fuel_agent.drivers import nailgun
from fuel_agent import errors from fuel_agent import errors
from fuel_agent import manager from fuel_agent import manager
from fuel_agent import objects from fuel_agent import objects
@ -438,7 +439,7 @@ class TestManager(test_base.BaseTestCase):
mock_fu): mock_fu):
mock_os.path.islink.return_value = True mock_os.path.islink.return_value = True
mock_utils.execute.return_value = (None, None) 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') self.mgr.mount_target('fake_chroot')
mock_open.assert_called_once_with('fake_chroot/etc/mtab', 'wb') mock_open.assert_called_once_with('fake_chroot/etc/mtab', 'wb')
mock_os.path.islink.assert_called_once_with('fake_chroot/etc/mtab') 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) @mock.patch('fuel_agent.manager.os', create=True)
def test_mount_target(self, mock_os, mock_open, mock_utils, mock_fu): def test_mount_target(self, mock_os, mock_open, mock_utils, mock_fu):
mock_os.path.islink.return_value = False 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( self.mgr.driver.partition_scheme.add_fs(
device='fake', mount='/var/lib', fs_type='xfs') device='fake', mount='/var/lib', fs_type='xfs')
self.mgr.driver.partition_scheme.add_fs( 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) @mock.patch('fuel_agent.manager.fu', create=True)
def test_umount_target(self, mock_fu): 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( self.mgr.driver.partition_scheme.add_fs(
device='fake', mount='/var/lib', fs_type='xfs') device='fake', mount='/var/lib', fs_type='xfs')
self.mgr.driver.partition_scheme.add_fs( 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.call('fake_chroot/', try_lazy_umount=True)],
mock_fu.umount_fs.call_args_list) 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.bu', create=True)
@mock.patch('fuel_agent.manager.fu', create=True) @mock.patch('fuel_agent.manager.fu', create=True)
@mock.patch('fuel_agent.manager.utils', 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()] 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.img.gz', loops[0], 'ext4', 'gzip'),
objects.Image('file:///fake/img-boot.img.gz', objects.Image('file:///fake/img-boot.img.gz',
loops[1], 'ext2', 'gzip')]) 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( self.mgr.driver.partition_scheme.add_fs(
device=loops[0], mount='/', fs_type='ext4') device=loops[0], mount='/', fs_type='ext4')
self.mgr.driver.partition_scheme.add_fs( self.mgr.driver.partition_scheme.add_fs(
device=loops[1], mount='/boot', fs_type='ext2') device=loops[1], mount='/boot', fs_type='ext2')
self.mgr.driver.metadata_uri = 'file:///fake/img.yaml' self.mgr.driver.metadata_uri = 'file:///fake/img.yaml'
self.mgr.driver.operating_system = objects.Ubuntu( self.mgr.driver._operating_system = objects.Ubuntu(
repos=[ repos=[
objects.DEBRepo('ubuntu', 'http://fakeubuntu', objects.DEBRepo('ubuntu', 'http://fakeubuntu',
'trusty', 'fakesection', priority=900), 'trusty', 'fakesection', priority=900),

View File

@ -12,12 +12,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import mock
import os import os
import mock
import six import six
from six.moves.urllib.parse import urlsplit from six.moves.urllib.parse import urlsplit
import unittest2
from oslotest import base as test_base
from fuel_agent.drivers.nailgun import NailgunBuildImage from fuel_agent.drivers.nailgun import NailgunBuildImage
from fuel_agent import errors 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): def test_default_trusty_packages(self):
self.assertEqual(NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES, self.assertEqual(NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES,
DEFAULT_TRUSTY_PACKAGES) DEFAULT_TRUSTY_PACKAGES)
@mock.patch.object(NailgunBuildImage, '__init__') @mock.patch.object(NailgunBuildImage, 'parse_schemes')
def test_parse_operating_system_error_bad_codename(self, mock_init): def test_parse_operating_system_error_bad_codename(self,
mock_init.return_value = None mock_parse_schemes):
driver = NailgunBuildImage() with self.assertRaises(errors.WrongInputDataError):
driver.data = {'codename': 'not-trusty'} data = {'codename': 'not-trusty'}
self.assertRaises(errors.WrongInputDataError, NailgunBuildImage(data)
driver.parse_operating_system)
@mock.patch('fuel_agent.objects.Ubuntu') @mock.patch('fuel_agent.objects.Ubuntu')
@mock.patch.object(NailgunBuildImage, '__init__') @mock.patch.object(NailgunBuildImage, 'parse_schemes')
def test_parse_operating_system_packages_given(self, mock_init, mock_ub): def test_parse_operating_system_packages_given(self, mock_parse_schemes,
mock_init.return_value = None mock_ub):
data = { data = {
'repos': [], 'repos': [],
'codename': 'trusty', 'codename': 'trusty',
'packages': ['pack'] 'packages': ['pack']
} }
driver = NailgunBuildImage()
driver.data = data
mock_ub_instance = mock_ub.return_value mock_ub_instance = mock_ub.return_value
mock_ub_instance.packages = data['packages'] mock_ub_instance.packages = data['packages']
driver.parse_operating_system() driver = NailgunBuildImage(data)
mock_ub.assert_called_once_with(repos=[], packages=data['packages']) mock_ub.assert_called_once_with(repos=[], packages=data['packages'])
self.assertEqual(driver.operating_system.packages, data['packages']) self.assertEqual(driver.operating_system.packages, data['packages'])
@mock.patch('fuel_agent.objects.Ubuntu') @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( def test_parse_operating_system_packages_not_given(
self, mock_init, mock_ub): self, mock_parse_schemes, mock_ub):
mock_init.return_value = None
data = { data = {
'repos': [], 'repos': [],
'codename': 'trusty' 'codename': 'trusty'
} }
driver = NailgunBuildImage()
driver.data = data
mock_ub_instance = mock_ub.return_value mock_ub_instance = mock_ub.return_value
mock_ub_instance.packages = NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES mock_ub_instance.packages = NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES
driver.parse_operating_system() driver = NailgunBuildImage(data)
mock_ub.assert_called_once_with( mock_ub.assert_called_once_with(
repos=[], packages=NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES) repos=[], packages=NailgunBuildImage.DEFAULT_TRUSTY_PACKAGES)
self.assertEqual(driver.operating_system.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.DEBRepo')
@mock.patch('fuel_agent.objects.Ubuntu') @mock.patch('fuel_agent.objects.Ubuntu')
@mock.patch.object(NailgunBuildImage, '__init__') @mock.patch.object(NailgunBuildImage, 'parse_schemes')
def test_parse_operating_system_repos(self, mock_init, mock_ub, mock_deb): def test_parse_operating_system_repos(self, mock_parse_schemes, mock_ub,
mock_init.return_value = None mock_deb):
data = { data = {
'repos': REPOS_SAMPLE, 'repos': REPOS_SAMPLE,
'codename': 'trusty' 'codename': 'trusty'
} }
driver = NailgunBuildImage()
driver.data = data
mock_deb_expected_calls = [] mock_deb_expected_calls = []
repos = [] repos = []
@ -174,7 +166,7 @@ class TestNailgunBuildImage(test_base.BaseTestCase):
} }
mock_deb_expected_calls.append(mock.call(**kwargs)) mock_deb_expected_calls.append(mock.call(**kwargs))
repos.append(objects.DEBRepo(**kwargs)) repos.append(objects.DEBRepo(**kwargs))
driver.parse_operating_system() driver = NailgunBuildImage(data)
mock_ub_instance = mock_ub.return_value mock_ub_instance = mock_ub.return_value
mock_ub_instance.repos = repos mock_ub_instance.repos = repos
mock_ub.assert_called_once_with( 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.drivers.nailgun.objects.Loop')
@mock.patch('fuel_agent.objects.Image') @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.PartitionScheme')
@mock.patch('fuel_agent.objects.ImageScheme') @mock.patch('fuel_agent.objects.ImageScheme')
@mock.patch.object(NailgunBuildImage, '__init__') @mock.patch.object(NailgunBuildImage, 'parse_operating_system')
def test_parse_schemes( 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_fs, mock_img, mock_loop):
mock_init.return_value = None
data = { data = {
'image_data': IMAGE_DATA_SAMPLE, 'image_data': IMAGE_DATA_SAMPLE,
'output': '/some/local/path', 'output': '/some/local/path',
} }
driver = NailgunBuildImage() driver = NailgunBuildImage(data)
driver.data = data
driver.parse_schemes()
mock_fs_expected_calls = [] mock_fs_expected_calls = []
mock_img_expected_calls = [] mock_img_expected_calls = []
@ -223,7 +212,7 @@ class TestNailgunBuildImage(test_base.BaseTestCase):
'fs_type': image['format'] 'fs_type': image['format']
} }
mock_fs_expected_calls.append(mock.call(**fs_kwargs)) mock_fs_expected_calls.append(mock.call(**fs_kwargs))
fss.append(objects.Fs(**fs_kwargs)) fss.append(objects.FS(**fs_kwargs))
if mount == '/': if mount == '/':
metadata_filename = filename.split('.', 1)[0] + '.yaml' metadata_filename = filename.split('.', 1)[0] + '.yaml'

View File

@ -14,16 +14,17 @@
import mock import mock
from oslotest import base as test_base import unittest2
from fuel_agent import errors from fuel_agent import errors
from fuel_agent.objects import partition from fuel_agent.objects import partition
class TestMD(test_base.BaseTestCase): class TestMultipleDevice(unittest2.TestCase):
def setUp(self): def setUp(self):
super(TestMD, self).setUp() super(self.__class__, self).setUp()
self.md = partition.Md('name', 'level') self.md = partition.MD(name='name', level='level')
def test_add_device_ok(self): def test_add_device_ok(self):
self.assertEqual(0, len(self.md.devices)) self.assertEqual(0, len(self.md.devices))
@ -59,8 +60,22 @@ class TestMD(test_base.BaseTestCase):
self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_spare, self.assertRaises(errors.MDDeviceDuplicationError, self.md.add_spare,
'device') '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): def setUp(self):
super(TestPartition, self).setUp() super(TestPartition, self).setUp()
self.pt = partition.Partition('name', 'count', 'device', 'begin', 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.assertEqual(1, len(self.pt.flags))
self.assertIn('fake_flag', 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): def setUp(self):
super(TestPartitionScheme, self).setUp() super(TestPartitionScheme, self).setUp()
self.p_scheme = partition.PartitionScheme() self.p_scheme = partition.PartitionScheme()
@ -83,30 +117,30 @@ class TestPartitionScheme(test_base.BaseTestCase):
self.p_scheme.root_device) self.p_scheme.root_device)
def test_fs_by_device(self): 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(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') actual_fs = self.p_scheme.fs_by_device('device')
self.assertEqual(expected_fs, actual_fs) self.assertEqual(expected_fs, actual_fs)
def test_fs_by_mount(self): 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(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') actual_fs = self.p_scheme.fs_by_mount('mount')
self.assertEqual(expected_fs, actual_fs) self.assertEqual(expected_fs, actual_fs)
def test_pv_by_name(self): 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(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') actual_pv = self.p_scheme.pv_by_name('pv')
self.assertEqual(expected_pv, actual_pv) self.assertEqual(expected_pv, actual_pv)
def test_vg_by_name(self): 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(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') actual_vg = self.p_scheme.vg_by_name('vg')
self.assertEqual(expected_vg, actual_vg) self.assertEqual(expected_vg, actual_vg)
@ -123,33 +157,33 @@ class TestPartitionScheme(test_base.BaseTestCase):
def test_md_next_name_fail(self): def test_md_next_name_fail(self):
self.p_scheme.mds = [ 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.assertRaises(errors.MDAlreadyExistsError,
self.p_scheme.md_next_name) self.p_scheme.md_next_name)
def test_md_by_name(self): def test_md_by_name(self):
self.assertEqual(0, len(self.p_scheme.mds)) 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(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')) self.assertEqual(expected_md, self.p_scheme.md_by_name('name'))
def test_md_by_mount(self): def test_md_by_mount(self):
self.assertEqual(0, len(self.p_scheme.mds)) self.assertEqual(0, len(self.p_scheme.mds))
self.assertEqual(0, len(self.p_scheme.fss)) self.assertEqual(0, len(self.p_scheme.fss))
expected_md = partition.Md('name', 'level') expected_md = partition.MD('name', 'level')
expected_fs = partition.Fs('name', mount='mount') expected_fs = partition.FS('name', mount='mount')
self.p_scheme.mds.append(expected_md) self.p_scheme.mds.append(expected_md)
self.p_scheme.fss.append(expected_fs) 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')) mount='wrong_mount'))
self.assertEqual(expected_md, self.p_scheme.md_by_mount('mount')) self.assertEqual(expected_md, self.p_scheme.md_by_mount('mount'))
def test_md_attach_by_mount_md_exists(self): def test_md_attach_by_mount_md_exists(self):
self.assertEqual(0, len(self.p_scheme.mds)) self.assertEqual(0, len(self.p_scheme.mds))
self.assertEqual(0, len(self.p_scheme.fss)) self.assertEqual(0, len(self.p_scheme.fss))
expected_md = partition.Md('name', 'level') expected_md = partition.MD('name', 'level')
expected_fs = partition.Fs('name', mount='mount') expected_fs = partition.FS('name', mount='mount')
self.p_scheme.mds.append(expected_md) self.p_scheme.mds.append(expected_md)
self.p_scheme.fss.append(expected_fs) self.p_scheme.fss.append(expected_fs)
actual_md = self.p_scheme.md_attach_by_mount('device', 'mount') 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) self.assertEqual('-F', self.p_scheme.fss[0].options)
class TestParted(test_base.BaseTestCase): class TestParted(unittest2.TestCase):
def setUp(self): def setUp(self):
super(TestParted, self).setUp() super(TestParted, self).setUp()
self.prtd = partition.Parted('name', 'label') self.prtd = partition.Parted('name', 'label')
@ -254,3 +288,99 @@ class TestParted(test_base.BaseTestCase):
'begin', 'end', 'primary')] 'begin', 'end', 'primary')]
self.prtd.partitions.extend(expected_partitions) self.prtd.partitions.extend(expected_partitions)
self.assertEqual(expected_partitions, self.prtd.primary) 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()

View File

@ -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

View File

@ -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)

View File

@ -24,6 +24,7 @@ console_scripts =
fuel_agent.drivers = fuel_agent.drivers =
nailgun = fuel_agent.drivers.nailgun:Nailgun nailgun = fuel_agent.drivers.nailgun:Nailgun
nailgun_simple = fuel_agent.drivers.simple:NailgunSimpleDriver
nailgun_build_image = fuel_agent.drivers.nailgun:NailgunBuildImage nailgun_build_image = fuel_agent.drivers.nailgun:NailgunBuildImage
[pbr] [pbr]

View File

@ -1,6 +1,9 @@
hacking>=0.8.0,<0.9 hacking>=0.8.0,<0.9
mock==1.0.1 mock==1.0.1
# TODO(prmtl): remove oslotest and (probably) testools in favor of unittest2
oslotest==1.0 oslotest==1.0
testtools>=0.9.34 testtools>=0.9.34
unittest2==1.0.1
pytest>=2.7.2 pytest>=2.7.2
pytest-cov>=1.8.1 pytest-cov>=1.8.1
requests-mock>=0.6