From be717da52b717b9a59f947e3ade2be5423d401a4 Mon Sep 17 00:00:00 2001 From: "Grzegorz Grasza (xek)" Date: Fri, 19 Jan 2018 13:29:30 +0100 Subject: [PATCH 07/11] System installation implementation Co-Authored-By: Grzegorz Grasza --- ironic_lib/system_installer/systemsetup.py | 93 ++++++++++++++++++++++++++++++ ironic_lib/tests/test_systemsetup.py | 90 +++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 ironic_lib/tests/test_systemsetup.py diff --git a/ironic_lib/system_installer/systemsetup.py b/ironic_lib/system_installer/systemsetup.py index e03c5b4..fa303e6 100644 --- a/ironic_lib/system_installer/systemsetup.py +++ b/ironic_lib/system_installer/systemsetup.py @@ -13,8 +13,101 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + +from ironic_lib import utils +from oslo_log import log + +from ironic_lib.system_installer import exceptions from ironic_lib.system_installer.filesystemsetup import FilesystemSetup +LOG = log.getLogger() + class SystemSetup(FilesystemSetup): """Basic disk formatting implementation""" + + def validate_env(self): + if not self._cmd_exists('guestmount'): + raise exceptions.EnvError('guestmount command not available') + + def setup_disks(self, devices, image_path): + fstab = [] + for disk_name, disk in sorted( + self.conf.items(), key=lambda x: x[1].get('mountpoint', 'z')): + if 'mountpoint' in disk and not disk.get('preserve', False): + mountpoint = disk['mountpoint'] + device = devices.get(disk_name, disk_name) + try: + os.makedirs('/next' + mountpoint) + except OSError as e: + if e.errno != os.errno.EEXIST: + raise + out, err = utils.execute('mount', device, '/next' + mountpoint) + LOG.debug("mount stdout: %s", out) + LOG.debug("mount stderr: %s", err) + fstab.append(self._generate_fstab_line( + device, disk.get('mountpoint'), disk.get('fstype', 'xfs'), + disk.get('mountopts'))) + self._copy_files(image_path) + for m in ['/dev', '/dev/pts', '/proc', '/sys', '/run']: + utils.execute('mount', '-B', m, '/next' + m) + self._write_fstab(fstab) + partition_label = self.get_boot_partition() + return self._install_bootloader(devices.get(partition_label, + partition_label)) + + def _generate_fstab_line(self, device, mountpoint, fstype, mountopts): + if not mountpoint and fstype != 'swap': + return '' + uuid = self._get_uuid(device) + opts = 'defaults' + dump = '0' + pas = '2' + if fstype == 'vfat': + opts = 'umask=0077' + if mountpoint in ['/', '/boot']: + pas = '1' + elif fstype == 'swap': + opts = 'sw' + pas = '0' + mountpoint = 'none' + return 'UUID=' + '\t'.join( + [uuid, mountpoint, fstype, mountopts or opts, dump, pas]) + '\n' + + def _copy_files(self, image_path): + try: + os.mkdir('/image') + except OSError as e: + if e.errno != os.errno.EEXIST: + raise + utils.execute('guestmount', '-a', image_path, '-i', '--ro', '/image') + utils.execute('cp', '-a', '/image/.', '/next/') + utils.execute('umount', '-R', '/image') + + def _write_fstab(self, fstab_list): + if ''.join(fstab_list).strip(): + with open('/next/etc/fstab', 'w') as fstab: + fstab.write('# generated by disk_partitioner.py\n') + fstab.writelines(fstab_list) + + def _install_bootloader(self, device): + device = device.rstrip('1234567890') # install to MBR + if os.path.isfile('/next/usr/sbin/grub-install'): + grub = 'grub' + elif os.path.isfile('/next/usr/sbin/grub2-install'): + grub = 'grub2' + else: + raise exceptions.EnvError('grub command not available on image') + utils.execute('chroot /next /usr/sbin/{}-mkconfig' + ' -o /boot/{}/grub.cfg'.format(grub, grub), shell=True) + utils.execute('chroot /next /usr/sbin/{}-install --recheck {}'.format( + grub, device), shell=True) + utils.execute('sync') + utils.execute('umount', '-R', '/next') + return device + + @staticmethod + def _get_uuid(device): + out, _ = utils.execute('blkid -o value -s UUID ' + device, shell=True) + return out.strip() diff --git a/ironic_lib/tests/test_systemsetup.py b/ironic_lib/tests/test_systemsetup.py new file mode 100644 index 0000000..0e127f5 --- /dev/null +++ b/ironic_lib/tests/test_systemsetup.py @@ -0,0 +1,90 @@ +# Copyright (c) 2018 Intel Corporation +# +# 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 unittest + +import mock + +from ironic_lib.system_installer import exceptions +from ironic_lib.system_installer import systemsetup + + +class SystemSetupTestCase(unittest.TestCase): + + @mock.patch.object(systemsetup.SystemSetup, '_cmd_exists') + def test_validate_env_fail(self, cmd): + system_setup = systemsetup.SystemSetup({'filesystems': {}}) + cmd.return_value = False + with self.assertRaises(exceptions.EnvError): + system_setup.validate_env() + + @mock.patch('ironic_lib.system_installer.systemsetup.open') + @mock.patch('ironic_lib.utils.execute') + @mock.patch.object(systemsetup.SystemSetup, '_get_uuid') + @mock.patch('os.path.isfile') + @mock.patch('os.mkdir') + def test_setup_disks(self, mkdir, is_file, uuid, exe_mock, mock_open): + uuid.return_value = 'test_uuid' + exe_mock.return_value = 'grub-install (GRUB) 2.02~beta3-4ubuntu2.2', 0 + + system_setup = systemsetup.SystemSetup( + {'filesystems': {'boot': {'mountpoint': '/boot', + 'mountopts': 'foo', + 'fstype': 'ext2'}, + 'root': {'mountpoint': '/', + 'fstype': 'ext4'}, + 'home': {'mountpoint': '/home'}}}) + system_setup.setup_disks({}, 'test_image') + exe_mock.assert_any_call('mount', 'boot', '/next/boot') + file_handle = mock_open.return_value.__enter__.return_value + file_handle.writelines.assert_called_with( + ['UUID=test_uuid\t/\text4\tdefaults\t0\t1\n', + 'UUID=test_uuid\t/boot\text2\tfoo\t0\t1\n', + 'UUID=test_uuid\t/home\txfs\tdefaults\t0\t2\n']) + + @mock.patch.object(systemsetup.SystemSetup, '_install_bootloader') + @mock.patch.object(systemsetup.SystemSetup, '_copy_files') + @mock.patch('ironic_lib.system_installer.systemsetup.open') + @mock.patch('ironic_lib.utils.execute') + @mock.patch.object(systemsetup.SystemSetup, '_get_uuid') + @mock.patch('os.path.isfile') + @mock.patch('os.mkdir') + def test_setup_disks_preserve_true(self, mkdir, is_file, + uuid, exe_mock, mock_open, + copy, bootloader): + uuid.return_value = 'test_uuid' + exe_mock.return_value = 'grub-install (GRUB) 2.02~beta3-4ubuntu2.2', 0 + + system_setup = systemsetup.SystemSetup( + {'filesystems': {'boot': {'mountpoint': '/boot', + 'mountopts': 'foo', + 'fstype': 'ext2'}, + 'home': {'mountpoint': '/home', + 'preserve': 1}}}) + system_setup.setup_disks({}, 'test_image') + exe_mock.assert_any_call('mount', 'boot', '/next/boot') + exe_mock.assert_any_call('mount', '-B', '/dev', '/next' + '/dev') + exe_mock.assert_any_call('mount', '-B', '/dev/pts', + '/next' + '/dev/pts') + exe_mock.assert_any_call('mount', '-B', '/proc', '/next' + '/proc') + exe_mock.assert_any_call('mount', '-B', '/sys', '/next' + '/sys') + exe_mock.assert_any_call('mount', '-B', '/run', '/next' + '/run') + + assert exe_mock.call_count == 6 + + file_handle = mock_open.return_value.__enter__.return_value + file_handle.writelines.assert_called_with( + ['UUID=test_uuid\t/boot\text2\tfoo\t0\t1\n', + 'UUID=test_uuid\t/home\txfs\tdefaults\t0\t2\n']) -- 2.14.1