Implemeted do_bootloader
Change-Id: I342e85190d297d36b2a038efe682c8b537d31c2f Implements: blueprint image-based-provisioning
This commit is contained in:
@@ -129,6 +129,8 @@ class Nailgun(object):
|
||||
for disk in self.ks_disks:
|
||||
parted = partition_scheme.add_parted(
|
||||
name=self._disk_dev(disk), label='gpt')
|
||||
# we install bootloader on every disk
|
||||
parted.install_bootloader = True
|
||||
# legacy boot partition
|
||||
parted.add_partition(size=24, flags=['bios_grub'])
|
||||
# uefi partition (for future use)
|
||||
@@ -211,6 +213,8 @@ class Nailgun(object):
|
||||
fs_type=volume.get('file_system', 'xfs'),
|
||||
fs_label=self._getlabel(volume.get('disk_label')))
|
||||
|
||||
partition_scheme.append_kernel_params(
|
||||
self.data['ks_meta']['pm_data']['kernel_params'])
|
||||
return partition_scheme
|
||||
|
||||
def configdrive_scheme(self):
|
||||
@@ -257,11 +261,28 @@ class Nailgun(object):
|
||||
def image_scheme(self, partition_scheme):
|
||||
data = self.data
|
||||
image_scheme = objects.ImageScheme()
|
||||
root_image_uri = data['ks_meta']['image_uri']
|
||||
image_scheme.add_image(
|
||||
uri=root_image_uri,
|
||||
target_device=partition_scheme.root_device(),
|
||||
image_format=data['ks_meta']['image_format'],
|
||||
container=data['ks_meta']['image_container'],
|
||||
)
|
||||
for fs in partition_scheme.fss:
|
||||
if fs.mount == 'swap':
|
||||
continue
|
||||
# We assume for every file system user needs to provide
|
||||
# file system image. For example if partitioning scheme has
|
||||
# /, /boot, /var/lib file systems then will try to download
|
||||
# profile.img.gz, profile-boot.img.gz and profile-var-lib.img.gz
|
||||
# files and copy them to corresponding volumes.
|
||||
uri = 'http://%s/targetimages/%s%s.img.gz' % (
|
||||
data['ks_meta']['master_ip'],
|
||||
data['profile'].split('_')[0],
|
||||
# / => ''
|
||||
# /boot => '-boot'
|
||||
# /var/lib => '-var-lib'
|
||||
'-'.join(fs.mount.split('/')).rstrip('-')
|
||||
)
|
||||
image_scheme.add_image(
|
||||
uri=uri,
|
||||
target_device=fs.device,
|
||||
# In the future we will get image_format and container format
|
||||
# from provision.json, but currently it is hard coded.
|
||||
image_format='ext4',
|
||||
container='gzip',
|
||||
)
|
||||
return image_scheme
|
||||
|
||||
@@ -122,3 +122,11 @@ class ProcessExecutionError(Exception):
|
||||
'stdout': stdout,
|
||||
'stderr': stderr}
|
||||
super(ProcessExecutionError, self).__init__(message)
|
||||
|
||||
|
||||
class GrubUtilsError(BaseError):
|
||||
pass
|
||||
|
||||
|
||||
class FsUtilsError(BaseError):
|
||||
pass
|
||||
|
||||
@@ -17,8 +17,9 @@ import os
|
||||
from oslo.config import cfg
|
||||
|
||||
from fuel_agent import errors
|
||||
from fuel_agent.utils import artifact_utils as au
|
||||
from fuel_agent.utils import fs_utils as fu
|
||||
from fuel_agent.utils import img_utils as iu
|
||||
from fuel_agent.utils import grub_utils as gu
|
||||
from fuel_agent.utils import lvm_utils as lu
|
||||
from fuel_agent.utils import md_utils as mu
|
||||
from fuel_agent.utils import partition_utils as pu
|
||||
@@ -136,22 +137,70 @@ class Manager(object):
|
||||
|
||||
def do_copyimage(self):
|
||||
for image in self.image_scheme.images:
|
||||
processing = iu.Chain()
|
||||
processing = au.Chain()
|
||||
processing.append(image.uri)
|
||||
|
||||
if image.uri.startswith('http://'):
|
||||
processing.append(iu.HttpUrl)
|
||||
processing.append(au.HttpUrl)
|
||||
elif image.uri.startswith('file://'):
|
||||
processing.append(iu.LocalFile)
|
||||
processing.append(au.LocalFile)
|
||||
|
||||
if image.container == 'gzip':
|
||||
processing.append(iu.GunzipStream)
|
||||
processing.append(au.GunzipStream)
|
||||
|
||||
processing.append(image.target_device)
|
||||
processing.process()
|
||||
# For every file system in partitioning scheme we call
|
||||
# make_fs utility. That means we do not care whether fs image
|
||||
# is available for a particular file system.
|
||||
# If image is not available we assume user wants to
|
||||
# leave this file system un-touched.
|
||||
try:
|
||||
processing.process()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def mount_target(self, chroot):
|
||||
# Here we are going to mount all file systems in partition scheme.
|
||||
# Shorter paths earlier. We sort all mount points by their depth.
|
||||
# ['/', '/boot', '/var', '/var/lib/mysql']
|
||||
key = lambda x: len(x.mount.rstrip('/').split('/'))
|
||||
for fs in sorted(self.partition_scheme.fss, key=key):
|
||||
if fs.mount == 'swap':
|
||||
continue
|
||||
mount = chroot + fs.mount
|
||||
if not os.path.isdir(mount):
|
||||
os.makedirs(mount, mode=0o755)
|
||||
fu.mount_fs(fs.type, fs.device, mount)
|
||||
fu.mount_bind(chroot, '/sys')
|
||||
fu.mount_bind(chroot, '/dev')
|
||||
fu.mount_bind(chroot, '/proc')
|
||||
|
||||
def umount_target(self, chroot):
|
||||
key = lambda x: len(x.mount.rstrip('/').split('/'))
|
||||
for fs in sorted(self.partition_scheme.fss, key=key, reverse=True):
|
||||
fu.umount_fs(fs.device)
|
||||
fu.umount_fs(chroot + '/proc')
|
||||
fu.umount_fs(chroot + '/dev')
|
||||
fu.umount_fs(chroot + '/sys')
|
||||
|
||||
def do_bootloader(self):
|
||||
pass
|
||||
chroot = '/tmp/target'
|
||||
self.mount_target(chroot)
|
||||
|
||||
grub_version = gu.grub_version_guess(chroot=chroot)
|
||||
boot_device = self.partition_scheme.boot_device(grub_version)
|
||||
install_devices = [d.name for d in self.partition_scheme.parteds
|
||||
if d.install_bootloader]
|
||||
kernel_params = self.partition_scheme.kernel_params
|
||||
|
||||
if grub_version == 1:
|
||||
gu.grub1_cfg(kernel_params=kernel_params, chroot=chroot)
|
||||
gu.grub1_install(install_devices, boot_device, chroot=chroot)
|
||||
else:
|
||||
gu.grub2_cfg(kernel_params=kernel_params, chroot=chroot)
|
||||
gu.grub2_install(install_devices, chroot=chroot)
|
||||
|
||||
self.umount_target(chroot)
|
||||
|
||||
def do_provisioning(self):
|
||||
self.do_parsing()
|
||||
|
||||
@@ -23,6 +23,7 @@ class Parted(object):
|
||||
self.name = name
|
||||
self.label = label
|
||||
self.partitions = []
|
||||
self.install_bootloader = False
|
||||
|
||||
def add_partition(self, **kwargs):
|
||||
# TODO(kozhukalov): validate before appending
|
||||
@@ -186,6 +187,7 @@ class PartitionScheme(object):
|
||||
self.vgs = []
|
||||
self.lvs = []
|
||||
self.fss = []
|
||||
self.kernel_params = ''
|
||||
|
||||
def add_parted(self, **kwargs):
|
||||
parted = Parted(**kwargs)
|
||||
@@ -282,19 +284,61 @@ class PartitionScheme(object):
|
||||
if found:
|
||||
return found[0]
|
||||
|
||||
def root_device(self):
|
||||
for fs in self.fss:
|
||||
if fs.mount == '/':
|
||||
return fs.device
|
||||
raise errors.WrongPartitionSchemeError(
|
||||
'Error while trying to find root device: '
|
||||
'root file system not found')
|
||||
def lv_by_device_name(self, device_name):
|
||||
found = filter(lambda x: x.device_name == device_name, self.lvs)
|
||||
if found:
|
||||
return found[0]
|
||||
|
||||
def root_device(self):
|
||||
fs = self.fs_by_mount('/')
|
||||
if not fs:
|
||||
raise errors.WrongPartitionSchemeError(
|
||||
'Error while trying to find root device: '
|
||||
'root file system not found')
|
||||
return fs.device
|
||||
|
||||
def boot_device(self, grub_version=2):
|
||||
# We assume /boot is a separate partition. If it is not
|
||||
# then we try to use root file system
|
||||
boot_fs = self.fs_by_mount('/boot') or self.fs_by_mount('/')
|
||||
if not boot_fs:
|
||||
raise errors.WrongPartitionSchemeError(
|
||||
'Error while trying to find boot device: '
|
||||
'boot file system not fount, '
|
||||
'it must be a separate mount point')
|
||||
|
||||
if grub_version == 1:
|
||||
# Legacy GRUB has a limitation. It is not able to mount MD devices.
|
||||
# If it is MD compatible it is only able to ignore MD metadata
|
||||
# and to mount one of those devices which are parts of MD device,
|
||||
# but it is possible only if MD device is a MIRROR.
|
||||
md = self.md_by_name(boot_fs.device)
|
||||
if md:
|
||||
try:
|
||||
return md.devices[0]
|
||||
except IndexError:
|
||||
raise errors.WrongPartitionSchemeError(
|
||||
'Error while trying to find boot device: '
|
||||
'md device %s does not have devices attached' %
|
||||
md.name)
|
||||
# Legacy GRUB is not able to mount LVM devices.
|
||||
if self.lv_by_device_name(boot_fs.device):
|
||||
raise errors.WrongPartitionSchemeError(
|
||||
'Error while trying to find boot device: '
|
||||
'found device is %s but legacy grub is not able to '
|
||||
'mount logical volumes' %
|
||||
boot_fs.device)
|
||||
|
||||
return boot_fs.device
|
||||
|
||||
# Configdrive device must be a small (about 10M) partition
|
||||
# on one of node hard drives. This partition is necessary
|
||||
# only if one uses cloud-init with configdrive.
|
||||
def configdrive_device(self):
|
||||
# Configdrive device must be a small (about 10M) partition
|
||||
# on one of node hard drives. This partition is necessary
|
||||
# only if one uses cloud-init with configdrive.
|
||||
for parted in self.parteds:
|
||||
for prt in parted.partitions:
|
||||
if prt.configdrive:
|
||||
return prt.name
|
||||
|
||||
def append_kernel_params(self, kernel_params):
|
||||
self.kernel_params += ' ' + kernel_params
|
||||
|
||||
@@ -17,18 +17,18 @@ from oslotest import base as test_base
|
||||
import requests
|
||||
import zlib
|
||||
|
||||
from fuel_agent.utils import img_utils as iu
|
||||
from fuel_agent.utils import artifact_utils as au
|
||||
|
||||
|
||||
class TestTarget(test_base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestTarget, self).setUp()
|
||||
self.tgt = iu.Target()
|
||||
self.tgt = au.Target()
|
||||
|
||||
def test_target_next(self):
|
||||
self.assertRaises(StopIteration, self.tgt.next)
|
||||
|
||||
@mock.patch.object(iu.Target, '__iter__')
|
||||
@mock.patch.object(au.Target, '__iter__')
|
||||
def test_target_target(self, mock_iter):
|
||||
mock_iter.return_value = iter(['chunk1', 'chunk2', 'chunk3'])
|
||||
m = mock.mock_open()
|
||||
@@ -46,7 +46,7 @@ class TestTarget(test_base.BaseTestCase):
|
||||
class TestLocalFile(test_base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestLocalFile, self).setUp()
|
||||
self.lf = iu.LocalFile('/dev/null')
|
||||
self.lf = au.LocalFile('/dev/null')
|
||||
|
||||
def test_localfile_next(self):
|
||||
self.lf.fileobj = mock.Mock()
|
||||
@@ -61,7 +61,8 @@ class TestHttpUrl(test_base.BaseTestCase):
|
||||
def test_httpurl_iter(self, mock_r_get):
|
||||
content = ['fake content #1', 'fake content #2']
|
||||
mock_r_get.return_value.iter_content.return_value = content
|
||||
httpurl = iu.HttpUrl('fake_url')
|
||||
mock_r_get.return_value.status_code = 200
|
||||
httpurl = au.HttpUrl('fake_url')
|
||||
for data in enumerate(httpurl):
|
||||
self.assertEqual(content[data[0]], data[1])
|
||||
self.assertEqual('fake_url', httpurl.url)
|
||||
@@ -71,7 +72,7 @@ class TestGunzipStream(test_base.BaseTestCase):
|
||||
def test_gunzip_stream_next(self):
|
||||
content = ['fake content #1']
|
||||
compressed_stream = [zlib.compress(data) for data in content]
|
||||
gunzip_stream = iu.GunzipStream(compressed_stream)
|
||||
gunzip_stream = au.GunzipStream(compressed_stream)
|
||||
for data in enumerate(gunzip_stream):
|
||||
self.assertEqual(content[data[0]], data[1])
|
||||
|
||||
@@ -79,7 +80,7 @@ class TestGunzipStream(test_base.BaseTestCase):
|
||||
class TestChain(test_base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestChain, self).setUp()
|
||||
self.chain = iu.Chain()
|
||||
self.chain = au.Chain()
|
||||
|
||||
def test_append(self):
|
||||
self.assertEqual(0, len(self.chain.processors))
|
||||
@@ -89,7 +90,7 @@ class TestChain(test_base.BaseTestCase):
|
||||
|
||||
def test_process(self):
|
||||
self.chain.processors.append('fake_uri')
|
||||
fake_processor = mock.Mock(spec=iu.Target)
|
||||
fake_processor = mock.Mock(spec=au.Target)
|
||||
self.chain.processors.append(fake_processor)
|
||||
self.chain.processors.append('fake_target')
|
||||
self.chain.process()
|
||||
@@ -15,6 +15,7 @@
|
||||
import mock
|
||||
from oslotest import base as test_base
|
||||
|
||||
from fuel_agent import errors
|
||||
from fuel_agent.utils import fs_utils as fu
|
||||
from fuel_agent.utils import utils
|
||||
|
||||
@@ -32,3 +33,56 @@ class TestFSUtils(test_base.BaseTestCase):
|
||||
fu.make_fs('swap', ' -f ', ' -L fake_label ', '/dev/fake')
|
||||
mock_exec.assert_called_once_with('mkswap', '-f', '-L', 'fake_label',
|
||||
'/dev/fake')
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_extend_fs_ok_ext3(self, mock_exec):
|
||||
fu.extend_fs('ext3', '/dev/fake')
|
||||
expected_calls = [
|
||||
mock.call('e2fsck', ' -f', '/dev/fake', check_exit_code=[0]),
|
||||
mock.call('resize2fs', '/dev/fake', check_exit_code=[0])
|
||||
]
|
||||
self.assertEqual(mock_exec.call_args_list, expected_calls)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_extend_fs_ok_ext4(self, mock_exec):
|
||||
fu.extend_fs('ext4', '/dev/fake')
|
||||
expected_calls = [
|
||||
mock.call('e2fsck', ' -f', '/dev/fake', check_exit_code=[0]),
|
||||
mock.call('resize2fs', '/dev/fake', check_exit_code=[0])
|
||||
]
|
||||
self.assertEqual(mock_exec.call_args_list, expected_calls)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_extend_fs_ok_xfs(self, mock_exec):
|
||||
fu.extend_fs('xfs', '/dev/fake')
|
||||
mock_exec.assert_called_once_with(
|
||||
'xfs_growfs', '/dev/fake', check_exit_code=[0])
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_extend_fs_unsupported_fs(self, mock_exec):
|
||||
self.assertRaises(errors.FsUtilsError, fu.extend_fs,
|
||||
'unsupported', '/dev/fake')
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_mount_fs(self, mock_exec):
|
||||
fu.mount_fs('ext3', '/dev/fake', '/target')
|
||||
mock_exec.assert_called_once_with(
|
||||
'mount', '-t', 'ext3', '/dev/fake', '/target', check_exit_code=[0])
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_mount_bind_no_path2(self, mock_exec):
|
||||
fu.mount_bind('/target', '/fake')
|
||||
mock_exec.assert_called_once_with(
|
||||
'mount', '--bind', '/fake', '/target/fake', check_exit_code=[0])
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_mount_bind_path2(self, mock_exec):
|
||||
fu.mount_bind('/target', '/fake', '/fake2')
|
||||
mock_exec.assert_called_once_with(
|
||||
'mount', '--bind', '/fake', '/target/fake2', check_exit_code=[0])
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_umount_fs(self, mock_exec):
|
||||
fu.umount_fs('/fake')
|
||||
mock_exec.assert_called_once_with(
|
||||
'umount', '/fake', check_exit_code=[0])
|
||||
|
||||
390
fuel_agent/tests/test_grub_utils.py
Normal file
390
fuel_agent/tests/test_grub_utils.py
Normal file
@@ -0,0 +1,390 @@
|
||||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
import os
|
||||
from oslotest import base as test_base
|
||||
import six
|
||||
import StringIO
|
||||
|
||||
if six.PY2:
|
||||
OPEN_FUNCTION_NAME = '__builtin__.open'
|
||||
else:
|
||||
OPEN_FUNCTION_NAME = 'builtins.open'
|
||||
|
||||
from fuel_agent import errors
|
||||
from fuel_agent.utils import grub_utils as gu
|
||||
from fuel_agent.utils import utils
|
||||
|
||||
|
||||
class TestGrubUtils(test_base.BaseTestCase):
|
||||
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
def test_guess_grub2_conf(self, mock_isfile):
|
||||
side_effect_values = {
|
||||
'/target/boot/grub/grub.cfg': True,
|
||||
'/target/boot/grub2/grub.cfg': False
|
||||
}
|
||||
|
||||
def side_effect(key):
|
||||
return side_effect_values[key]
|
||||
|
||||
mock_isfile.side_effect = side_effect
|
||||
self.assertEqual(gu.guess_grub2_conf('/target'),
|
||||
'/boot/grub/grub.cfg')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/boot/grub/grub.cfg': False,
|
||||
'/target/boot/grub2/grub.cfg': True
|
||||
}
|
||||
self.assertEqual(gu.guess_grub2_conf('/target'),
|
||||
'/boot/grub2/grub.cfg')
|
||||
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
def test_guess_grub2_default(self, mock_isfile):
|
||||
side_effect_values = {
|
||||
'/target/etc/default/grub': True,
|
||||
'/target/etc/sysconfig/grub': False
|
||||
}
|
||||
|
||||
def side_effect(key):
|
||||
return side_effect_values[key]
|
||||
|
||||
mock_isfile.side_effect = side_effect
|
||||
self.assertEqual(gu.guess_grub2_default('/target'),
|
||||
'/etc/default/grub')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/etc/default/grub': False,
|
||||
'/target/etc/sysconfig/grub': True
|
||||
}
|
||||
self.assertEqual(gu.guess_grub2_default('/target'),
|
||||
'/etc/sysconfig/grub')
|
||||
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
def test_guess_grub2_mkconfig(self, mock_isfile):
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-mkconfig': True,
|
||||
'/target/sbin/grub2-mkconfig': False,
|
||||
'/target/usr/sbin/grub-mkconfig': False,
|
||||
'/target/usr/sbin/grub2-mkconfig': False
|
||||
}
|
||||
|
||||
def side_effect(key):
|
||||
return side_effect_values[key]
|
||||
|
||||
mock_isfile.side_effect = side_effect
|
||||
self.assertEqual(gu.guess_grub2_mkconfig('/target'),
|
||||
'/sbin/grub-mkconfig')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-mkconfig': False,
|
||||
'/target/sbin/grub2-mkconfig': True,
|
||||
'/target/usr/sbin/grub-mkconfig': False,
|
||||
'/target/usr/sbin/grub2-mkconfig': False
|
||||
}
|
||||
self.assertEqual(gu.guess_grub2_mkconfig('/target'),
|
||||
'/sbin/grub2-mkconfig')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-mkconfig': False,
|
||||
'/target/sbin/grub2-mkconfig': False,
|
||||
'/target/usr/sbin/grub-mkconfig': True,
|
||||
'/target/usr/sbin/grub2-mkconfig': False
|
||||
}
|
||||
self.assertEqual(gu.guess_grub2_mkconfig('/target'),
|
||||
'/usr/sbin/grub-mkconfig')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-mkconfig': False,
|
||||
'/target/sbin/grub2-mkconfig': False,
|
||||
'/target/usr/sbin/grub-mkconfig': False,
|
||||
'/target/usr/sbin/grub2-mkconfig': True
|
||||
}
|
||||
self.assertEqual(gu.guess_grub2_mkconfig('/target'),
|
||||
'/usr/sbin/grub2-mkconfig')
|
||||
|
||||
@mock.patch.object(gu, 'guess_grub_install')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_guess_grub_version_1(self, mock_exec, mock_ggi):
|
||||
mock_ggi.return_value = '/grub_install'
|
||||
mock_exec.return_value = ('foo 0.97 bar', '')
|
||||
version = gu.guess_grub_version('/target')
|
||||
mock_exec.assert_called_once_with('/target/grub_install', '-v')
|
||||
self.assertEqual(version, 1)
|
||||
|
||||
@mock.patch.object(gu, 'guess_grub_install')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_guess_grub_version_2(self, mock_exec, mock_ggi):
|
||||
mock_ggi.return_value = '/grub_install'
|
||||
mock_exec.return_value = ('foo bar', '')
|
||||
version = gu.guess_grub_version('/target')
|
||||
mock_exec.assert_called_once_with('/target/grub_install', '-v')
|
||||
self.assertEqual(version, 2)
|
||||
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
def test_guess_grub(self, mock_isfile):
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub': True,
|
||||
'/target/usr/sbin/grub': False
|
||||
}
|
||||
|
||||
def side_effect(key):
|
||||
return side_effect_values[key]
|
||||
|
||||
mock_isfile.side_effect = side_effect
|
||||
self.assertEqual(gu.guess_grub('/target'),
|
||||
'/sbin/grub')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub': False,
|
||||
'/target/usr/sbin/grub': True
|
||||
}
|
||||
self.assertEqual(gu.guess_grub('/target'),
|
||||
'/usr/sbin/grub')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub': False,
|
||||
'/target/usr/sbin/grub': False
|
||||
}
|
||||
self.assertRaises(errors.GrubUtilsError, gu.guess_grub, '/target')
|
||||
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
def test_grub_install(self, mock_isfile):
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-install': True,
|
||||
'/target/sbin/grub2-install': False,
|
||||
'/target/usr/sbin/grub-install': False,
|
||||
'/target/usr/sbin/grub2-install': False
|
||||
}
|
||||
|
||||
def side_effect(key):
|
||||
return side_effect_values[key]
|
||||
|
||||
mock_isfile.side_effect = side_effect
|
||||
self.assertEqual(gu.guess_grub_install('/target'),
|
||||
'/sbin/grub-install')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-install': False,
|
||||
'/target/sbin/grub2-install': True,
|
||||
'/target/usr/sbin/grub-install': False,
|
||||
'/target/usr/sbin/grub2-install': False
|
||||
}
|
||||
self.assertEqual(gu.guess_grub_install('/target'),
|
||||
'/sbin/grub2-install')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-install': False,
|
||||
'/target/sbin/grub2-install': False,
|
||||
'/target/usr/sbin/grub-install': True,
|
||||
'/target/usr/sbin/grub2-install': False
|
||||
}
|
||||
self.assertEqual(gu.guess_grub_install('/target'),
|
||||
'/usr/sbin/grub-install')
|
||||
|
||||
side_effect_values = {
|
||||
'/target/sbin/grub-install': False,
|
||||
'/target/sbin/grub2-install': False,
|
||||
'/target/usr/sbin/grub-install': False,
|
||||
'/target/usr/sbin/grub2-install': True
|
||||
}
|
||||
self.assertEqual(gu.guess_grub_install('/target'),
|
||||
'/usr/sbin/grub2-install')
|
||||
|
||||
@mock.patch.object(os, 'listdir')
|
||||
def test_guess_kernel(self, mock_listdir):
|
||||
mock_listdir.return_value = ['1', '2', 'vmlinuz-version', '3']
|
||||
self.assertEqual(gu.guess_kernel('/target'), 'vmlinuz-version')
|
||||
mock_listdir.return_value = ['1', '2', '3']
|
||||
self.assertRaises(errors.GrubUtilsError, gu.guess_kernel, '/target')
|
||||
|
||||
@mock.patch.object(os, 'listdir')
|
||||
def test_guess_initrd(self, mock_listdir):
|
||||
mock_listdir.return_value = ['1', '2', 'initramfs-version', '3']
|
||||
self.assertEqual(gu.guess_initrd('/target'), 'initramfs-version')
|
||||
mock_listdir.return_value = ['1', '2', 'initrd-version', '3']
|
||||
self.assertEqual(gu.guess_initrd('/target'), 'initrd-version')
|
||||
mock_listdir.return_value = ['1', '2', '3']
|
||||
self.assertRaises(errors.GrubUtilsError, gu.guess_initrd, '/target')
|
||||
|
||||
@mock.patch.object(gu, 'grub1_mbr')
|
||||
def test_grub1_install(self, mock_mbr):
|
||||
install_devices = ['/dev/foo', '/dev/bar']
|
||||
expected_calls_mbr = []
|
||||
for install_device in install_devices:
|
||||
expected_calls_mbr.append(
|
||||
mock.call(install_device, '/dev/foo', '0', chroot='/target'))
|
||||
gu.grub1_install(install_devices, '/dev/foo1', '/target')
|
||||
self.assertEqual(expected_calls_mbr, mock_mbr.call_args_list)
|
||||
# should raise exception if boot_device (second argument)
|
||||
# is not a partition but a whole disk
|
||||
self.assertRaises(errors.GrubUtilsError, gu.grub1_install,
|
||||
'/dev/foo', '/dev/foo', chroot='/target')
|
||||
|
||||
@mock.patch.object(gu, 'guess_grub')
|
||||
@mock.patch.object(os, 'chmod')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_grub1_mbr_install_differs_boot(self, mock_exec,
|
||||
mock_chmod, mock_guess):
|
||||
mock_guess.return_value = '/sbin/grub'
|
||||
|
||||
# install_device != boot_disk
|
||||
batch = 'device (hd0) /dev/foo\n'
|
||||
batch += 'geometry (hd0) 130 255 63\n'
|
||||
batch += 'device (hd1) /dev/bar\n'
|
||||
batch += 'geometry (hd1) 130 255 63\n'
|
||||
batch += 'root (hd1,0)\n'
|
||||
batch += 'setup (hd0)\n'
|
||||
batch += 'quit\n'
|
||||
script = 'cat /tmp/grub.batch | /sbin/grub --no-floppy --batch'
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch(OPEN_FUNCTION_NAME, new=mock_open, create=True):
|
||||
gu.grub1_mbr('/dev/foo', '/dev/bar', '0', chroot='/target')
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[mock.call('/target/tmp/grub.batch', 'wb'),
|
||||
mock.call('/target/tmp/grub.sh', 'wb')]
|
||||
)
|
||||
mock_open_file = mock_open()
|
||||
self.assertEqual(
|
||||
mock_open_file.write.call_args_list,
|
||||
[mock.call(batch), mock.call(script)]
|
||||
)
|
||||
mock_chmod.assert_called_once_with('/target/tmp/grub.sh', 0o755)
|
||||
mock_exec.assert_called_once_with(
|
||||
'chroot', '/target', '/tmp/grub.sh',
|
||||
run_as_root=True, check_exit_code=[0])
|
||||
|
||||
@mock.patch.object(gu, 'guess_grub')
|
||||
@mock.patch.object(os, 'chmod')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_grub1_mbr_install_same_as_boot(self, mock_exec,
|
||||
mock_chmod, mock_guess):
|
||||
mock_guess.return_value = '/sbin/grub'
|
||||
|
||||
# install_device == boot_disk
|
||||
batch = 'device (hd0) /dev/foo\n'
|
||||
batch += 'geometry (hd0) 130 255 63\n'
|
||||
batch += 'root (hd0,0)\n'
|
||||
batch += 'setup (hd0)\n'
|
||||
batch += 'quit\n'
|
||||
script = 'cat /tmp/grub.batch | /sbin/grub --no-floppy --batch'
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch(OPEN_FUNCTION_NAME, new=mock_open, create=True):
|
||||
gu.grub1_mbr('/dev/foo', '/dev/foo', '0', chroot='/target')
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[mock.call('/target/tmp/grub.batch', 'wb'),
|
||||
mock.call('/target/tmp/grub.sh', 'wb')]
|
||||
)
|
||||
mock_open_file = mock_open()
|
||||
self.assertEqual(
|
||||
mock_open_file.write.call_args_list,
|
||||
[mock.call(batch), mock.call(script)]
|
||||
)
|
||||
mock_chmod.assert_called_once_with('/target/tmp/grub.sh', 0o755)
|
||||
mock_exec.assert_called_once_with(
|
||||
'chroot', '/target', '/tmp/grub.sh',
|
||||
run_as_root=True, check_exit_code=[0])
|
||||
|
||||
@mock.patch.object(gu, 'guess_kernel')
|
||||
@mock.patch.object(gu, 'guess_initrd')
|
||||
def test_grub1_cfg_kernel_initrd_are_not_set(self, mock_initrd,
|
||||
mock_kernel):
|
||||
mock_kernel.return_value = 'kernel-version'
|
||||
mock_initrd.return_value = 'initrd-version'
|
||||
config = """
|
||||
default=0
|
||||
timeout=5
|
||||
title Default (kernel-version)
|
||||
kernel /kernel-version kernel-params
|
||||
initrd /initrd-version
|
||||
"""
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch(OPEN_FUNCTION_NAME, new=mock_open, create=True):
|
||||
gu.grub1_cfg(chroot='/target', kernel_params='kernel-params')
|
||||
mock_open.assert_called_once_with('/target/boot/grub/grub.conf', 'wb')
|
||||
mock_open_file = mock_open()
|
||||
mock_open_file.write.assert_called_once_with(config)
|
||||
|
||||
def test_grub1_cfg_kernel_initrd_are_set(self):
|
||||
config = """
|
||||
default=0
|
||||
timeout=5
|
||||
title Default (kernel-version-set)
|
||||
kernel /kernel-version-set kernel-params
|
||||
initrd /initrd-version-set
|
||||
"""
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch(OPEN_FUNCTION_NAME, new=mock_open, create=True):
|
||||
gu.grub1_cfg(kernel='kernel-version-set',
|
||||
initrd='initrd-version-set',
|
||||
chroot='/target', kernel_params='kernel-params')
|
||||
mock_open.assert_called_once_with('/target/boot/grub/grub.conf', 'wb')
|
||||
mock_open_file = mock_open()
|
||||
mock_open_file.write.assert_called_once_with(config)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
@mock.patch.object(gu, 'guess_grub_install')
|
||||
def test_grub2_install(self, mock_guess_grub, mock_exec):
|
||||
mock_guess_grub.return_value = '/sbin/grub'
|
||||
expected_calls = [
|
||||
mock.call('chroot', '/target', '/sbin/grub', '/dev/foo',
|
||||
run_as_root=True, check_exit_code=[0]),
|
||||
mock.call('chroot', '/target', '/sbin/grub', '/dev/bar',
|
||||
run_as_root=True, check_exit_code=[0])
|
||||
]
|
||||
gu.grub2_install(['/dev/foo', '/dev/bar'], chroot='/target')
|
||||
self.assertEqual(mock_exec.call_args_list, expected_calls)
|
||||
|
||||
@mock.patch.object(gu, 'guess_grub2_conf')
|
||||
@mock.patch.object(gu, 'guess_grub2_mkconfig')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
@mock.patch.object(gu, 'guess_grub2_default')
|
||||
def test_grub2_cfg(self, mock_def, mock_exec, mock_mkconfig, mock_conf):
|
||||
mock_def.return_value = '/etc/default/grub'
|
||||
mock_mkconfig.return_value = '/sbin/grub-mkconfig'
|
||||
mock_conf.return_value = '/boot/grub/grub.cfg'
|
||||
orig_content = """foo
|
||||
GRUB_CMDLINE_LINUX="kernel-params-orig"
|
||||
bar"""
|
||||
new_content = """foo
|
||||
GRUB_CMDLINE_LINUX="kernel-params-new"
|
||||
bar"""
|
||||
|
||||
# mock_open = mock.mock_open(read_data=orig_content)
|
||||
with mock.patch(OPEN_FUNCTION_NAME,
|
||||
new=mock.mock_open(read_data=orig_content),
|
||||
create=True) as mock_open:
|
||||
mock_open.return_value = mock.MagicMock(spec=file)
|
||||
handle = mock_open.return_value.__enter__.return_value
|
||||
handle.__iter__.return_value = StringIO.StringIO(orig_content)
|
||||
gu.grub2_cfg(kernel_params='kernel-params-new', chroot='/target')
|
||||
|
||||
self.assertEqual(
|
||||
mock_open.call_args_list,
|
||||
[mock.call('/target/etc/default/grub'),
|
||||
mock.call('/target/etc/default/grub', 'wb')]
|
||||
)
|
||||
|
||||
handle.write.assert_called_once_with(new_content)
|
||||
mock_exec.assert_called_once_with('chroot', '/target',
|
||||
'/sbin/grub-mkconfig',
|
||||
'-o', '/boot/grub/grub.cfg',
|
||||
run_as_root=True)
|
||||
@@ -21,9 +21,9 @@ from fuel_agent import errors
|
||||
from fuel_agent import manager
|
||||
from fuel_agent.objects import partition
|
||||
from fuel_agent.tests import test_nailgun
|
||||
from fuel_agent.utils import artifact_utils as au
|
||||
from fuel_agent.utils import fs_utils as fu
|
||||
from fuel_agent.utils import hardware_utils as hu
|
||||
from fuel_agent.utils import img_utils as iu
|
||||
from fuel_agent.utils import lvm_utils as lu
|
||||
from fuel_agent.utils import md_utils as mu
|
||||
from fuel_agent.utils import partition_utils as pu
|
||||
@@ -131,7 +131,7 @@ class TestManager(test_base.BaseTestCase):
|
||||
def test_do_configdrive(self, mock_lbd, mock_u_ras, mock_u_e):
|
||||
mock_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE
|
||||
self.mgr.do_parsing()
|
||||
self.assertEqual(1, len(self.mgr.image_scheme.images))
|
||||
self.assertEqual(4, len(self.mgr.image_scheme.images))
|
||||
self.mgr.do_configdrive()
|
||||
mock_u_ras_expected_calls = [
|
||||
mock.call(CONF.nc_template_path, 'cloud_config_ubuntu.jinja2',
|
||||
@@ -155,7 +155,7 @@ class TestManager(test_base.BaseTestCase):
|
||||
'%s/%s' % (CONF.tmp_path, 'user-data'),
|
||||
'%s/%s' % (CONF.tmp_path, 'meta-data'))]
|
||||
self.assertEqual(mock_u_e_expected_calls, mock_u_e.call_args_list)
|
||||
self.assertEqual(2, len(self.mgr.image_scheme.images))
|
||||
self.assertEqual(5, len(self.mgr.image_scheme.images))
|
||||
cf_drv_img = self.mgr.image_scheme.images[-1]
|
||||
self.assertEqual('file://%s' % CONF.config_drive_path, cf_drv_img.uri)
|
||||
self.assertEqual('/dev/sda7',
|
||||
@@ -175,15 +175,15 @@ class TestManager(test_base.BaseTestCase):
|
||||
self.assertRaises(errors.WrongPartitionSchemeError,
|
||||
self.mgr.do_configdrive)
|
||||
|
||||
@mock.patch.object(iu, 'GunzipStream')
|
||||
@mock.patch.object(iu, 'LocalFile')
|
||||
@mock.patch.object(iu, 'HttpUrl')
|
||||
@mock.patch.object(iu, 'Chain')
|
||||
@mock.patch.object(au, 'GunzipStream')
|
||||
@mock.patch.object(au, 'LocalFile')
|
||||
@mock.patch.object(au, 'HttpUrl')
|
||||
@mock.patch.object(au, 'Chain')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
@mock.patch.object(utils, 'render_and_save')
|
||||
@mock.patch.object(hu, 'list_block_devices')
|
||||
def test_do_copyimage(self, mock_lbd, mock_u_ras, mock_u_e, mock_iu_c,
|
||||
mock_iu_h, mock_iu_l, mock_iu_g):
|
||||
def test_do_copyimage(self, mock_lbd, mock_u_ras, mock_u_e, mock_au_c,
|
||||
mock_au_h, mock_au_l, mock_au_g):
|
||||
|
||||
class FakeChain(object):
|
||||
processors = []
|
||||
@@ -195,14 +195,24 @@ class TestManager(test_base.BaseTestCase):
|
||||
pass
|
||||
|
||||
mock_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE
|
||||
mock_iu_c.return_value = FakeChain()
|
||||
mock_au_c.return_value = FakeChain()
|
||||
self.mgr.do_parsing()
|
||||
self.mgr.do_configdrive()
|
||||
self.mgr.do_copyimage()
|
||||
imgs = self.mgr.image_scheme.images
|
||||
self.assertEqual(2, len(imgs))
|
||||
expected_processors_list = [imgs[0].uri, iu.HttpUrl, iu.GunzipStream,
|
||||
imgs[0].target_device, imgs[1].uri,
|
||||
iu.LocalFile, imgs[1].target_device]
|
||||
self.assertEqual(5, len(imgs))
|
||||
expected_processors_list = []
|
||||
for img in imgs[:-1]:
|
||||
expected_processors_list += [
|
||||
img.uri,
|
||||
au.HttpUrl,
|
||||
au.GunzipStream,
|
||||
img.target_device
|
||||
]
|
||||
expected_processors_list += [
|
||||
imgs[-1].uri,
|
||||
au.LocalFile,
|
||||
imgs[-1].target_device
|
||||
]
|
||||
self.assertEqual(expected_processors_list,
|
||||
mock_iu_c.return_value.processors)
|
||||
mock_au_c.return_value.processors)
|
||||
|
||||
@@ -17,6 +17,7 @@ from oslotest import base as test_base
|
||||
|
||||
from fuel_agent.drivers import nailgun
|
||||
from fuel_agent import errors
|
||||
from fuel_agent.objects import image
|
||||
from fuel_agent.utils import hardware_utils as hu
|
||||
|
||||
|
||||
@@ -477,13 +478,28 @@ class TestNailgun(test_base.BaseTestCase):
|
||||
mock_lbd.return_value = LIST_BLOCK_DEVICES_SAMPLE
|
||||
p_scheme = self.drv.partition_scheme()
|
||||
i_scheme = self.drv.image_scheme(p_scheme)
|
||||
self.assertEqual(1, len(i_scheme.images))
|
||||
img = i_scheme.images[0]
|
||||
self.assertEqual('gzip', img.container)
|
||||
self.assertEqual('ext4', img.image_format)
|
||||
self.assertEqual('/dev/mapper/os-root', img.target_device)
|
||||
self.assertEqual('http://fake_image_url', img.uri)
|
||||
self.assertEqual(None, img.size)
|
||||
expected_images = []
|
||||
for fs in p_scheme.fss:
|
||||
if fs.mount == 'swap':
|
||||
continue
|
||||
expected_images.append(image.Image(
|
||||
uri='http://%s/targetimages/%s%s.img.gz' % (
|
||||
self.drv.data['ks_meta']['master_ip'],
|
||||
self.drv.data['profile'].split('_')[0],
|
||||
'-'.join(fs.mount.split('/')).rstrip('-')),
|
||||
target_device=fs.device,
|
||||
image_format='ext4',
|
||||
container='gzip'
|
||||
))
|
||||
expected_images = sorted(expected_images, key=lambda x: x.uri)
|
||||
for i, img in enumerate(sorted(i_scheme.images, key=lambda x: x.uri)):
|
||||
self.assertEqual(img.uri, expected_images[i].uri)
|
||||
self.assertEqual(img.target_device,
|
||||
expected_images[i].target_device)
|
||||
self.assertEqual(img.image_format,
|
||||
expected_images[i].image_format)
|
||||
self.assertEqual(img.container,
|
||||
expected_images[i].container)
|
||||
|
||||
def test_getlabel(self):
|
||||
self.assertEqual('', self.drv._getlabel(None))
|
||||
|
||||
172
fuel_agent/utils/artifact_utils.py
Normal file
172
fuel_agent/utils/artifact_utils.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import abc
|
||||
import requests
|
||||
import tarfile
|
||||
import tempfile
|
||||
import zlib
|
||||
|
||||
|
||||
class Target(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
raise StopIteration()
|
||||
|
||||
def target(self, filename='/dev/null'):
|
||||
with open(filename, 'wb') as f:
|
||||
for chunk in self:
|
||||
f.write(chunk)
|
||||
f.flush()
|
||||
|
||||
|
||||
class LocalFile(Target):
|
||||
def __init__(self, filename):
|
||||
self.filename = str(filename)
|
||||
self.fileobj = None
|
||||
|
||||
def next(self):
|
||||
if not self.fileobj:
|
||||
self.fileobj = open(self.filename, 'rb')
|
||||
buffer = self.fileobj.read(1048576)
|
||||
if buffer:
|
||||
return buffer
|
||||
else:
|
||||
self.fileobj.close()
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
class HttpUrl(Target):
|
||||
def __init__(self, url):
|
||||
self.url = str(url)
|
||||
|
||||
def __iter__(self):
|
||||
response = requests.get(self.url, stream=True)
|
||||
if response.status_code != 200:
|
||||
raise Exception('Can not get %s' % self.url)
|
||||
return iter(response.iter_content(1048576))
|
||||
|
||||
|
||||
class GunzipStream(Target):
|
||||
def __init__(self, stream):
|
||||
self.stream = iter(stream)
|
||||
#NOTE(agordeev): toggle automatic header detection on
|
||||
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 32)
|
||||
|
||||
def next(self):
|
||||
try:
|
||||
return self.decompressor.decompress(self.stream.next())
|
||||
except StopIteration:
|
||||
raise
|
||||
|
||||
|
||||
class ForwardFileStream(Target):
|
||||
def __init__(self, stream):
|
||||
self.stream = iter(stream)
|
||||
self.position = 0
|
||||
self.chunk = ''
|
||||
self.closed = False
|
||||
|
||||
def next(self):
|
||||
buffer = self.read()
|
||||
if buffer:
|
||||
return buffer
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
def tell(self):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
return self.position
|
||||
|
||||
def seek(self, position):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
|
||||
if position < self.position:
|
||||
raise ValueError('Backward seek operation is impossible')
|
||||
elif position < self.position + len(self.chunk):
|
||||
self.chunk = self.chunk[(position - self.position):]
|
||||
self.position = position
|
||||
else:
|
||||
try:
|
||||
current = self.position + len(self.chunk)
|
||||
while True:
|
||||
chunk = self.stream.next()
|
||||
if current + len(chunk) >= position:
|
||||
self.chunk = chunk[(position - current):]
|
||||
self.position = position
|
||||
break
|
||||
current += len(chunk)
|
||||
except StopIteration:
|
||||
self.chunk = None
|
||||
self.position = position
|
||||
|
||||
def read(self, length=1048576):
|
||||
# NOTE(kozhukalov): default lenght = 1048576 is not usual behaviour,
|
||||
# but that is ok for our use case.
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
if self.chunk is None:
|
||||
return None
|
||||
try:
|
||||
while len(self.chunk) < length:
|
||||
self.chunk += self.stream.next()
|
||||
result = self.chunk[:length]
|
||||
self.chunk = self.chunk[length:]
|
||||
except StopIteration:
|
||||
result = self.chunk
|
||||
self.chunk = None
|
||||
self.position += len(result)
|
||||
return result
|
||||
|
||||
|
||||
class TarStream(Target):
|
||||
def __init__(self, stream):
|
||||
self.stream = iter(stream)
|
||||
self.tarobj = None
|
||||
|
||||
def target(self, filename=None):
|
||||
if not self.tarobj:
|
||||
self.tarobj = tarfile.open(
|
||||
fileobj=ForwardFileStream(self.stream), mode='r:')
|
||||
self.tarobj.extractall(path=(filename or tempfile.gettempdir()))
|
||||
|
||||
|
||||
class Chain(object):
|
||||
def __init__(self):
|
||||
self.processors = []
|
||||
|
||||
def append(self, processor):
|
||||
self.processors.append(processor)
|
||||
|
||||
def process(self):
|
||||
def jump(proc, next_proc):
|
||||
# if next_proc is just a string we assume it is a filename
|
||||
# and we save stream into a file
|
||||
if isinstance(next_proc, (str, unicode)):
|
||||
proc.target(next_proc)
|
||||
return LocalFile(next_proc)
|
||||
# if next_proc is not a string we return new instance
|
||||
# initialized with the previous one
|
||||
else:
|
||||
return next_proc(proc)
|
||||
return reduce(jump, self.processors)
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from fuel_agent import errors
|
||||
from fuel_agent.utils import utils
|
||||
|
||||
|
||||
@@ -27,3 +28,32 @@ def make_fs(fs_type, fs_options, fs_label, dev):
|
||||
cmd_line.extend([s for s in opt.split(' ') if s])
|
||||
cmd_line.append(dev)
|
||||
utils.execute(*cmd_line)
|
||||
|
||||
|
||||
def extend_fs(fs_type, fs_dev):
|
||||
if fs_type in ('ext3', 'ext4'):
|
||||
# ext3,4 file system can be mounted
|
||||
# must be checked with e2fsck -f
|
||||
utils.execute('e2fsck', ' -f', fs_dev, check_exit_code=[0])
|
||||
utils.execute('resize2fs', fs_dev, check_exit_code=[0])
|
||||
elif fs_type == 'xfs':
|
||||
# xfs file system must be mounted
|
||||
utils.execute('xfs_growfs', fs_dev, check_exit_code=[0])
|
||||
else:
|
||||
raise errors.FsUtilsError('Unsupported file system type')
|
||||
|
||||
|
||||
def mount_fs(fs_type, fs_dev, fs_mount):
|
||||
utils.execute('mount', '-t', fs_type, fs_dev, fs_mount,
|
||||
check_exit_code=[0])
|
||||
|
||||
|
||||
def mount_bind(chroot, path, path2=None):
|
||||
if not path2:
|
||||
path2 = path
|
||||
utils.execute('mount', '--bind', path, chroot + path2,
|
||||
check_exit_code=[0])
|
||||
|
||||
|
||||
def umount_fs(fs_mount):
|
||||
utils.execute('umount', fs_mount, check_exit_code=[0])
|
||||
|
||||
188
fuel_agent/utils/grub_utils.py
Normal file
188
fuel_agent/utils/grub_utils.py
Normal file
@@ -0,0 +1,188 @@
|
||||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from fuel_agent import errors
|
||||
from fuel_agent.openstack.common import log as logging
|
||||
from fuel_agent.utils import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def guess_grub2_conf(chroot=''):
|
||||
for filename in ('/boot/grub/grub.cfg', '/boot/grub2/grub.cfg'):
|
||||
if os.path.isfile(chroot + filename):
|
||||
return filename
|
||||
|
||||
|
||||
def guess_grub2_default(chroot=''):
|
||||
for filename in ('/etc/default/grub', '/etc/sysconfig/grub'):
|
||||
if os.path.isfile(chroot + filename):
|
||||
return filename
|
||||
|
||||
|
||||
def guess_grub2_mkconfig(chroot=''):
|
||||
for grub_mkconfig in \
|
||||
('/sbin/grub-mkconfig', '/sbin/grub2-mkconfig',
|
||||
'/usr/sbin/grub-mkconfig', '/usr/sbin/grub2-mkconfig'):
|
||||
if os.path.isfile(chroot + grub_mkconfig):
|
||||
return grub_mkconfig
|
||||
|
||||
|
||||
def guess_grub_version(chroot=''):
|
||||
grub_install = guess_grub_install(chroot=chroot)
|
||||
LOG.debug('Trying to run %s -v' % grub_install)
|
||||
result = utils.execute(chroot + grub_install, '-v')
|
||||
version = 1 if result[0].find('0.97') > 0 else 2
|
||||
LOG.debug('Looks like grub version is %s' % version)
|
||||
return version
|
||||
|
||||
|
||||
def guess_grub(chroot=''):
|
||||
for grub in ('/sbin/grub', '/usr/sbin/grub'):
|
||||
LOG.debug('Looking for grub: trying %s' % grub)
|
||||
if os.path.isfile(chroot + grub):
|
||||
LOG.debug('grub found: %s' % grub)
|
||||
return grub
|
||||
raise errors.GrubUtilsError('grub not found')
|
||||
|
||||
|
||||
def guess_grub_install(chroot=''):
|
||||
for grub_install in ('/sbin/grub-install', '/sbin/grub2-install',
|
||||
'/usr/sbin/grub-install', '/usr/sbin/grub2-install'):
|
||||
LOG.debug('Looking for grub-install: trying %s' % grub_install)
|
||||
if os.path.isfile(chroot + grub_install):
|
||||
LOG.debug('grub-install found: %s' % grub_install)
|
||||
return grub_install
|
||||
raise errors.GrubUtilsError('grub-install not found')
|
||||
|
||||
|
||||
def guess_kernel(chroot=''):
|
||||
for filename in sorted(os.listdir(chroot + '/boot'), reverse=True):
|
||||
# We assume kernel name is always starts with vmlinuz.
|
||||
# We use the newest one.
|
||||
if filename.startswith('vmlinuz'):
|
||||
return filename
|
||||
raise errors.GrubUtilsError('Error while trying to find kernel: not found')
|
||||
|
||||
|
||||
def guess_initrd(chroot=''):
|
||||
for filename in sorted(os.listdir(chroot + '/boot'), reverse=True):
|
||||
# We assume initrd starts either with initramfs or initrd.
|
||||
if filename.startswith('initramfs') or \
|
||||
filename.startswith('initrd'):
|
||||
return filename
|
||||
raise errors.GrubUtilsError('Error while trying to find initrd: not found')
|
||||
|
||||
|
||||
def grub1_install(install_devices, boot_device, chroot=''):
|
||||
match = re.search(r'(.+?)(p?)(\d*)$', boot_device)
|
||||
# Checking whether boot device is a partition
|
||||
# !!! It must be a partition not a whole disk. !!!
|
||||
if not match.group(3):
|
||||
raise errors.GrubUtilsError(
|
||||
'Error while installing legacy grub: '
|
||||
'boot device must be a partition')
|
||||
boot_disk = match.group(1)
|
||||
boot_part = str(int(match.group(3)) - 1)
|
||||
|
||||
for install_device in install_devices:
|
||||
grub1_mbr(install_device, boot_disk, boot_part, chroot=chroot)
|
||||
|
||||
|
||||
def grub1_mbr(install_device, boot_disk, boot_part, chroot=''):
|
||||
# The device on which we are going to install
|
||||
# stage1 needs to be mapped as hd0, otherwise system won't be able to boot.
|
||||
batch = 'device (hd0) {0}\n'.format(install_device)
|
||||
# That is much easier to use grub-install, but unfortunately
|
||||
# it is not able to install bootloader on huge disks.
|
||||
# Instead we set drive geometry manually to avoid grub register
|
||||
# overlapping. We set it so as to make grub
|
||||
# thinking that disk size is equal to 1G.
|
||||
# 130 cylinders * (16065 * 512 = 8225280 bytes) = 1G
|
||||
# We also assume that boot partition is in the beginning
|
||||
# of disk between 0 and 1G.
|
||||
batch += 'geometry (hd0) 130 255 63\n'
|
||||
if boot_disk != install_device:
|
||||
batch += 'device (hd1) {0}\n'.format(boot_disk)
|
||||
batch += 'geometry (hd1) 130 255 63\n'
|
||||
batch += 'root (hd1,{0})\n'.format(boot_part)
|
||||
else:
|
||||
batch += 'root (hd0,{0})\n'.format(boot_part)
|
||||
batch += 'setup (hd0)\n'
|
||||
batch += 'quit\n'
|
||||
|
||||
with open(chroot + '/tmp/grub.batch', 'wb') as f:
|
||||
LOG.debug('Grub batch content: \n%s' % batch)
|
||||
f.write(batch)
|
||||
|
||||
script = 'cat /tmp/grub.batch | {0} --no-floppy --batch'.format(
|
||||
guess_grub(chroot=chroot))
|
||||
with open(chroot + '/tmp/grub.sh', 'wb') as f:
|
||||
LOG.debug('Grub script content: \n%s' % script)
|
||||
f.write(script)
|
||||
|
||||
os.chmod(chroot + '/tmp/grub.sh', 0o755)
|
||||
cmd = ['/tmp/grub.sh']
|
||||
if chroot:
|
||||
cmd[:0] = ['chroot', chroot]
|
||||
utils.execute(*cmd, run_as_root=True, check_exit_code=[0])
|
||||
|
||||
|
||||
def grub1_cfg(kernel=None, initrd=None,
|
||||
kernel_params='', chroot=''):
|
||||
|
||||
if not kernel:
|
||||
kernel = guess_kernel(chroot=chroot)
|
||||
if not initrd:
|
||||
initrd = guess_initrd(chroot=chroot)
|
||||
|
||||
config = """
|
||||
default=0
|
||||
timeout=5
|
||||
title Default ({kernel})
|
||||
kernel /{kernel} {kernel_params}
|
||||
initrd /{initrd}
|
||||
""".format(kernel=kernel, initrd=initrd,
|
||||
kernel_params=kernel_params)
|
||||
with open(chroot + '/boot/grub/grub.conf', 'wb') as f:
|
||||
f.write(config)
|
||||
|
||||
|
||||
def grub2_install(install_devices, chroot=''):
|
||||
grub_install = guess_grub_install()
|
||||
for install_device in install_devices:
|
||||
cmd = [grub_install, install_device]
|
||||
if chroot:
|
||||
cmd[:0] = ['chroot', chroot]
|
||||
utils.execute(*cmd, run_as_root=True, check_exit_code=[0])
|
||||
|
||||
|
||||
def grub2_cfg(kernel_params='', chroot=''):
|
||||
grub_defaults = chroot + guess_grub2_default(chroot=chroot)
|
||||
regex = re.compile(r'^.*GRUB_CMDLINE_LINUX.*')
|
||||
new_content = ''
|
||||
with open(grub_defaults) as f:
|
||||
for line in f:
|
||||
new_content += regex.sub(
|
||||
'GRUB_CMDLINE_LINUX="{kernel_params}"'.
|
||||
format(kernel_params=kernel_params), line)
|
||||
with open(grub_defaults, 'wb') as f:
|
||||
f.write(new_content)
|
||||
cmd = [guess_grub2_mkconfig(), '-o', guess_grub2_conf()]
|
||||
if chroot:
|
||||
cmd[:0] = ['chroot', chroot]
|
||||
utils.execute(*cmd, run_as_root=True)
|
||||
@@ -1,91 +0,0 @@
|
||||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import abc
|
||||
import requests
|
||||
import zlib
|
||||
|
||||
|
||||
class Target(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
raise StopIteration()
|
||||
|
||||
def target(self, filename='/dev/null'):
|
||||
with open(filename, 'wb') as f:
|
||||
for chunk in self:
|
||||
f.write(chunk)
|
||||
f.flush()
|
||||
|
||||
|
||||
class LocalFile(Target):
|
||||
def __init__(self, filename):
|
||||
self.filename = str(filename)
|
||||
self.fileobj = None
|
||||
|
||||
def next(self):
|
||||
if not self.fileobj:
|
||||
self.fileobj = open(self.filename, 'rb')
|
||||
buffer = self.fileobj.read(1048576)
|
||||
if buffer:
|
||||
return buffer
|
||||
else:
|
||||
self.fileobj.close()
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
class HttpUrl(Target):
|
||||
def __init__(self, url):
|
||||
self.url = str(url)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(requests.get(self.url, stream=True).iter_content(1048576))
|
||||
|
||||
|
||||
class GunzipStream(Target):
|
||||
def __init__(self, stream):
|
||||
self.stream = iter(stream)
|
||||
#NOTE(agordeev): toggle automatic header detection on
|
||||
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 32)
|
||||
|
||||
def next(self):
|
||||
try:
|
||||
return self.decompressor.decompress(self.stream.next())
|
||||
except StopIteration:
|
||||
raise
|
||||
|
||||
|
||||
class Chain(object):
|
||||
def __init__(self):
|
||||
self.processors = []
|
||||
|
||||
def append(self, processor):
|
||||
self.processors.append(processor)
|
||||
|
||||
def process(self):
|
||||
def jump(proc, next_proc):
|
||||
# if next_proc is just a string we assume it is a filename
|
||||
# and we save stream into a file
|
||||
if isinstance(next_proc, (str, unicode)):
|
||||
proc.target(next_proc)
|
||||
return LocalFile(next_proc)
|
||||
# if next_proc is not a string we return new instance
|
||||
# initialized with the previous one
|
||||
else:
|
||||
return next_proc(proc)
|
||||
return reduce(jump, self.processors)
|
||||
Reference in New Issue
Block a user