def test_read_with_skipped_entries(self, mock_os_path_exists):
        def skip_device(device):
            if '/dev/mynode' in device:
                return False
            return True

        mock_os_path_exists.side_effect = skip_device
        fstab = Fstab()
        with self._caplog.at_level(logging.WARNING):
            fstab.read('../data/fstab')
            assert 'Device path /dev/mynode not found and skipped' in \
                self._caplog.text
        assert fstab.get_devices() == [
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/',
                device='/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                options='acl,user_xattr'),
            self.fstab.fstab_entry_type(fstype='vfat',
                                        mountpoint='/boot/efi',
                                        device='/dev/disk/by-uuid/FCF7-B051',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home',
                                        device='/dev/disk/by-label/foo',
                                        options='defaults'),
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/bar',
                device='/dev/disk/by-partuuid/3c8bd108-01',
                options='defaults')
        ]
Beispiel #2
0
    def test_main_kexec_reboot(self, mock_Fstab, mock_Command_run,
                               mock_logger_setup,
                               mock_get_migration_config_file,
                               mock_os_path_exists):
        def skip_device(device):
            if '/dev/mynode' in device:
                return False
            return True

        mock_os_path_exists.side_effect = skip_device
        fstab = Fstab()
        fstab_mock = Mock()
        fstab_mock.read.return_value = fstab.read('../data/system-root.fstab')
        fstab_mock.get_devices.return_value = fstab.get_devices()
        mock_Fstab.return_value = fstab_mock
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        main()
        assert mock_Command_run.call_args_list == [
            call(['systemctl', 'status', '-l', '--all'], raise_on_error=False),
            call(['systemctl', 'stop', 'suse-migration-console-log'],
                 raise_on_error=False),
            call(['umount', '--lazy', '/system-root/home'],
                 raise_on_error=False),
            call(['umount', '--lazy', '/system-root/boot/efi'],
                 raise_on_error=False),
            call(['umount', '--lazy', '/system-root/'], raise_on_error=False),
            call(['systemctl', 'kexec'])
        ]
Beispiel #3
0
 def test_main_force_reboot(self, mock_Fstab, mock_Command_run, mock_info,
                            mock_warning, mock_get_migration_config_file):
     fstab = Fstab()
     fstab_mock = Mock()
     fstab_mock.read.return_value = fstab.read('../data/system-root.fstab')
     fstab_mock.get_devices.return_value = fstab.get_devices()
     mock_Fstab.return_value = fstab_mock
     mock_Command_run.side_effect = [
         MagicMock(),
         MagicMock(),
         MagicMock(),
         MagicMock(), Exception, None
     ]
     mock_get_migration_config_file.return_value = \
         '../data/migration-config.yml'
     main()
     assert mock_info.called
     assert mock_Command_run.call_args_list == [
         call(['systemctl', 'status', '-l', '--all'], raise_on_error=False),
         call(['umount', '--lazy', '/system-root/home'],
              raise_on_error=False),
         call(['umount', '--lazy', '/system-root/boot/efi'],
              raise_on_error=False),
         call(['umount', '--lazy', '/system-root/'], raise_on_error=False),
         call(['systemctl', 'reboot']),
         call(['systemctl', '--force', 'reboot'])
     ]
     mock_warning.assert_called_once_with('Reboot system: [Force Reboot]')
    def test_mount_system_raises(self, mock_Command_run, mock_logger_setup,
                                 mock_os_path_exists):
        def skip_device(device):
            if '/dev/mynode' in device:
                return False
            return True

        def command_calls(command):
            # mock error on mounting home
            if '/system-root/home' in command:
                raise Exception

        mock_os_path_exists.side_effect = skip_device
        mock_Command_run.side_effect = command_calls
        with raises(DistMigrationSystemMountException):
            fstab = Fstab()
            fstab.read('../data/fstab')
            mount_system(Defaults.get_system_root_path(), fstab)
        assert mock_Command_run.call_args_list == [
            call([
                'mount', '-o', 'acl,user_xattr',
                '/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                '/system-root/'
            ]),
            call([
                'mount', '-o', 'defaults', '/dev/disk/by-partuuid/3c8bd108-01',
                '/system-root/bar'
            ]),
            call([
                'mount', '-o', 'defaults', '/dev/disk/by-label/foo',
                '/system-root/home'
            ])
        ]
Beispiel #5
0
def read_system_fstab(root_path):
    log = logging.getLogger(Defaults.get_migration_log_name())
    log.info('Reading fstab from associated disks')
    lsblk_call = Command.run(['lsblk', '-p', '-n', '-r', '-o', 'NAME,TYPE'])
    considered_block_types = ['part', 'raid', 'lvm']
    considered_block_devices = []
    lvm_managed_block_device_found = False
    for entry in lsblk_call.output.split(os.linesep):
        block_record = entry.split()
        if len(block_record) >= 2:
            block_type = block_record[1]
            if block_type in considered_block_types:
                if block_type == 'lvm':
                    lvm_managed_block_device_found = True
                considered_block_devices.append(block_record[0])

    if lvm_managed_block_device_found:
        log.info('LVM managed block device(s) found, activating volume groups')
        Command.run(['vgchange', '-a', 'y'])

    for block_device in considered_block_devices:
        try:
            Command.run(['mount', block_device, root_path],
                        raise_on_error=False)
            fstab_file = os.sep.join([root_path, 'etc', 'fstab'])
            if os.path.exists(fstab_file):
                fstab = Fstab()
                fstab.read(fstab_file)
                return (fstab, lsblk_call.output)
        finally:
            log.info('Umount {0}'.format(root_path))
            Command.run(['umount', root_path], raise_on_error=False)
    return (None, lsblk_call.output)
Beispiel #6
0
 def test_read_with_skipped_entries(self, mock_os_path_exists,
                                    mock_log_warning):
     mock_os_path_exists.return_value = False
     fstab = Fstab()
     fstab.read('../data/fstab')
     mock_log_warning.assert_called_once_with(
         'Device path /dev/mynode not found and skipped')
     assert fstab.get_devices() == [
         self.fstab.fstab_entry_type(
             fstype='ext4',
             mountpoint='/',
             device='/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
             options='acl,user_xattr'),
         self.fstab.fstab_entry_type(fstype='vfat',
                                     mountpoint='/boot/efi',
                                     device='/dev/disk/by-uuid/FCF7-B051',
                                     options='defaults'),
         self.fstab.fstab_entry_type(fstype='ext4',
                                     mountpoint='/home',
                                     device='/dev/disk/by-label/foo',
                                     options='defaults'),
         self.fstab.fstab_entry_type(
             fstype='ext4',
             mountpoint='/bar',
             device='/dev/disk/by-partuuid/3c8bd108-01',
             options='defaults')
     ]
    def test_main_force_reboot(
        self, mock_Fstab, mock_get_migration_config_file,
        mock_logger_setup, mock_Command_run, mock_os_path_exists
    ):
        def skip_device(device):
            if '/dev/mynode' in device:
                return False
            return True

        mock_os_path_exists.side_effect = skip_device
        fstab = Fstab()
        fstab_mock = Mock()
        fstab_mock.read.return_value = fstab.read('../data/system-root.fstab')
        fstab_mock.get_devices.return_value = fstab.get_devices()
        mock_Fstab.return_value = fstab_mock
        mock_Command_run.side_effect = [
            MagicMock(),
            MagicMock(),
            MagicMock(),
            MagicMock(),
            MagicMock(),
            Exception,
            None
        ]
        mock_get_migration_config_file.return_value = \
            '../data/migration-config-hard-reboot.yml'
        with self._caplog.at_level(logging.WARNING):
            main()
            assert mock_Command_run.call_args_list == [
                call(
                    ['systemctl', 'status', '-l', '--all'],
                    raise_on_error=False
                ),
                call(
                    ['systemctl', 'stop', 'suse-migration-console-log'],
                    raise_on_error=False
                ),
                call(
                    ['umount', '--lazy', '/system-root/boot/efi'],
                    raise_on_error=False
                ),
                call(
                    ['umount', '--lazy', '/system-root/home'],
                    raise_on_error=False
                ),
                call(
                    ['umount', '--lazy', '/system-root/'],
                    raise_on_error=False
                ),
                call(['systemctl', 'reboot']),
                call(['systemctl', '--force', 'reboot'])
            ]
            assert 'Reboot system: [Force Reboot]' in self._caplog.text
Beispiel #8
0
def main():
    """
    DistMigration reboot with new kernel

    After the migration process is finished, the system reboots
    unless the debug option is set.

    Before reboot a reverse umount of the filesystems that got mounted
    by the mount_system service is performed and thus releases the
    upgraded system from the migration host. If for whatever reason
    a filesystem is busy and can't be umounted, this condition is not
    handled as an error. The reason is that the cleanup should not
    prevent us from continuing with the reboot process. The risk on
    reboot of the migration host with a potential active mount
    is something we accept
    """
    Logger.setup()
    log = logging.getLogger(Defaults.get_migration_log_name())
    try:
        log.info('Systemctl Status Information: {0}{1}'.format(
            os.linesep,
            Command.run(['systemctl', 'status', '-l', '--all'],
                        raise_on_error=False).output))
        # stop console dialog log. The service holds a busy state
        # on system-root and stands in our way in case of debug
        # mode because it grabs the master console in/output
        Command.run(['systemctl', 'stop', 'suse-migration-console-log'],
                    raise_on_error=False)
        if MigrationConfig().is_debug_requested():
            log.info('Reboot skipped due to debug flag set')
        else:
            log.info('Umounting system')
            system_mount = Fstab()
            system_mount.read(Defaults.get_system_mount_info_file())
            for mount in reversed(system_mount.get_devices()):
                log.info('Umounting {0}: {1}'.format(
                    mount.mountpoint,
                    Command.run(['umount', '--lazy', mount.mountpoint],
                                raise_on_error=False)))
            if not MigrationConfig().is_soft_reboot_requested():
                restart_system = 'reboot'
            else:
                restart_system = 'kexec'
            log.info('Reboot system: {0}{1}'.format(
                os.linesep, Command.run(['systemctl', restart_system])))
    except Exception:
        # Uhh, we don't want to be here, but we also don't
        # want to be stuck in the migration live system.
        # Keep fingers crossed:
        log.warning('Reboot system: [Force Reboot]')
        Command.run(['systemctl', '--force', 'reboot'])
def mount_system(root_path, fstab):
    log = logging.getLogger(Defaults.get_migration_log_name())
    log.info('Mount system in {0}'.format(root_path))
    system_mount = Fstab()
    explicit_mount_points = {
        'devtmpfs': os.sep.join([root_path, 'dev']),
        'proc': os.sep.join([root_path, 'proc']),
        'sysfs': os.sep.join([root_path, 'sys'])
    }
    try:
        for fstab_entry in fstab.get_devices():
            mountpoint = ''.join([root_path, fstab_entry.mountpoint])
            if mountpoint not in explicit_mount_points.values():
                if fstab_entry.eligible_for_mount:
                    log.info('Mounting {0}'.format(mountpoint))
                    Command.run([
                        'mount', '-o', fstab_entry.options, fstab_entry.device,
                        mountpoint
                    ])
                system_mount.add_entry(fstab_entry.device, mountpoint,
                                       fstab_entry.fstype,
                                       fstab_entry.eligible_for_mount)

        log.info('Mounting kernel file systems inside {0}'.format(root_path))
        for mount_type, mount_point in explicit_mount_points.items():
            Command.run(['mount', '-t', mount_type, mount_type, mount_point])
            system_mount.add_entry(mount_type, mount_point)
    except Exception as issue:
        log.error('Mounting system for upgrade failed with {0}'.format(issue))
        raise DistMigrationSystemMountException(
            'Mounting system for upgrade failed with {0}'.format(issue))
    system_mount.export(Defaults.get_system_mount_info_file())
Beispiel #10
0
    def test_main(self, mock_fstab, mock_command_run, mock_configparser_items,
                  mock_os_exists, mock_os_listdir, mock_log):
        def luks(device):
            command = Mock()
            command.returncode = 0
            command.output = 'ext4'
            if device[-1] == '/dev/disk/by-label/foo':
                command.output = 'crypto_LUKS'
            return command

        fstab = Fstab()
        fstab_mock = Mock()
        fstab_mock.read.return_value = fstab.read('../data/fstab')
        fstab_mock.get_devices.return_value = fstab.get_devices()
        mock_fstab.return_value = fstab_mock
        mock_command_run.side_effect = luks
        mock_os_exists.return_value = True
        mock_os_listdir.return_value = [
            'no_remote.repo', 'super_repo.repo', 'another.repo'
        ]
        repo_no_foo = 'hd:/?device=/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2'
        repo_no_bar = 'hd:/?device=/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea4'
        mock_configparser_items.side_effect = [[
            ('name', 'Foo'), ('enabled', '1'), ('autorefresh', '0'),
            ('baseurl', 'https://download.foo.com/foo/repo/'),
            ('type', 'rpm-md')
        ],
                                               [('name', 'No_Foo'),
                                                ('enabled', '1'),
                                                ('autorefresh', '0'),
                                                ('baseurl', f"{repo_no_foo}"),
                                                ('path', '/'),
                                                ('keeppackages', '0')],
                                               [('name', 'No_Bar'),
                                                ('enabled', '1'),
                                                ('autorefresh', '0'),
                                                ('baseurl', f"{repo_no_bar}"),
                                                ('path', '/'),
                                                ('keeppackages', '0')]]
        warning_message_remote_repos = \
            'The following repositories may cause the migration to fail, as they ' \
            'may not be available during the migration'
        warning_message_show_repos = \
            'To see all the repositories and their urls, you can run "zypper repos --url"'

        with self._caplog.at_level(logging.WARNING):
            main()
            assert warning_message_remote_repos in self._caplog.text
            assert warning_message_show_repos in self._caplog.text
Beispiel #11
0
def encryption():
    log = logging.getLogger(Defaults.get_migration_log_name())
    fstab = Fstab()
    fstab.read('/etc/fstab')
    fstab_entries = fstab.get_devices()

    for fstab_entry in fstab_entries:
        result = Command.run(
            ["blkid", "-s", "TYPE", "-o", "value", fstab_entry.device]
        )
        if result.returncode == 0:
            if 'LUKS' in result.output:
                log.warning(
                    'There are encrypted filesystems: {}, this may be an '
                    'issue when migrating'.format(fstab_entry.device)
                )
Beispiel #12
0
def read_system_fstab(root_path):
    log.info('Reading fstab from associated disks')
    lsblk_call = Command.run(['lsblk', '-p', '-n', '-r', '-o', 'NAME,TYPE'])
    for entry in lsblk_call.output.split(os.linesep):
        block_record = entry.split()
        if len(block_record) >= 2:
            block_type = block_record[1]
            if block_type == 'part' or block_type.startswith('raid'):
                try:
                    Command.run(['mount', block_record[0], root_path],
                                raise_on_error=False)
                    fstab_file = os.sep.join([root_path, 'etc', 'fstab'])
                    if os.path.exists(fstab_file):
                        fstab = Fstab()
                        fstab.read(fstab_file)
                        return (fstab, lsblk_call.output)
                finally:
                    log.info('Umount {0}'.format(root_path))
                    Command.run(['umount', root_path], raise_on_error=False)
    return (None, lsblk_call.output)
Beispiel #13
0
 def test_main_kexec_reboot(self, mock_Fstab, mock_Command_run, mock_info,
                            mock_get_migration_config_file):
     fstab = Fstab()
     fstab_mock = Mock()
     fstab_mock.read.return_value = fstab.read('../data/system-root.fstab')
     fstab_mock.get_devices.return_value = fstab.get_devices()
     mock_Fstab.return_value = fstab_mock
     mock_get_migration_config_file.return_value = \
         '../data/migration-config.yml'
     main()
     assert mock_info.called
     assert mock_Command_run.call_args_list == [
         call(['systemctl', 'status', '-l', '--all'], raise_on_error=False),
         call(['umount', '--lazy', '/system-root/home'],
              raise_on_error=False),
         call(['umount', '--lazy', '/system-root/boot/efi'],
              raise_on_error=False),
         call(['umount', '--lazy', '/system-root/'], raise_on_error=False),
         call(['systemctl', 'reboot'])
     ]
def main():
    """
    DistMigration activate host network setup

    Setup and activate the network as it is setup on the host
    to become migrated. This includes the import of the resolver
    and network configuration from the migration host
    """
    Logger.setup()
    log = logging.getLogger(Defaults.get_migration_log_name())
    root_path = Defaults.get_system_root_path()

    resolv_conf = os.sep.join([root_path, 'etc', 'resolv.conf'])
    if not os.path.exists(resolv_conf):
        raise DistMigrationNameResolverException(
            'Could not find {0} on migration host'.format(resolv_conf))
    if has_host_resolv_setup(resolv_conf):
        log.info('Copying {}'.format(resolv_conf))
        shutil.copy(resolv_conf, '/etc/resolv.conf')
    else:
        log.info('Empty {}, continuing without copying it'.format(resolv_conf))

    sysconfig_network_providers = os.sep.join(
        [root_path, 'etc', 'sysconfig', 'network', 'providers'])
    sysconfig_network_setup = os.sep.join(
        [root_path, 'etc', 'sysconfig', 'network', '*'])
    try:
        log.info('Running setup host network service')
        system_mount = Fstab()
        system_mount.read(Defaults.get_system_mount_info_file())
        Command.run([
            'mount', '--bind', sysconfig_network_providers,
            '/etc/sysconfig/network/providers'
        ])
        system_mount.add_entry(sysconfig_network_providers,
                               '/etc/sysconfig/network/providers')
        for network_setup in glob.glob(sysconfig_network_setup):
            if os.path.isfile(network_setup):
                shutil.copy(network_setup, '/etc/sysconfig/network')
        Command.run(['systemctl', 'reload', 'network'])
        system_mount.export(Defaults.get_system_mount_info_file())
    except Exception as issue:
        log.error(
            'Preparation of migration host network failed with {0}'.format(
                issue))
        raise DistMigrationHostNetworkException(
            'Preparation of migration host network failed with {0}'.format(
                issue))
Beispiel #15
0
 def test_main_raises_and_umount_file_system(
     self, mock_os_listdir, mock_shutil_copy, mock_Command_run,
     mock_os_path_exists, mock_Fstab,
     mock_logger_setup, mock_update_regionsrv_setup
 ):
     fstab = Fstab()
     fstab_mock = Mock()
     fstab_mock.read.return_value = fstab.read('../data/bind-mounted.fstab')
     fstab_mock.get_devices.return_value = fstab.get_devices()
     fstab_mock.export.side_effect = Exception
     mock_Fstab.return_value = fstab_mock
     mock_os_path_exists.return_value = True
     with raises(DistMigrationZypperMetaDataException):
         main()
         assert mock_Command_run.call_args_list == [
             call(['ip', 'a'], raise_on_error=False),
             call(['ip', 'r'], raise_on_error=False),
             call(['cat', '/etc/resolv.conf'], raise_on_error=False),
             call(['cat', '/proc/net/bonding/bond*'], raise_on_error=False),
             call(['umount', '/system-root/sys'], raise_on_error=False),
             call(['umount', '/system-root/proc'], raise_on_error=False),
             call(['umount', '/system-root/dev'], raise_on_error=False)
         ]
Beispiel #16
0
 def test_add_entry(self):
     fstab = Fstab()
     fstab.add_entry('/dev/sda', '/foo')
     assert fstab.get_devices() == [
         fstab.fstab_entry_type(fstype='none',
                                mountpoint='/foo',
                                device='/dev/sda',
                                options='defaults')
     ]
    def test_mount_system_raises(
            self, mock_Command_run, mock_warning, mock_info,
            mock_error, mock_log_file
    ):
        def command_calls(command):
            # mock error on mounting home, testing reverse umount
            if '/system-root/home' in command:
                raise Exception

        mock_log_file.return_value = '../data/logfile'
        mock_Command_run.side_effect = command_calls
        with raises(DistMigrationSystemMountException):
            fstab = Fstab()
            fstab.read('../data/fstab')
            mount_system(
                Defaults.get_system_root_path(), fstab
            )
        assert mock_Command_run.call_args_list == [
            call([
                'mount', '-o', 'acl,user_xattr',
                '/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                '/system-root/'
            ]),
            call([
                'mount', '-o', 'defaults',
                '/dev/disk/by-uuid/FCF7-B051', '/system-root/boot/efi'
            ]),
            call([
                'mount', '-o', 'defaults',
                '/dev/disk/by-label/foo', '/system-root/home'
            ]),
            call(['umount', '/system-root/boot/efi']),
            call(['umount', '/system-root/'])
        ]
        assert mock_info.called
        assert mock_error.called
Beispiel #18
0
 def test_export(self):
     fstab = Fstab()
     fstab.add_entry('/dev/sda', '/foo')
     with patch('builtins.open', create=True) as mock_open:
         mock_open.return_value = MagicMock(spec=io.IOBase)
         fstab.export('filename')
         file_handle = mock_open.return_value.__enter__.return_value
         mock_open.assert_called_once_with('filename', 'w')
         assert file_handle.write.call_args_list == [
             call('/dev/sda /foo none defaults 0 0\n')
         ]
Beispiel #19
0
def mount_system(root_path, fstab):
    log.info('Mount system in {0}'.format(root_path))
    mount_list = []
    system_mount = Fstab()
    for fstab_entry in fstab.get_devices():
        try:
            mountpoint = ''.join([root_path, fstab_entry.mountpoint])
            log.info('Mounting {0}'.format(mountpoint))
            Command.run([
                'mount', '-o', fstab_entry.options, fstab_entry.device,
                mountpoint
            ])
            system_mount.add_entry(fstab_entry.device, mountpoint,
                                   fstab_entry.fstype)
            mount_list.append(mountpoint)
        except Exception as issue:
            log.error(
                'Mounting system for upgrade failed with {0}'.format(issue))
            for mountpoint in reversed(mount_list):
                Command.run(['umount', mountpoint])
            raise DistMigrationSystemMountException(
                'Mounting system for upgrade failed with {0}'.format(issue))
    system_mount.export(Defaults.get_system_mount_info_file())
def main():
    """
    DistMigration prepare for migration

    Prepare the migration live system to allow zypper migration to
    upgrade the system across major distribution versions. The zypper
    migration process contacts the service that provides the configured
    repositories on the system being migrated. The service must be one
    of SUSE's repository services, SCC, RMT, or SMT. This requiers
    information from the target system. This service makes the necessary
    information available inside the live system that performs the migration.
    """
    root_path = Defaults.get_system_root_path()
    suse_connect_setup = os.sep.join(
        [root_path, 'etc', 'SUSEConnect']
    )
    suse_cloud_regionsrv_setup = os.sep.join(
        [root_path, 'etc', 'regionserverclnt.cfg']
    )
    hosts_setup = os.sep.join(
        [root_path, 'etc', 'hosts']
    )
    trust_anchors = os.sep.join(
        [root_path, 'usr', 'share', 'pki', 'trust', 'anchors']
    )
    if os.path.exists(suse_connect_setup):
        shutil.copy(
            suse_connect_setup, '/etc/SUSEConnect'
        )
    if os.path.exists(suse_cloud_regionsrv_setup):
        shutil.copy(
            suse_cloud_regionsrv_setup, '/etc/regionserverclnt.cfg'
        )
    if os.path.exists(hosts_setup):
        shutil.copy(
            hosts_setup, '/etc/hosts'
        )
    if os.path.exists(trust_anchors):
        certificates = os.listdir(trust_anchors)
        if certificates:
            for cert in certificates:
                log.info(
                    'Importing certificate: {0}'.format(cert)
                )
                shutil.copy(
                    os.sep.join([trust_anchors, cert]),
                    '/usr/share/pki/trust/anchors/'
                )
            log.info('Update certificate pool')
            Command.run(
                ['update-ca-certificates']
            )

    zypp_metadata = os.sep.join(
        [root_path, 'etc', 'zypp']
    )
    zypp_plugins = os.sep.join(
        [root_path, 'usr', 'lib', 'zypp', 'plugins']
    )
    cloud_register_metadata = os.sep.join(
        [root_path, 'var', 'lib', 'cloudregister']
    )
    dev_mount_point = os.sep.join(
        [root_path, 'dev']
    )
    proc_mount_point = os.sep.join(
        [root_path, 'proc']
    )
    sys_mount_point = os.sep.join(
        [root_path, 'sys']
    )
    try:
        # log network info as network-online.target is done at this point
        log_network_details()
        log.info('Running prepare service')
        system_mount = Fstab()
        system_mount.read(
            Defaults.get_system_mount_info_file()
        )
        log.info('Bind mounting /etc/zypp')
        Command.run(
            ['mount', '--bind', zypp_metadata, '/etc/zypp']
        )
        system_mount.add_entry(
            zypp_metadata, '/etc/zypp'
        )
        log.info('Bind mounting /usr/lib/zypp/plugins')
        Command.run(
            ['mount', '--bind', zypp_plugins, '/usr/lib/zypp/plugins']
        )
        system_mount.add_entry(
            zypp_plugins, '/usr/lib/zypp/plugins'
        )
        if os.path.exists(cloud_register_metadata):
            log.info('Bind mounting /var/lib/cloudregister')
            Path.create('/var/lib/cloudregister')
            Command.run(
                [
                    'mount', '--bind', cloud_register_metadata,
                    '/var/lib/cloudregister'
                ]
            )
        log.info('Mounting kernel file systems inside {0}'.format(root_path))
        Command.run(
            ['mount', '-t', 'devtmpfs', 'devtmpfs', dev_mount_point]
        )
        system_mount.add_entry(
            'devtmpfs', dev_mount_point
        )
        Command.run(
            ['mount', '-t', 'proc', 'proc', proc_mount_point]
        )
        system_mount.add_entry(
            '/proc', proc_mount_point
        )
        Command.run(
            ['mount', '-t', 'sysfs', 'sysfs', sys_mount_point]
        )
        system_mount.add_entry(
            'sysfs', sys_mount_point
        )
        system_mount.export(
            Defaults.get_system_mount_info_file()
        )
    except Exception as issue:
        log.error(
            'Preparation of zypper metadata failed with {0}'.format(
                issue
            )
        )
        log.info('Unmounting kernel file systems, if any')
        for entry in reversed(system_mount.get_devices()):
            Command.run(
                ['umount', entry.mountpoint], raise_on_error=False
            )
        raise DistMigrationZypperMetaDataException(
            'Preparation of zypper metadata failed with {0}'.format(
                issue
            )
        )
Beispiel #21
0
class TestFstab(object):
    @fixture(autouse=True)
    def inject_fixtures(self, caplog):
        self._caplog = caplog

    @patch('os.path.exists')
    def setup(self, mock_os_path_exists):
        mock_os_path_exists.return_value = True
        self.fstab = Fstab()
        self.fstab.read('../data/fstab')

    @patch('os.path.exists')
    def test_read_with_skipped_entries(self, mock_os_path_exists):
        def skip_device(device):
            if '/dev/mynode' in device:
                return False
            return True

        mock_os_path_exists.side_effect = skip_device
        fstab = Fstab()
        with self._caplog.at_level(logging.WARNING):
            fstab.read('../data/fstab')
            assert 'Device path /dev/mynode not found and skipped' in \
                self._caplog.text
        assert fstab.get_devices() == [
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/',
                device='/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                options='acl,user_xattr'),
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/bar',
                device='/dev/disk/by-partuuid/3c8bd108-01',
                options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home',
                                        device='/dev/disk/by-label/foo',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='vfat',
                                        mountpoint='/boot/efi',
                                        device='/dev/disk/by-uuid/FCF7-B051',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home/stack',
                                        device='/dev/homeboy',
                                        options='defaults')
        ]

    def test_get_devices(self):
        assert self.fstab.get_devices() == [
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/',
                device='/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                options='acl,user_xattr'),
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/bar',
                device='/dev/disk/by-partuuid/3c8bd108-01',
                options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/foo',
                                        device='/dev/mynode',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home',
                                        device='/dev/disk/by-label/foo',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='vfat',
                                        mountpoint='/boot/efi',
                                        device='/dev/disk/by-uuid/FCF7-B051',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home/stack',
                                        device='/dev/homeboy',
                                        options='defaults')
        ]

    def test_add_entry(self):
        fstab = Fstab()
        fstab.add_entry('/dev/sda', '/foo')
        assert fstab.get_devices() == [
            fstab.fstab_entry_type(fstype='none',
                                   mountpoint='/foo',
                                   device='/dev/sda',
                                   options='defaults')
        ]

    def test_export(self):
        fstab = Fstab()
        fstab.add_entry('/dev/sda', '/foo')
        with patch('builtins.open', create=True) as mock_open:
            mock_open.return_value = MagicMock(spec=io.IOBase)
            fstab.export('filename')
            file_handle = mock_open.return_value.__enter__.return_value
            mock_open.assert_called_once_with('filename', 'w')
            assert file_handle.write.call_args_list == [
                call('/dev/sda /foo none defaults 0 0\n')
            ]
    def test_main(self, mock_path_exists, mock_is_mounted, mock_Fstab,
                  mock_path_wipe, mock_path_create, mock_Command_run,
                  mock_logger_setup, mock_update_migration_config_file,
                  mock_get_migration_config_file,
                  mock_get_system_migration_custom_config_file, mock_yaml_dump,
                  mock_yaml_safe_load):
        def _is_mounted(path):
            if path == '/run/initramfs/isoscan':
                return True
            return False

        fstab = Fstab()
        fstab_mock = Mock()
        fstab_mock.read.return_value = fstab.read('../data/fstab')
        fstab_mock.get_devices.return_value = fstab.get_devices()
        mock_is_mounted.side_effect = _is_mounted
        mock_path_exists.side_effect = [True, True, False]
        mock_Fstab.return_value = fstab_mock
        command = Mock()
        command.returncode = 1
        command.output = '/dev/sda1 part\n/dev/mapper/LVRoot lvm'
        mock_Command_run.return_value = command
        mock_get_system_migration_custom_config_file.return_value = \
            '../data/optional-migration-config.yml'
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_yaml_safe_load.return_value = {
            'migration_product': 'SLES/15/x86_64'
        }
        with patch('builtins.open', create=True) as mock_open:
            main()
            assert mock_update_migration_config_file.called
            assert mock_Command_run.call_args_list == [
                call(['mount', '-o', 'remount,rw', '/run/initramfs/isoscan']),
                call(['lsblk', '-p', '-n', '-r', '-o', 'NAME,TYPE']),
                call(['vgchange', '-a', 'y']),
                call(['mount', '/dev/sda1', '/system-root'],
                     raise_on_error=False),
                call(['umount', '/system-root'], raise_on_error=False),
                call([
                    'mount', '-o', 'acl,user_xattr', '/dev/disk/by-uuid/'
                    'bd604632-663b-4d4c-b5b0-8d8686267ea2', '/system-root/'
                ]),
                call([
                    'mount', '-o', 'defaults',
                    '/dev/disk/by-partuuid/3c8bd108-01', '/system-root/bar'
                ]),
                call([
                    'mount', '-o', 'defaults', '/dev/mynode',
                    '/system-root/foo'
                ]),
                call([
                    'mount', '-o', 'defaults', '/dev/disk/by-label/foo',
                    '/system-root/home'
                ]),
                call([
                    'mount', '-o', 'defaults', '/dev/disk/by-uuid/FCF7-B051',
                    '/system-root/boot/efi'
                ]),
                call([
                    'mount', '-o', 'defaults', '/dev/homeboy',
                    '/system-root/home/stack'
                ]),
                call([
                    'mount', '-t', 'devtmpfs', 'devtmpfs', '/system-root/dev'
                ]),
                call(['mount', '-t', 'proc', 'proc', '/system-root/proc']),
                call(['mount', '-t', 'sysfs', 'sysfs', '/system-root/sys'])
            ]
            assert fstab_mock.add_entry.call_args_list == [
                call('/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                     '/system-root/', 'ext4'),
                call('/dev/disk/by-partuuid/3c8bd108-01', '/system-root/bar',
                     'ext4'),
                call('/dev/mynode', '/system-root/foo', 'ext4'),
                call('/dev/disk/by-label/foo', '/system-root/home', 'ext4'),
                call(
                    '/dev/disk/by-uuid/FCF7-B051',
                    '/system-root/boot/efi',
                    'vfat',
                ),
                call('/dev/homeboy', '/system-root/home/stack', 'ext4'),
                call('devtmpfs', '/system-root/dev'),
                call('/proc', '/system-root/proc'),
                call('sysfs', '/system-root/sys')
            ]
            fstab_mock.export.assert_called_once_with('/etc/system-root.fstab')
            assert mock_open.call_args_list == [
                call('../data/migration-config.yml', 'r')
            ]
Beispiel #23
0
class TestFstab(object):
    @patch('os.path.exists')
    def setup(self, mock_os_path_exists):
        mock_os_path_exists.return_value = True
        self.fstab = Fstab()
        self.fstab.read('../data/fstab')

    @patch('suse_migration_services.logger.log.warning')
    @patch('os.path.exists')
    def test_read_with_skipped_entries(self, mock_os_path_exists,
                                       mock_log_warning):
        mock_os_path_exists.return_value = False
        fstab = Fstab()
        fstab.read('../data/fstab')
        mock_log_warning.assert_called_once_with(
            'Device path /dev/mynode not found and skipped')
        assert fstab.get_devices() == [
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/',
                device='/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                options='acl,user_xattr'),
            self.fstab.fstab_entry_type(fstype='vfat',
                                        mountpoint='/boot/efi',
                                        device='/dev/disk/by-uuid/FCF7-B051',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home',
                                        device='/dev/disk/by-label/foo',
                                        options='defaults'),
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/bar',
                device='/dev/disk/by-partuuid/3c8bd108-01',
                options='defaults')
        ]

    def test_get_devices(self):
        assert self.fstab.get_devices() == [
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/',
                device='/dev/disk/by-uuid/bd604632-663b-4d4c-b5b0-8d8686267ea2',
                options='acl,user_xattr'),
            self.fstab.fstab_entry_type(fstype='vfat',
                                        mountpoint='/boot/efi',
                                        device='/dev/disk/by-uuid/FCF7-B051',
                                        options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/home',
                                        device='/dev/disk/by-label/foo',
                                        options='defaults'),
            self.fstab.fstab_entry_type(
                fstype='ext4',
                mountpoint='/bar',
                device='/dev/disk/by-partuuid/3c8bd108-01',
                options='defaults'),
            self.fstab.fstab_entry_type(fstype='ext4',
                                        mountpoint='/foo',
                                        device='/dev/mynode',
                                        options='defaults')
        ]

    def test_add_entry(self):
        fstab = Fstab()
        fstab.add_entry('/dev/sda', '/foo')
        assert fstab.get_devices() == [
            fstab.fstab_entry_type(fstype='none',
                                   mountpoint='/foo',
                                   device='/dev/sda',
                                   options='defaults')
        ]

    def test_export(self):
        fstab = Fstab()
        fstab.add_entry('/dev/sda', '/foo')
        with patch('builtins.open', create=True) as mock_open:
            mock_open.return_value = MagicMock(spec=io.IOBase)
            fstab.export('filename')
            file_handle = mock_open.return_value.__enter__.return_value
            mock_open.assert_called_once_with('filename', 'w')
            assert file_handle.write.call_args_list == [
                call('/dev/sda /foo none defaults 0 0\n')
            ]
Beispiel #24
0
def main():
    """
    DistMigration reboot with new kernel

    After the migration process is finished, the system reboots
    unless the debug option is set.

    Before reboot a reverse umount of the filesystems that got mounted
    by the mount_system service is performed and thus releases the
    upgraded system from the migration host. If for whatever reason
    a filesystem is busy and can't be umounted, this condition is not
    handled as an error. The reason is that the cleanup should not
    prevent us from continuing with the reboot process. The risk on
    reboot of the migration host with a potential active mount
    is something we accept
    """
    try:
        log.info(
            'Systemctl Status Information: {0}{1}'.format(
                os.linesep, Command.run(
                    ['systemctl', 'status', '-l', '--all'], raise_on_error=False
                ).output
            )
        )
        if MigrationConfig().is_debug_requested():
            log.info('Reboot skipped due to debug flag set')
        else:
            log.info('Umounting system')
            system_mount = Fstab()
            system_mount.read(
                Defaults.get_system_mount_info_file()
            )
            for mount in reversed(system_mount.get_devices()):
                log.info(
                    'Umounting {0}: {1}'.format(
                        mount.mountpoint, Command.run(
                            ['umount', '--lazy', mount.mountpoint],
                            raise_on_error=False
                        )
                    )
                )
            log.info(
                # reboot is performed through systemd. The call through
                # systemd checks if there is a kexec loaded kernel and
                # transparently turns 'systemctl reboot' into
                # 'systemctl kexec'. Thus both ways, soft and hard
                # reboot are managed in one call.
                'Reboot system: {0}{1}'.format(
                    os.linesep, Command.run(
                        ['systemctl', 'reboot']
                    )
                )
            )
    except Exception:
        # Uhh, we don't want to be here, but we also don't
        # want to be stuck in the migration live system.
        # Keep fingers crossed:
        log.warning('Reboot system: [Force Reboot]')
        Command.run(
            ['systemctl', '--force', 'reboot']
        )
def main():
    """
    DistMigration prepare for migration

    Prepare the migration live system to allow zypper migration to
    upgrade the system across major distribution versions. The zypper
    migration process contacts the service that provides the configured
    repositories on the system being migrated. The service must be one
    of SUSE's repository services, SCC, RMT, or SMT. This requiers
    information from the target system. This service makes the necessary
    information available inside the live system that performs the migration.
    """
    Logger.setup()
    log = logging.getLogger(Defaults.get_migration_log_name())
    root_path = Defaults.get_system_root_path()
    suse_connect_setup = os.sep.join([root_path, 'etc', 'SUSEConnect'])
    suse_cloud_regionsrv_setup = os.sep.join(
        [root_path, 'etc', 'regionserverclnt.cfg'])
    hosts_setup = os.sep.join([root_path, 'etc', 'hosts'])
    trust_anchors = os.sep.join(
        [root_path, 'usr', 'share', 'pki', 'trust', 'anchors'])
    if os.path.exists(suse_connect_setup):
        shutil.copy(suse_connect_setup, '/etc/SUSEConnect')
    if os.path.exists(suse_cloud_regionsrv_setup):
        migration_suse_cloud_regionsrv_setup = '/etc/regionserverclnt.cfg'
        shutil.copy(suse_cloud_regionsrv_setup,
                    migration_suse_cloud_regionsrv_setup)
        update_regionsrv_setup(root_path, migration_suse_cloud_regionsrv_setup)
    if os.path.exists(hosts_setup):
        shutil.copy(hosts_setup, '/etc/hosts')
    if os.path.exists(trust_anchors):
        certificates = os.listdir(trust_anchors)
        if certificates:
            for cert in certificates:
                log.info('Importing certificate: {0}'.format(cert))
                shutil.copy(os.sep.join([trust_anchors, cert]),
                            '/usr/share/pki/trust/anchors/')
            log.info('Update certificate pool')
            Command.run(['update-ca-certificates'])

    zypp_metadata = os.sep.join([root_path, 'etc', 'zypp'])
    zypp_plugins_services = os.sep.join(
        [root_path, 'usr', 'lib', 'zypp', 'plugins', 'services'])
    cloud_register_metadata = os.sep.join(
        [root_path, 'var', 'lib', 'cloudregister'])
    zypper_log_file = os.sep.join([root_path, 'var', 'log', 'zypper.log'])
    if os.path.exists(zypper_log_file):
        try:
            zypper_host_log_file = zypper_log_file.replace(root_path, '')
            if not os.path.exists(zypper_host_log_file):
                with open(zypper_host_log_file, 'w'):
                    # we bind mount the system zypper log file
                    # but the mount target does not exist.
                    # Create it as empty file prior bind mounting
                    pass
                Command.run(
                    ['mount', '--bind', zypper_log_file, zypper_host_log_file])
        except Exception as issue:
            log.warning(
                'Bind mounting zypper log file failed with: {0}'.format(issue))
    try:
        # log network info as network-online.target is done at this point
        log_network_details()
        log.info('Running prepare service')
        system_mount = Fstab()
        system_mount.read(Defaults.get_system_mount_info_file())
        log.info('Bind mounting /etc/zypp')
        Command.run(['mount', '--bind', zypp_metadata, '/etc/zypp'])
        system_mount.add_entry(zypp_metadata, '/etc/zypp')
        log.info('Bind mounting /usr/lib/zypp/plugins')
        Command.run([
            'mount', '--bind', zypp_plugins_services,
            '/usr/lib/zypp/plugins/services'
        ])
        system_mount.add_entry(zypp_plugins_services,
                               '/usr/lib/zypp/plugins/services')
        if os.path.exists(cloud_register_metadata):
            log.info('Bind mounting /var/lib/cloudregister')
            Path.create('/var/lib/cloudregister')
            Command.run([
                'mount', '--bind', cloud_register_metadata,
                '/var/lib/cloudregister'
            ])
            update_smt_cache = '/usr/sbin/updatesmtcache'
            if os.path.isfile(update_smt_cache):
                log.info('Updating SMT cache')
                Command.run([update_smt_cache])
        system_mount.export(Defaults.get_system_mount_info_file())
        # Check if system is registered
        migration_config = MigrationConfig()
        if migration_config.is_zypper_migration_plugin_requested():
            if not SUSEConnect.is_registered():
                message = 'System not registered. Aborting migration.'
                log.error(message)
                raise DistMigrationSystemNotRegisteredException(message)
    except Exception as issue:
        log.error(
            'Preparation of zypper metadata failed with {0}'.format(issue))
        # Not unmounting any of the bind mounts above; the reboot
        # service should take care of that anyway
        raise DistMigrationZypperMetaDataException(
            'Preparation of zypper metadata failed with {0}'.format(issue))
Beispiel #26
0
 def setup(self, mock_os_path_exists):
     mock_os_path_exists.return_value = True
     self.fstab = Fstab()
     self.fstab.read('../data/fstab')
Beispiel #27
0
def mount_system(root_path, fstab):
    log = logging.getLogger(Defaults.get_migration_log_name())
    log.info('Mount system in {0}'.format(root_path))
    system_mount = Fstab()
    try:
        for fstab_entry in fstab.get_devices():
            mountpoint = ''.join([root_path, fstab_entry.mountpoint])
            log.info('Mounting {0}'.format(mountpoint))
            Command.run([
                'mount', '-o', fstab_entry.options, fstab_entry.device,
                mountpoint
            ])
            system_mount.add_entry(fstab_entry.device, mountpoint,
                                   fstab_entry.fstype)

        log.info('Mounting kernel file systems inside {0}'.format(root_path))
        dev_mount_point = os.sep.join([root_path, 'dev'])
        Command.run(['mount', '-t', 'devtmpfs', 'devtmpfs', dev_mount_point])
        system_mount.add_entry('devtmpfs', dev_mount_point)
        proc_mount_point = os.sep.join([root_path, 'proc'])
        Command.run(['mount', '-t', 'proc', 'proc', proc_mount_point])
        system_mount.add_entry('/proc', proc_mount_point)
        sys_mount_point = os.sep.join([root_path, 'sys'])
        Command.run(['mount', '-t', 'sysfs', 'sysfs', sys_mount_point])
        system_mount.add_entry('sysfs', sys_mount_point)
    except Exception as issue:
        log.error('Mounting system for upgrade failed with {0}'.format(issue))
        raise DistMigrationSystemMountException(
            'Mounting system for upgrade failed with {0}'.format(issue))
    system_mount.export(Defaults.get_system_mount_info_file())