diff --git a/ironic-lib/0007-System-installation-implementation.patch b/ironic-lib/0007-System-installation-implementation.patch new file mode 100644 index 0000000..9ac9c26 --- /dev/null +++ b/ironic-lib/0007-System-installation-implementation.patch @@ -0,0 +1,217 @@ +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 +