Beispiel #1
0
class TestMigrationConfig(object):
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def setup(self, mock_get_system_migration_config_custom_file,
              mock_get_migration_config_file):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config.yml'
        self.config = MigrationConfig()

    def test_get_migration_product(self):
        assert self.config.get_migration_product() == 'SLES/15/x86_64'

    def test_get_preserve_udev_rules_list(self):
        assert self.config.get_preserve_udev_rules_list() == [
            '/etc/udev/rules.d/a.rules', '/etc/udev/rules.d/b.rules'
        ]

    @patch('suse_migration_services.logger.log.error')
    def test_get_migration_product_targets(self, mock_error):
        self.config.config_data = {'not_migration_product': 'another_info'}

        with raises(DistMigrationProductNotFoundException):
            self.config.get_migration_product()
            assert mock_error.called

    @patch.object(MigrationConfig, '_write_config_file')
    @patch('suse_migration_services.logger.log.info')
    def test_update_migration_config_file(
        self,
        mock_info,
        mock_write_config_file,
    ):
        self.config.update_migration_config_file()
        assert self.config.get_migration_product() == 'SLES/15.1/x86_64'
        assert self.config.is_debug_requested() is True
        assert mock_info.called

    def test_is_zypper_migration_plugin_requested(self):
        assert self.config.is_zypper_migration_plugin_requested() is True

    def test_is_debug_requested(self):
        assert self.config.is_debug_requested() is False

    @patch('yaml.dump')
    def test_write_config_file(self, mock_yaml_dump):
        with patch('builtins.open', create=True) as mock_open:
            mock_open.return_value = MagicMock(spec=io.IOBase)
            file_handle = mock_open.return_value.__enter__.return_value
            self.config._write_config_file()
            mock_open.assert_called_once_with(
                self.config.migration_config_file, 'w')
            mock_yaml_dump.assert_called_once_with(self.config.config_data,
                                                   file_handle,
                                                   default_flow_style=False)
Beispiel #2
0
def main():
    """
    DistMigration run zypper based migration

    Call zypper migration plugin and migrate the system.
    The output of the call is logged on the system to migrate
    """
    root_path = Defaults.get_system_root_path()

    try:
        log.info('Running migrate service')
        migration_config = MigrationConfig()
        if migration_config.is_zypper_migration_plugin_requested():
            bash_command = ' '.join([
                'zypper', 'migration', '--non-interactive',
                '--gpg-auto-import-keys', '--no-selfupdate',
                '--auto-agree-with-licenses', '--allow-vendor-change',
                '--strict-errors-dist-migration', '--replacefiles',
                '--product',
                migration_config.get_migration_product(), '--root', root_path,
                '&>>',
                Defaults.get_migration_log_file()
            ])
            Command.run(['bash', '-c', bash_command])
        else:
            bash_command = ' '.join([
                'zypper', '--non-interactive', '--gpg-auto-import-keys',
                '--root', root_path, 'dup', '--auto-agree-with-licenses',
                '--allow-vendor-change', '--replacefiles', '&>>',
                Defaults.get_migration_log_file()
            ])
            zypper_call = Command.run(['bash', '-c', bash_command],
                                      raise_on_error=False)
            if zypper_has_failed(zypper_call.returncode):
                raise DistMigrationCommandException(
                    '{0} failed with: {1}: {2}'.format(bash_command,
                                                       zypper_call.output,
                                                       zypper_call.error))
    except Exception as issue:
        etc_issue_path = os.sep.join([root_path, 'etc/issue'])
        log_path_migrated_system = os.sep + os.path.relpath(
            Defaults.get_migration_log_file(), root_path)
        with open(etc_issue_path, 'w') as issue_file:
            issue_file.write(
                'Migration has failed, for further details see {0}'.format(
                    log_path_migrated_system))
        log.error('migrate service failed with {0}'.format(issue))
        raise DistMigrationZypperException(
            'Migration failed with {0}'.format(issue))
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 #4
0
class TestMigrationConfig(object):
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def setup(
        self, mock_get_system_migration_config_custom_file,
        mock_get_migration_config_file
    ):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config.yml'
        self.config = MigrationConfig()

    @patch.object(Defaults, 'get_os_release')
    @patch.object(Defaults, 'get_system_root_path')
    def test_get_migration_product_auto_detected(
        self, mock_get_system_root_path, mock_get_os_release
    ):
        os_release_tuple = namedtuple(
            'OSRelease', [
                'name', 'version', 'version_id',
                'pretty_name', 'id', 'id_like', 'ansi_color', 'cpe_name'
            ]
        )
        os_release_result = os_release_tuple(
            name='SLES', version='15-SP1', version_id='15.1',
            pretty_name='SUSE Linux Enterprise Server 15 SP1',
            id='sles', id_like='suse', ansi_color='0;32',
            cpe_name='cpe:/o:suse:sles:15:sp1'
        )
        mock_get_system_root_path.return_value = '../data'
        mock_get_os_release.return_value = os_release_result
        assert self.config.get_migration_product() == 'SLES/15.1/x86_64'

    def test_get_preserve_udev_rules_list(self):
        assert self.config.get_preserve_udev_rules_list() == [
            '/etc/udev/rules.d/a.rules', '/etc/udev/rules.d/b.rules'
        ]

    @patch.object(Defaults, 'get_os_release')
    @patch.object(SUSEBaseProduct, 'get_tag')
    @patch.object(Defaults, 'get_system_root_path')
    def test_get_migration_product_targets(
        self, mock_get_system_root_path,
        mock_get_product_name, mock_get_os_release
    ):
        os_release_tuple = namedtuple(
            'OSRelease', [
                'name', 'version', 'version_id',
                'pretty_name', 'id', 'id_like', 'ansi_color', 'cpe_name'
            ]
        )
        os_release_result = os_release_tuple(
            name='SLES', version='15-SP1', version_id='15.1',
            pretty_name='SUSE Linux Enterprise Server 15 SP1',
            id='sles', id_like='suse', ansi_color='0;32',
            cpe_name='cpe:/o:suse:sles:15:sp1'
        )
        mock_get_system_root_path.return_value = '../data'
        mock_get_os_release.return_value = os_release_result
        mock_get_product_name.side_effect = Exception
        self.config.config_data = {'not_migration_product': 'another_info'}

        with raises(DistMigrationProductNotFoundException):
            self.config.get_migration_product()

    @patch.object(Defaults, 'get_os_release')
    @patch.object(SUSEBaseProduct, 'get_product_name')
    @patch.object(Defaults, 'get_system_root_path')
    @patch.object(MigrationConfig, '_write_config_file')
    def test_update_migration_config_file_no_autodetect(
        self, mock_write_config_file,
        mock_get_system_root_path, mock_get_product_name,
        mock_get_os_release
    ):
        os_release_tuple = namedtuple(
            'OSRelease', [
                'name', 'version', 'version_id',
                'pretty_name', 'id', 'id_like', 'ansi_color', 'cpe_name'
            ]
        )
        os_release_result = os_release_tuple(
            name='SLES', version='15-SP1', version_id='15.1',
            pretty_name='SUSE Linux Enterprise Server 15 SP1',
            id='sles', id_like='suse', ansi_color='0;32',
            cpe_name='cpe:/o:suse:sles:15:sp1'
        )
        mock_get_os_release.return_value = os_release_result
        mock_get_system_root_path.return_value = '../data'
        mock_get_product_name.return_value = None
        self.config.update_migration_config_file()
        assert self.config.get_migration_product() == 'SLES/15.1/x86_64'
        assert self.config.is_debug_requested() is True

    def test_is_zypper_migration_plugin_requested(self):
        assert self.config.is_zypper_migration_plugin_requested() is True

    def test_is_debug_requested(self):
        assert self.config.is_debug_requested() is False

    @patch('yaml.dump')
    def test_write_config_file(self, mock_yaml_dump):
        with patch('builtins.open', create=True) as mock_open:
            mock_open.return_value = MagicMock(spec=io.IOBase)
            file_handle = mock_open.return_value.__enter__.return_value
            self.config._write_config_file()
            mock_open.assert_called_once_with(
                self.config.migration_config_file, 'w'
            )
            mock_yaml_dump.assert_called_once_with(
                self.config.config_data, file_handle, default_flow_style=False
            )

    @patch.object(MigrationConfig, '_write_config_file')
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def test_update_migration_config_file_empty(
        self, mock_get_system_migration_config_custom_file,
        mock_get_migration_config_file,
        mock_write_config_file,
    ):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config-empty.yml'
        self.config = MigrationConfig()
        self.config.update_migration_config_file()

    @patch.object(MigrationConfig, '_write_config_file')
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def test_update_migration_config_file_just_comments(
        self, mock_get_system_migration_config_custom_file,
        mock_get_migration_config_file,
        mock_write_config_file,
    ):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config-just-comments.yml'
        self.config = MigrationConfig()
        self.config.update_migration_config_file()

    @patch.object(MigrationConfig, '_write_config_file')
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def test_update_migration_config_file_slightly_broken(
        self, mock_get_system_migration_config_custom_file,
        mock_get_migration_config_file, mock_write_config_file,
    ):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config-corrupt-string.yml'
        self.config = MigrationConfig()
        with raises(DistMigrationConfigDataException):
            self.config.update_migration_config_file()

    @patch.object(MigrationConfig, '_write_config_file')
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def test_update_migration_config_file_very_broken(
        self, mock_get_system_migration_config_custom_file,
        mock_get_migration_config_file, mock_write_config_file,
    ):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config-corrupt-mess.yml'
        self.config = MigrationConfig()
        with raises(DistMigrationConfigDataException):
            self.config.update_migration_config_file()

    @patch.object(MigrationConfig, '_write_config_file')
    @patch.object(Defaults, 'get_migration_config_file')
    @patch.object(Defaults, 'get_system_migration_custom_config_file')
    def test_update_migration_config_file_violates_schema(
        self, mock_get_system_migration_config_custom_file,
        mock_get_migration_config_file, mock_write_config_file,
    ):
        mock_get_migration_config_file.return_value = \
            '../data/migration-config.yml'
        mock_get_system_migration_config_custom_file.return_value = \
            '../data/custom-migration-config-violates-schema.yml'
        self.config = MigrationConfig()
        with raises(DistMigrationConfigDataException):
            self.config.update_migration_config_file()