change configdrive format to ConfigDrive version 2

We put configdrive with is9660 filesystem to a
partition on a hard disk. New hard disks may have
4K sectors, but blocksize of iso9660 fs is 2K so
it will not work.

To fix this bug we should use another filesystem (ext2)
and another config drive format (files, directory
structure), because NoCloud format, which is currently
used support only vfat and iso9660 filesystems.

Conflicts:

	bareon/drivers/deploy/nailgun.py
	fuel_agent/tests/test_manager.py

Change-Id: Ia0f244f19bab3dfaceef8a092ad03667675a5557
Closes-Bug: #1544818
This commit is contained in:
Dmitry Guryanov 2016-04-06 21:01:16 +03:00 committed by Alexander Gordeev
parent 10bb3b646e
commit 0e86a2f0a5
5 changed files with 184 additions and 68 deletions

View File

@ -14,12 +14,16 @@
import os
import shutil
import tempfile
from oslo_config import cfg
import six
from bareon.actions import base
from bareon import errors
from bareon.openstack.common import log as logging
from bareon.utils import fs as fu
from bareon.utils import utils
opts = [
@ -54,7 +58,7 @@ LOG = logging.getLogger(__name__)
class ConfigDriveAction(base.BaseAction):
"""ConfigDriveAction
creates No cloud datasource image for cloud-init
creates ConfigDrive datasource image for cloud-init
"""
def validate(self):
@ -64,43 +68,78 @@ class ConfigDriveAction(base.BaseAction):
def execute(self):
self.do_configdrive()
def _make_configdrive_image(self, src_files):
bs = 4096
configdrive_device = self.driver.partition_scheme.configdrive_device()
size = utils.execute('blockdev', '--getsize64', configdrive_device)[0]
size = int(size.strip())
utils.execute('truncate', '--size=%d' % size, CONF.config_drive_path)
fu.make_fs(
fs_type='ext2',
fs_options=' -b %d -F ' % bs,
fs_label='config-2',
dev=six.text_type(CONF.config_drive_path))
mount_point = tempfile.mkdtemp(dir=CONF.tmp_path)
try:
fu.mount_fs('ext2', CONF.config_drive_path, mount_point)
for file_path in src_files:
name = os.path.basename(file_path)
if os.path.isdir(file_path):
shutil.copytree(file_path, os.path.join(mount_point, name))
else:
shutil.copy2(file_path, mount_point)
except Exception as exc:
LOG.error('Error copying files to configdrive: %s', exc)
raise
finally:
fu.umount_fs(mount_point)
os.rmdir(mount_point)
def _prepare_configdrive_files(self):
# see data sources part of cloud-init documentation
# for directory structure
cd_root = tempfile.mkdtemp(dir=CONF.tmp_path)
cd_latest = os.path.join(cd_root, 'openstack', 'latest')
md_output_path = os.path.join(cd_latest, 'meta_data.json')
ud_output_path = os.path.join(cd_latest, 'user_data')
os.makedirs(cd_latest)
cc_output_path = os.path.join(CONF.tmp_path, 'cloud_config.txt')
bh_output_path = os.path.join(CONF.tmp_path, 'boothook.txt')
tmpl_dir = CONF.nc_template_path
utils.render_and_save(
tmpl_dir,
self.driver.configdrive_scheme.template_names('cloud_config'),
self.driver.configdrive_scheme.template_data(),
cc_output_path
)
utils.render_and_save(
tmpl_dir,
self.driver.configdrive_scheme.template_names('boothook'),
self.driver.configdrive_scheme.template_data(),
bh_output_path
)
utils.render_and_save(
tmpl_dir,
self.driver.configdrive_scheme.template_names('meta_data_json'),
self.driver.configdrive_scheme.template_data(),
md_output_path
)
utils.execute(
'write-mime-multipart', '--output=%s' % ud_output_path,
'%s:text/cloud-boothook' % bh_output_path,
'%s:text/cloud-config' % cc_output_path)
return [os.path.join(cd_root, 'openstack')]
def do_configdrive(self):
LOG.debug('--- Creating configdrive (do_configdrive) ---')
if CONF.prepare_configdrive:
cc_output_path = os.path.join(CONF.tmp_path, 'cloud_config.txt')
bh_output_path = os.path.join(CONF.tmp_path, 'boothook.txt')
# NOTE:file should be strictly named as 'user-data'
# the same is for meta-data as well
ud_output_path = os.path.join(CONF.tmp_path, 'user-data')
md_output_path = os.path.join(CONF.tmp_path, 'meta-data')
tmpl_dir = CONF.nc_template_path
utils.render_and_save(
tmpl_dir,
self.driver.configdrive_scheme.template_names('cloud_config'),
self.driver.configdrive_scheme.template_data(),
cc_output_path
)
utils.render_and_save(
tmpl_dir,
self.driver.configdrive_scheme.template_names('boothook'),
self.driver.configdrive_scheme.template_data(),
bh_output_path
)
utils.render_and_save(
tmpl_dir,
self.driver.configdrive_scheme.template_names('meta_data'),
self.driver.configdrive_scheme.template_data(),
md_output_path
)
utils.execute(
'write-mime-multipart', '--output=%s' % ud_output_path,
'%s:text/cloud-boothook' % bh_output_path,
'%s:text/cloud-config' % cc_output_path)
utils.execute('genisoimage', '-output', CONF.config_drive_path,
'-volid', 'cidata', '-joliet', '-rock',
ud_output_path, md_output_path)
files = self._prepare_configdrive_files()
self._make_configdrive_image(files)
if CONF.prepare_configdrive or os.path.isfile(CONF.config_drive_path):
self._add_configdrive_image()
@ -114,10 +153,11 @@ class ConfigDriveAction(base.BaseAction):
'configdrive device not found')
size = os.path.getsize(CONF.config_drive_path)
md5 = utils.calculate_md5(CONF.config_drive_path, size)
fs_type = fu.get_fs_type(CONF.config_drive_path)
self.driver.image_scheme.add_image(
uri='file://%s' % CONF.config_drive_path,
target_device=configdrive_device,
format='iso9660',
format=fs_type,
container='raw',
size=size,
md5=md5,

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import six
import unittest2
@ -40,16 +41,30 @@ class TestConfigDriveAction(unittest2.TestCase):
profile='pro_fi-le')
self.drv.configdrive_scheme.template_data = mock.Mock()
self.drv.image_scheme = objects.ImageScheme()
self.drv.partition_scheme.configdrive_device.return_value = '/dev/sda7'
def test_do_configdrive(self):
with mock.patch.multiple(self.action,
_prepare_configdrive_files=mock.DEFAULT,
_make_configdrive_image=mock.DEFAULT,
_add_configdrive_image=mock.DEFAULT) as mocks:
mocks['_prepare_configdrive_files'].return_value = 'x'
self.action.execute()
mocks['_prepare_configdrive_files'].assert_called_once_with()
mocks['_make_configdrive_image'].assert_called_once_with('x')
mocks['_add_configdrive_image'].assert_called_once_with()
@mock.patch.object(configdrive, 'tempfile', autospec=True)
@mock.patch.object(configdrive, 'os', autospec=True)
@mock.patch.object(configdrive, 'utils', autospec=True)
def test_do_configdrive(self, mock_utils, mock_os):
self.drv.partition_scheme.configdrive_device.return_value = '/dev/sda7'
mock_os.path.getsize.return_value = 123
mock_os.path.join = lambda x, y: '%s/%s' % (x, y)
mock_utils.calculate_md5.return_value = 'fakemd5'
self.assertEqual(0, len(self.drv.image_scheme.images))
self.action.execute()
def test_prepare_configdrive_files(self, mock_utils, mock_os, mock_temp):
mock_os.path.join = os.path.join
mock_temp.mkdtemp.return_value = '/tmp/qwe'
ret = self.action._prepare_configdrive_files()
self.assertEqual(ret, ['/tmp/qwe/openstack'])
mock_temp.mkdtemp.assert_called_once_with(dir=CONF.tmp_path)
mock_os.makedirs.assert_called_once_with('/tmp/qwe/openstack/latest')
mock_u_ras_expected_calls = [
mock.call(CONF.nc_template_path,
['cloud_config_pro_fi-le.jinja2',
@ -64,41 +79,83 @@ class TestConfigDriveAction(unittest2.TestCase):
'boothook.jinja2'],
mock.ANY, '%s/%s' % (CONF.tmp_path, 'boothook.txt')),
mock.call(CONF.nc_template_path,
['meta_data_pro_fi-le.jinja2',
'meta_data_pro.jinja2',
'meta_data_pro_fi.jinja2',
'meta_data.jinja2'],
mock.ANY, '%s/%s' % (CONF.tmp_path, 'meta-data'))]
['meta_data_json_pro_fi-le.jinja2',
'meta_data_json_pro.jinja2',
'meta_data_json_pro_fi.jinja2',
'meta_data_json.jinja2'],
mock.ANY, '/tmp/qwe/openstack/latest/meta_data.json')]
self.assertEqual(mock_u_ras_expected_calls,
mock_utils.render_and_save.call_args_list)
mock_u_e_expected_calls = [
mock.call('write-mime-multipart',
'--output=%s' % ('%s/%s' % (CONF.tmp_path, 'user-data')),
'%s:text/cloud-boothook' % ('%s/%s' % (CONF.tmp_path,
'boothook.txt')),
'%s:text/cloud-config' % ('%s/%s' % (CONF.tmp_path,
'cloud_config.txt'))
),
mock.call('genisoimage', '-output', CONF.config_drive_path,
'-volid', 'cidata', '-joliet', '-rock',
'%s/%s' % (CONF.tmp_path, 'user-data'),
'%s/%s' % (CONF.tmp_path, 'meta-data'))]
self.assertEqual(mock_u_e_expected_calls,
mock_utils.execute.call_args_list)
mock_utils.execute.assert_called_once_with(
'write-mime-multipart',
'--output=/tmp/qwe/openstack/latest/user_data',
'%s/%s:text/cloud-boothook' % (CONF.tmp_path, 'boothook.txt'),
'%s/%s:text/cloud-config' % (CONF.tmp_path, 'cloud_config.txt'))
@mock.patch.object(configdrive, 'tempfile', autospec=True)
@mock.patch.object(configdrive, 'shutil', autospec=True)
@mock.patch.object(configdrive, 'fu', autospec=True)
@mock.patch.object(configdrive, 'os', autospec=True)
@mock.patch.object(configdrive, 'utils', autospec=True)
def test_make_configdrive_image(self, mock_utils, mock_os, mock_fu,
mock_shutil, mock_temp):
mock_utils.execute.side_effect = [(' 795648', ''), None]
mock_os.path.isdir.side_effect = [True, False]
mock_os.path.join = os.path.join
mock_os.path.basename = os.path.basename
mock_temp.mkdtemp.return_value = '/tmp/mount_point'
self.action._make_configdrive_image(['/tmp/openstack',
'/tmp/somefile'])
mock_u_e_calls = [
mock.call('blockdev', '--getsize64', '/dev/sda7'),
mock.call('truncate', '--size=795648', CONF.config_drive_path)]
self.assertEqual(mock_u_e_calls, mock_utils.execute.call_args_list,
str(mock_utils.execute.call_args_list))
mock_fu.make_fs.assert_called_with(fs_type='ext2',
fs_options=' -b 4096 -F ',
fs_label='config-2',
dev=CONF.config_drive_path)
mock_fu.mount_fs.assert_called_with('ext2',
CONF.config_drive_path,
'/tmp/mount_point')
mock_fu.umount_fs.assert_called_with('/tmp/mount_point')
mock_os.rmdir.assert_called_with('/tmp/mount_point')
mock_shutil.copy2.assert_called_with('/tmp/somefile',
'/tmp/mount_point')
mock_shutil.copytree.assert_called_with('/tmp/openstack',
'/tmp/mount_point/openstack')
@mock.patch.object(configdrive, 'fu', autospec=True)
@mock.patch.object(configdrive, 'os', autospec=True)
@mock.patch.object(configdrive, 'utils', autospec=True)
def test_add_configdrive_image(self, mock_utils, mock_os, mock_fu):
mock_fu.get_fs_type.return_value = 'ext999'
mock_utils.calculate_md5.return_value = 'fakemd5'
mock_os.path.getsize.return_value = 123
self.action._add_configdrive_image()
self.assertEqual(1, len(self.drv.image_scheme.images))
cf_drv_img = self.drv.image_scheme.images[-1]
cf_drv_img = self.drv.image_scheme.images[0]
self.assertEqual('file://%s' % CONF.config_drive_path, cf_drv_img.uri)
self.assertEqual('/dev/sda7',
self.drv.partition_scheme.configdrive_device())
self.assertEqual('iso9660', cf_drv_img.format)
self.assertEqual('/dev/sda7', cf_drv_img.target_device)
self.assertEqual('ext999', cf_drv_img.format)
self.assertEqual('raw', cf_drv_img.container)
self.assertEqual('fakemd5', cf_drv_img.md5)
self.assertEqual(123, cf_drv_img.size)
@mock.patch.object(configdrive, 'os', autospec=True)
@mock.patch.object(configdrive, 'utils', autospec=True)
def test_do_configdrive_no_configdrive_device(self, mock_utils, mock_os):
def test_add_configdrive_image_no_configdrive_device(self, mock_utils,
mock_os):
self.drv.partition_scheme.configdrive_device.return_value = None
mock_utils.calculate_md5.return_value = 'fakemd5'
mock_os.path.getsize.return_value = 123
self.assertRaises(errors.WrongPartitionSchemeError,
self.action.execute)
self.action._add_configdrive_image)

View File

@ -161,6 +161,15 @@ class TestFSUtils(unittest2.TestCase):
self.assertEqual(fu.format_fs_label(long_label),
template.format(long_label_trimmed))
def test_get_fs_type(self, mock_exec):
output = "megafs\n"
mock_exec.return_value = (output, '')
ret = fu.get_fs_type('/dev/sda4')
mock_exec.assert_called_once_with('blkid', '-o', 'value',
'-s', 'TYPE', '-c', '/dev/null',
'/dev/sda4')
self.assertEqual(ret, 'megafs')
class TestFSRetry(unittest2.TestCase):

View File

@ -128,3 +128,9 @@ def umount_fs(fs_mount, try_lazy_umount=False):
utils.execute('umount', '-l', fs_mount, check_exit_code=[0])
else:
raise
def get_fs_type(device):
output = utils.execute('blkid', '-o', 'value', '-s', 'TYPE',
'-c', '/dev/null', device)[0]
return output.strip()

View File

@ -0,0 +1,4 @@
{
"hostname": "{{ common.hostname }}",
"uuid": "some-unused-id"
}