# Copyright 2015 Red Hat, Inc.
# All Rights Reserved.
#
#    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 os
import shutil
import tempfile
from unittest import mock

from ironic_lib import disk_utils
from ironic_lib import utils as ilib_utils
from oslo_concurrency import processutils

from ironic_python_agent import efi_utils
from ironic_python_agent import errors
from ironic_python_agent.extensions import image
from ironic_python_agent import hardware
from ironic_python_agent import partition_utils
from ironic_python_agent import raid_utils
from ironic_python_agent.tests.unit import base
from ironic_python_agent.tests.unit.samples import hardware_samples as hws


@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@mock.patch.object(ilib_utils, 'execute', autospec=True)
@mock.patch.object(tempfile, 'mkdtemp', lambda *_: '/tmp/fake-dir')
@mock.patch.object(shutil, 'rmtree', lambda *_: None)
class TestImageExtension(base.IronicAgentTest):

    def setUp(self):
        super(TestImageExtension, self).setUp()
        self.agent_extension = image.ImageExtension()
        self.fake_dev = '/dev/fake'
        self.fake_efi_system_part = '/dev/fake1'
        self.fake_root_part = '/dev/fake2'
        self.fake_prep_boot_part = '/dev/fake3'
        self.fake_root_uuid = '11111111-2222-3333-4444-555555555555'
        self.fake_efi_system_part_uuid = '45AB-2312'
        self.fake_prep_boot_part_uuid = '76937797-3253-8843-999999999999'
        self.fake_dir = '/tmp/fake-dir'

    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_bios(self, mock_grub2,
                                      mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='bios')
        ]
        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid).join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None, prep_boot_part_uuid=None,
            target_boot_mode='bios'
        )

    @mock.patch.object(efi_utils, 'manage_uefi', autospec=True)
    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_uefi(self, mock_grub2, mock_uefi,
                                      mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_uefi.return_value = False
        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi'
        ).join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev,
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            prep_boot_part_uuid=None,
            target_boot_mode='uefi'
        )

    @mock.patch.object(efi_utils, 'manage_uefi', autospec=True)
    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_uefi_ignores_manage_failure(
            self, mock_grub2, mock_uefi,
            mock_execute, mock_dispatch):
        self.config(ignore_bootloader_failure=True)
        mock_uefi.side_effect = OSError('meow')
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_uefi.return_value = False
        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi'
        ).join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev,
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            prep_boot_part_uuid=None,
            target_boot_mode='uefi'
        )

    @mock.patch.object(efi_utils, 'manage_uefi', autospec=True)
    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_uefi_ignores_grub_failure(
            self, mock_grub2, mock_uefi,
            mock_execute, mock_dispatch):
        self.config(ignore_bootloader_failure=True)
        mock_grub2.side_effect = OSError('meow')
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_uefi.return_value = False
        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi'
        ).join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev,
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            prep_boot_part_uuid=None,
            target_boot_mode='uefi'
        )

    @mock.patch.object(efi_utils, 'manage_uefi', autospec=True)
    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_uefi_ignores_grub_failure_api_override(
            self, mock_grub2, mock_uefi,
            mock_execute, mock_dispatch):
        self.config(ignore_bootloader_failure=False)
        mock_grub2.side_effect = OSError('meow')
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_uefi.return_value = False
        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi', ignore_bootloader_failure=True,
        ).join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev,
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            prep_boot_part_uuid=None,
            target_boot_mode='uefi'
        )

    @mock.patch.object(efi_utils, 'manage_uefi', autospec=True)
    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_uefi_grub_failure_api_override(
            self, mock_grub2, mock_uefi,
            mock_execute, mock_dispatch):
        self.config(ignore_bootloader_failure=True)
        mock_grub2.side_effect = OSError('meow')
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_uefi.return_value = False
        result = self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi', ignore_bootloader_failure=False,
        ).join()
        self.assertIsNotNone(result.command_error)
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev,
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            prep_boot_part_uuid=None,
            target_boot_mode='uefi'
        )

    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_no_root(self, mock_grub2,
                                         mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='bios')
        ]
        self.agent_extension.install_bootloader(
            root_uuid='0x00000000').join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        self.assertFalse(mock_grub2.called)

    @mock.patch.object(hardware, 'is_md_device', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    @mock.patch.object(efi_utils, '_get_efi_bootloaders', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    @mock.patch.object(disk_utils, 'find_efi_partition', autospec=False)
    @mock.patch.object(os, 'makedirs', autospec=True)
    def test__uefi_bootloader_given_partition(
            self, mkdir_mock, mock_utils_efi_part, mock_get_partition,
            mock_efi_bl, mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_get_partition.return_value = self.fake_efi_system_part
        mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI']
        mock_utils_efi_part.return_value = None

        mock_execute.side_effect = iter([('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', '')])

        expected = [mock.call('efibootmgr', '--version'),
                    mock.call('partx', '-av', '/dev/fake', attempts=3,
                              delay_on_retry=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call('efibootmgr', '-v'),
                    mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
                              '-p', '1', '-w',
                              '-L', 'ironic1', '-l',
                              '\\EFI\\BOOT\\BOOTX64.EFI'),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call('sync')]

        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid).join()

        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_utils_efi_part.assert_called_once_with(self.fake_dev)
        self.assertEqual(8, mock_execute.call_count)

    @mock.patch.object(hardware, 'is_md_device', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    @mock.patch.object(efi_utils, '_get_efi_bootloaders', autospec=True)
    @mock.patch.object(efi_utils, 'get_partition_path_by_number',
                       autospec=True)
    @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    def test__uefi_bootloader_find_partition(
            self, mkdir_mock, mock_utils_efi_part, mock_get_part_path,
            mock_efi_bl, mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_get_part_path.return_value = self.fake_efi_system_part
        mock_utils_efi_part.return_value = {'number': '1'}
        mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI']
        mock_execute.side_effect = iter([('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', '')])

        expected = [mock.call('efibootmgr', '--version'),
                    mock.call('partx', '-av', '/dev/fake', attempts=3,
                              delay_on_retry=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call('efibootmgr', '-v'),
                    mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
                              '-p', '1', '-w',
                              '-L', 'ironic1', '-l',
                              '\\EFI\\BOOT\\BOOTX64.EFI'),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call('sync')]

        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None).join()

        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_utils_efi_part.assert_called_once_with(self.fake_dev)
        self.assertEqual(8, mock_execute.call_count)

    @mock.patch.object(hardware, 'is_md_device', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    @mock.patch.object(efi_utils, '_get_efi_bootloaders', autospec=True)
    @mock.patch.object(efi_utils, 'get_partition_path_by_number',
                       autospec=True)
    @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    def test__uefi_bootloader_with_entry_removal(
            self, mkdir_mock, mock_utils_efi_part, mock_get_part_path,
            mock_efi_bl, mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_get_part_path.return_value = self.fake_efi_system_part
        mock_utils_efi_part.return_value = {'number': '1'}
        mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI']
        stdout_msg = """
BootCurrent: 0001
Timeout: 0 seconds
BootOrder: 0000,00001
Boot0000 ironic1 HD(1,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI)
Boot0001 ironic2 HD(1,GPT,4f3c6294-bf9b-4208-9808-111111111112)File(\EFI\Boot\BOOTX64.EFI)
Boot0002 VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51)
"""  # noqa This is a giant literal string for testing.
        mock_execute.side_effect = iter([('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         (stdout_msg, ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', '')])

        expected = [mock.call('efibootmgr', '--version'),
                    mock.call('partx', '-av', '/dev/fake', attempts=3,
                              delay_on_retry=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call('efibootmgr', '-v'),
                    mock.call('efibootmgr', '-b', '0000', '-B'),
                    mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
                              '-p', '1', '-w',
                              '-L', 'ironic1', '-l',
                              '\\EFI\\BOOT\\BOOTX64.EFI'),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call('sync')]

        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None).join()

        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_utils_efi_part.assert_called_once_with(self.fake_dev)
        self.assertEqual(9, mock_execute.call_count)

    @mock.patch.object(hardware, 'is_md_device', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    @mock.patch.object(efi_utils, '_get_efi_bootloaders', autospec=True)
    @mock.patch.object(efi_utils, 'get_partition_path_by_number',
                       autospec=True)
    @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    def test__uefi_bootloader_with_entry_removal_lenovo(
            self, mkdir_mock, mock_utils_efi_part, mock_get_part_path,
            mock_efi_bl, mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_get_part_path.return_value = self.fake_efi_system_part
        mock_utils_efi_part.return_value = {'number': '1'}
        mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI']
        # NOTE(TheJulia): This test string was derived from a lenovo SR650
        # which does do some weird things with additional entries.
        # most notabley
        stdout_msg = """
BootCurrent: 0000
Timeout: 1 seconds
BootOrder: 0000,0003,0002,0001
Boot0000* ironic1       HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x64000)/File(\EFI\boot\shimx64.efi)
Boot0001* CD/DVD Rom    VenHw(1fad3248-0000-7950-2166-a1e506fdb83a,02000000)..GO
Boot0002* Hard Disk     VenHw(1fad3248-0000-7950-2166-a1e506fdb83a,01000000)..GO..NO..........V.U.E.F.I.:. . . .S.C.S.I. .H.a.r.d. .D.r.i.v.e........A....................................*..............@.........U..[J.Uy...H.......BO
Boot0003* Network       VenHw(1fad3248-0000-7950-2166-a1e506fdb83a,05000000)..GO..NO............U.E.F.I.:. . . .S.L.O.T.2. .(.2.F./.0./.0.). .P.X.E. .I.P.4. . .Q.L.o.g.i.c. .Q.L.4.1.2.6.2. .P.C.I.e. .2.5.G.b. .2.-.P.o.r.t. .S.F.P.2.8. .E.t.h.e.r.n.e.t. .A.d.a.p.t.e.r. .-. .P.X.E........A....................%.4..Z...............................................................Gd-.;.A..MQ..L.P.X.E. .I.P.4. .Q.L.o.g.i.c. .Q.L.4.1.2.6.2. .P.C.I.e. .2.5.G.b. .2.-.P.o.r.t. .S.F.P.2.8. .E.t.h.e.r.n.e.t. .A.d.a.p.t.e.r. .-. .P.X.E.......BO..NO............U.E.F.I.:. . . .S.L.O.T.1. .(.3.0./.0./.0.). .P.X.E. .I.P.4. . .Q.L.o.g.i.c. .Q.L.4.1.2.6.2. .P.C.I.e. .2.5.G.b. .2.-.P.o.r.t. .S.F.P.2.8. .E.t.h.e.r.n.e.t. .A.d.a.p.t.e.r. .-.
Boot0004* ironic1      HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x64000)/File(\EFI\boot\shimx64.efi)
"""  # noqa This is a giant literal string for testing.
        mock_execute.side_effect = iter([('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         (stdout_msg, ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', '')])
        expected = [mock.call('efibootmgr', '--version'),
                    mock.call('partx', '-av', '/dev/fake', attempts=3,
                              delay_on_retry=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call('efibootmgr', '-v'),
                    mock.call('efibootmgr', '-b', '0000', '-B'),
                    mock.call('efibootmgr', '-b', '0004', '-B'),
                    mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
                              '-p', '1', '-w',
                              '-L', 'ironic1', '-l',
                              '\\EFI\\BOOT\\BOOTX64.EFI'),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call('sync')]

        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None).join()

        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_utils_efi_part.assert_called_once_with(self.fake_dev)
        self.assertEqual(10, mock_execute.call_count)

    @mock.patch.object(hardware, 'is_md_device', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    @mock.patch.object(efi_utils, '_get_efi_bootloaders', autospec=True)
    @mock.patch.object(efi_utils, 'get_partition_path_by_number',
                       autospec=True)
    @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    def test__add_multi_bootloaders(
            self, mkdir_mock, mock_utils_efi_part, mock_get_part_path,
            mock_efi_bl, mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_get_part_path.return_value = self.fake_efi_system_part
        mock_utils_efi_part.return_value = {'number': '1'}
        mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI',
                                    'WINDOWS/system32/winload.efi']

        mock_execute.side_effect = iter([('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', ''), ('', ''),
                                         ('', '')])

        expected = [mock.call('efibootmgr', '--version'),
                    mock.call('partx', '-av', '/dev/fake', attempts=3,
                              delay_on_retry=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call('efibootmgr', '-v'),
                    mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
                              '-p', '1', '-w',
                              '-L', 'ironic1', '-l',
                              '\\EFI\\BOOT\\BOOTX64.EFI'),
                    mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
                              '-p', '1', '-w',
                              '-L', 'ironic2', '-l',
                              '\\WINDOWS\\system32\\winload.efi'),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call('sync')]

        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None).join()

        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_utils_efi_part.assert_called_once_with(self.fake_dev)
        self.assertEqual(9, mock_execute.call_count)

    @mock.patch.object(image, '_install_grub2', autospec=True)
    def test__install_bootloader_prep(self, mock_grub2,
                                      mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='bios')
        ]
        self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None,
            prep_boot_part_uuid=self.fake_prep_boot_part_uuid).join()
        mock_dispatch.assert_any_call('get_os_install_device')
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(2, mock_dispatch.call_count)
        mock_grub2.assert_called_once_with(
            self.fake_dev,
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None,
            prep_boot_part_uuid=self.fake_prep_boot_part_uuid,
            target_boot_mode='bios'
        )

    @mock.patch.object(hardware, 'is_md_device', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    def test_install_bootloader_failure(self, mock_execute, mock_dispatch):
        mock_dispatch.side_effect = [
            self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
        ]
        mock_execute.side_effect = FileNotFoundError
        result = self.agent_extension.install_bootloader(
            root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None).join()
        self.assertIsNotNone(result.command_error)
        expected = [mock.call('efibootmgr', '--version')]
        mock_execute.assert_has_calls(expected)

    @mock.patch.object(os.path, 'exists', lambda *_: True)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2(self, mock_get_part_uuid, environ_mock,
                            mock_md_get_raid_devices, mock_is_md_device,
                            mock_append_to_fstab, mock_execute,
                            mock_dispatch):
        mock_get_part_uuid.return_value = self.fake_root_part
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        image._install_grub2(self.fake_dev, self.fake_root_uuid)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub2-install %s"' %
                               (self.fake_dir, self.fake_dev)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_called_once_with(self.fake_dev,
                                                   uuid=self.fake_root_uuid)
        self.assertFalse(mock_dispatch.called)
        self.assertFalse(mock_append_to_fstab.called)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_prep(self, mock_get_part_uuid, environ_mock,
                                 mock_md_get_raid_devices, mock_is_md_device,
                                 mock_execute, mock_dispatch):
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_prep_boot_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        image._install_grub2(self.fake_dev, self.fake_root_uuid,
                             prep_boot_part_uuid=self.fake_prep_boot_part_uuid)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub-install %s"' %
                               (self.fake_dir, self.fake_prep_boot_part)),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub-mkconfig -o '
                               '/boot/grub/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_prep_boot_part_uuid)
        self.assertFalse(mock_dispatch.called)

    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(os.path, 'exists', lambda *_: False)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi(self, mock_get_part_uuid, mkdir_mock,
                                 environ_mock, mock_md_get_raid_devices,
                                 mock_is_md_device, mock_append_to_fstab,
                                 mock_execute, mock_dispatch):
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', self.fake_efi_system_part,
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub-mkconfig -o '
                               '/boot/grub/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_append_to_fstab.assert_called_with(self.fake_dir,
                                                self.fake_efi_system_part_uuid)

    @mock.patch.object(hardware, 'get_multipath_status', lambda *_: False)
    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_fstab(self, mock_get_part_uuid, mkdir_mock,
                                       environ_mock, mock_md_get_raid_devices,
                                       mock_is_md_device, mock_exists,
                                       mock_execute, mock_dispatch):
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        mock_exists.side_effect = iter([False, True, False, True, True])
        mock_execute.return_value = (hws.PARTUUID_DEVICE_TEMPLATE, '')

        with mock.patch('builtins.open', mock.mock_open()) as mock_open:
            image._install_grub2(
                self.fake_dev, root_uuid=self.fake_root_uuid,
                efi_system_part_uuid=self.fake_efi_system_part_uuid,
                target_boot_mode='uefi')
            write_calls = [
                mock.call(self.fake_dir + '/etc/fstab', 'r+'),
                mock.call().__enter__(),
                mock.call().read(),
                mock.call().writelines('UUID=%s\t/boot/efi\tvfat\t'
                                       'umask=0077\t0\t1'
                                       '\n' % self.fake_efi_system_part_uuid),
                mock.call().__exit__(None, None, None)]
            mock_open.assert_has_calls(write_calls)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub2-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub2-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', self.fake_efi_system_part,
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('lsblk', '-bia', '--json',
                              '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
                              check_exit_code=[0]),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)

    @mock.patch.object(hardware, 'get_multipath_status', lambda *_: False)
    @mock.patch.object(image, '_efi_boot_setup', lambda *_: False)
    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_no_fstab(
            self, mock_get_part_uuid,
            mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_execute, mock_dispatch):
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        mock_execute.return_value = (hws.PARTUUID_DEVICE_TEMPLATE, '')
        # Validates the complete opposite path *and* no-write behavior
        # occurs if the entry already exists.
        fstab_data = (
            'PARTUUID=%s\tpath vfat option' % self.fake_efi_system_part_uuid)
        mock_exists.side_effect = [True, False, True, True, True, False,
                                   True, True]
        with mock.patch('builtins.open',
                        mock.mock_open(read_data=fstab_data)) as mock_open:
            image._install_grub2(
                self.fake_dev, root_uuid=self.fake_root_uuid,
                efi_system_part_uuid=self.fake_efi_system_part_uuid,
                target_boot_mode='uefi')
            write_calls = [
                mock.call(self.fake_dir + '/etc/fstab', 'r+'),
                mock.call().__enter__(),
                mock.call().read(),
                mock.call().__exit__(None, None, None)]
            mock_open.assert_has_calls(write_calls)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('umount', self.fake_dir + '/boot/efi'),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    # NOTE(TheJulia): chroot mount is for whole disk images
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub2-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub2-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', self.fake_efi_system_part,
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('lsblk', '-bia', '--json',
                              '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
                              check_exit_code=[0]),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)

    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2'])
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(image, '_efi_boot_setup', autospec=True)
    @mock.patch.object(shutil, 'copytree', autospec=True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_partition_image_with_loader(
            self, mock_get_part_uuid, mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_copytree, mock_efi_setup,
            mock_append_to_fstab, mock_execute,
            mock_dispatch):
        mock_exists.side_effect = [True, False, True, True, True, False, True,
                                   False, False]
        mock_efi_setup.return_value = True
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')
        mock_efi_setup.assert_called_once_with(self.fake_dev,
                                               self.fake_efi_system_part_uuid)
        mock_copytree.assert_has_calls([
            mock.call(self.fake_dir + '/boot/efi/EFI',
                      self.fake_dir + '/efi_loader'),
            mock.call(self.fake_dir + '/efi_loader',
                      self.fake_dir + '/boot/efi/EFI')])

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call('chroot %s /bin/sh -c "grub2-mkconfig -o '
                              '/boot/grub2/grub.cfg"' % self.fake_dir,
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('mount', '-t', 'vfat', '/dev/fake1',
                              self.fake_dir + '/boot/efi'),
                    mock.call('umount', self.fake_dir + '/boot/efi'),
                    mock.call('chroot %s /bin/sh -c "umount -a -t '
                              'vfat"' % self.fake_dir, shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mkdir_mock.assert_not_called()
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_append_to_fstab.assert_called_with(self.fake_dir,
                                                self.fake_efi_system_part_uuid)

    @mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2'])
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(shutil, 'copy2', autospec=True)
    @mock.patch.object(os.path, 'isfile', autospec=True)
    @mock.patch.object(image, '_efi_boot_setup', autospec=True)
    @mock.patch.object(shutil, 'copytree', autospec=True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_partition_image_with_loader_with_grubcfg(
            self, mock_get_part_uuid, mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_copytree, mock_efi_setup,
            mock_isfile, mock_copy2,
            mock_fstab_append, mock_execute,
            mock_dispatch):
        mock_exists.return_value = True
        mock_efi_setup.return_value = True
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        mock_isfile.side_effect = [True, False, False, True, True, False]

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')
        mock_efi_setup.assert_called_once_with(self.fake_dev,
                                               self.fake_efi_system_part_uuid)
        mock_copytree.assert_has_calls([
            mock.call(self.fake_dir + '/boot/efi/EFI',
                      self.fake_dir + '/efi_loader'),
            mock.call(self.fake_dir + '/efi_loader',
                      self.fake_dir + '/boot/efi/EFI')])

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot ' + self.fake_dir + ' /bin/sh -c '
                               '"grub2-mkconfig -o /boot/grub2/grub.cfg"'),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('mount', '-t', 'vfat', '/dev/fake1',
                              self.fake_dir + '/boot/efi'),
                    mock.call('umount', self.fake_dir + '/boot/efi'),
                    mock.call(('chroot ' + self.fake_dir
                               + ' /bin/sh -c "umount -a -t vfat"'),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mkdir_mock.assert_not_called()
        mock_execute.assert_has_calls(expected)
        mock_copy2.assert_has_calls([])
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        mock_fstab_append.assert_called_once_with(
            self.fake_dir,
            self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)

    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(image, '_preserve_efi_assets', autospec=True)
    @mock.patch.object(image, '_efi_boot_setup', autospec=True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_partition_image_with_preserve_failure(
            self, mock_get_part_uuid, mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_efi_setup,
            mock_preserve_efi_assets,
            mock_append_to_fstab,
            mock_execute, mock_dispatch):
        mock_exists.return_value = True
        mock_efi_setup.side_effect = Exception('meow')
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        mock_preserve_efi_assets.return_value = False

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')
        self.assertFalse(mock_efi_setup.called)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub2-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub2-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', self.fake_efi_system_part,
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]

        mkdir_mock.assert_not_called()
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_preserve_efi_assets.assert_called_with(
            self.fake_dir,
            self.fake_dir + '/boot/efi/EFI',
            '/dev/fake1',
            self.fake_dir + '/boot/efi')
        mock_append_to_fstab.assert_called_with(self.fake_dir,
                                                self.fake_efi_system_part_uuid)

    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(image, '_preserve_efi_assets', autospec=True)
    @mock.patch.object(image, '_efi_boot_setup', autospec=True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_partition_image_with_preserve_failure2(
            self, mock_get_part_uuid, mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_efi_setup,
            mock_preserve_efi_assets,
            mock_append_to_fstab,
            mock_execute, mock_dispatch):
        mock_exists.return_value = True
        mock_efi_setup.side_effect = Exception('meow')
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        mock_preserve_efi_assets.return_value = None
        exec_results = [('', '')] * 21
        already_exists = processutils.ProcessExecutionError(
            '/dev is already mounted at /path')
        # Mark mounts as already mounted, which is where os.path.ismount
        # usage corresponds.
        exec_results[6] = already_exists
        exec_results[8] = already_exists

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')
        self.assertFalse(mock_efi_setup.called)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub2-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub2-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', self.fake_efi_system_part,
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]

        mkdir_mock.assert_not_called()
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_preserve_efi_assets.assert_called_with(
            self.fake_dir,
            self.fake_dir + '/boot/efi/EFI',
            '/dev/fake1',
            self.fake_dir + '/boot/efi')
        mock_append_to_fstab.assert_called_with(self.fake_dir,
                                                self.fake_efi_system_part_uuid)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(os, 'listdir', autospec=True)
    @mock.patch.object(shutil, 'copy2', autospec=True)
    @mock.patch.object(os.path, 'isfile', autospec=True)
    @mock.patch.object(image, '_efi_boot_setup', autospec=True)
    @mock.patch.object(shutil, 'copytree', autospec=True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_partition_image_with_loader_grubcfg_fails(
            self, mock_get_part_uuid, mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_copytree, mock_efi_setup,
            mock_isfile, mock_copy2,
            mock_oslistdir, mock_append_to_fstab,
            mock_execute, mock_dispatch):
        mock_exists.return_value = True
        mock_efi_setup.return_value = True
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}
        mock_isfile.side_effect = [True, False, False, True, False,
                                   True, False]
        mock_copy2.side_effect = OSError('copy failed')
        mock_oslistdir.return_value = ['file1', 'file2']

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')
        mock_efi_setup.assert_called_once_with(self.fake_dev,
                                               self.fake_efi_system_part_uuid)
        mock_copytree.assert_has_calls([
            mock.call(self.fake_dir + '/boot/efi/EFI',
                      self.fake_dir + '/efi_loader'),
            mock.call(self.fake_dir + '/efi_loader',
                      self.fake_dir + '/boot/efi/EFI')])

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot ' + self.fake_dir + ' /bin/sh -c '
                               '"grub2-mkconfig -o /boot/grub2/grub.cfg"'),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('mount', '-t', 'vfat', '/dev/fake1',
                              self.fake_dir + '/boot/efi'),
                    mock.call('umount', self.fake_dir + '/boot/efi'),
                    mock.call(('chroot ' + self.fake_dir
                               + ' /bin/sh -c "umount -a -t vfat"'),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys', attempts=3,
                              delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mkdir_mock.assert_not_called()
        mock_execute.assert_has_calls(expected)
        self.assertEqual(3, mock_copy2.call_count)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        self.assertEqual(2, mock_oslistdir.call_count)
        mock_append_to_fstab.assert_called_with(self.fake_dir,
                                                self.fake_efi_system_part_uuid)

    @mock.patch.object(os.path, 'ismount', lambda *_: False)
    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(os, 'listdir', autospec=True)
    @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
    @mock.patch.object(image, '_efi_boot_setup', autospec=True)
    @mock.patch.object(shutil, 'copytree', autospec=True)
    @mock.patch.object(os.path, 'exists', autospec=True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_partition_image_with_no_loader(
            self, mock_get_part_uuid, mkdir_mock,
            environ_mock, mock_md_get_raid_devices,
            mock_is_md_device, mock_exists,
            mock_copytree, mock_efi_setup,
            mock_append_to_fstab, mock_oslistdir,
            mock_execute, mock_dispatch):
        mock_exists.side_effect = [True, False, False, True, True, True, True]
        mock_efi_setup.side_effect = Exception('meow')
        mock_oslistdir.return_value = ['file1']
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call('mount', '-t', 'vfat', '/dev/fake1',
                              self.fake_dir + '/boot/efi'),
                    mock.call('umount', self.fake_dir + '/boot/efi'),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub2-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub2-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', self.fake_efi_system_part,
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub2-mkconfig -o '
                               '/boot/grub2/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]

        mkdir_mock.assert_not_called()
        mock_execute.assert_has_calls(expected)
        self.assertEqual(2, mock_copytree.call_count)
        self.assertTrue(mock_efi_setup.called)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_append_to_fstab.assert_called_with(self.fake_dir,
                                                self.fake_efi_system_part_uuid)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_umount_fails(
            self, mock_get_part_uuid, mkdir_mock, environ_mock,
            mock_md_get_raid_devices, mock_is_md_device, mock_execute,
            mock_dispatch):
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        mock_is_md_device.return_value = False
        mock_md_get_raid_devices.return_value = {}

        def umount_raise_func(*args, **kwargs):
            if args[0] == 'umount':
                raise processutils.ProcessExecutionError('error')

        mock_execute.side_effect = umount_raise_func
        environ_mock.get.return_value = '/sbin'
        self.assertRaises(errors.CommandExecutionError,
                          image._install_grub2,
                          self.fake_dev, root_uuid=self.fake_root_uuid,
                          efi_system_part_uuid=self.fake_efi_system_part_uuid)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', self.fake_efi_system_part,
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                              '"grub-install --removable"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    # Call from for loop
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    # Call from finally
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True)
                    ]
        mock_execute.assert_has_calls(expected)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_uefi_mount_fails(
            self, mock_get_part_uuid, mkdir_mock, environ_mock,
            mock_is_md_device, mock_md_get_raid_devices, mock_execute,
            mock_dispatch):
        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        mock_is_md_device.side_effect = [False, False]
        environ_mock.get.return_value = '/sbin'
        mock_md_get_raid_devices.return_value = {}

        def mount_raise_func(*args, **kwargs):
            if args[0] == 'mount':
                raise processutils.ProcessExecutionError('error')

        mock_execute.side_effect = mount_raise_func
        self.assertRaises(errors.CommandExecutionError,
                          image._install_grub2,
                          self.fake_dev, root_uuid=self.fake_root_uuid,
                          efi_system_part_uuid=self.fake_efi_system_part_uuid)

        expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mock_execute.assert_has_calls(expected)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    def test__install_grub2_command_fail(self, mock_get_part_uuid,
                                         mock_execute,
                                         mock_dispatch):
        mock_get_part_uuid.return_value = self.fake_root_part
        mock_execute.side_effect = processutils.ProcessExecutionError('boom')

        self.assertRaises(errors.CommandExecutionError, image._install_grub2,
                          self.fake_dev, self.fake_root_uuid)

        mock_get_part_uuid.assert_called_once_with(self.fake_dev,
                                                   uuid=self.fake_root_uuid)
        self.assertFalse(mock_dispatch.called)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_restart', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(hardware, 'get_holder_disks', autospec=True,
                       return_value=['/dev/sda', '/dev/sdb'])
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    @mock.patch.object(raid_utils, 'prepare_boot_partitions_for_softraid',
                       autospec=True,
                       return_value='/dev/md/esp')
    @mock.patch.object(image, '_has_dracut',
                       autospec=True,
                       return_value=False)
    def test__install_grub2_softraid_uefi_gpt(
            self, mock_dracut,
            mock_prepare, mock_get_part_uuid, mkdir_mock, environ_mock,
            mock_holder, mock_md_get_raid_devices, mock_restart,
            mock_is_md_device,
            mock_execute, mock_dispatch):

        # return success for every execute call
        mock_execute.side_effect = [('', '')] * 21

        # make grub2-install calls fail
        grub_failure = processutils.ProcessExecutionError(
            stdout='',
            stderr='grub2-install: error: this utility cannot be used '
                   'for EFI platforms because it does not support '
                   'UEFI Secure Boot.\n',
            exit_code=1,
            cmd='grub2-install'
        )
        mock_execute.side_effect[9] = grub_failure
        mock_execute.side_effect[10] = grub_failure

        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = True
        mock_md_get_raid_devices.return_value = {}

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=self.fake_efi_system_part_uuid,
            target_boot_mode='uefi')

        expected = [mock.call('partx', '-av', '/dev/fake', attempts=3,
                              delay_on_retry=True),
                    mock.call('udevadm', 'settle'),
                    mock.call('mount', '/dev/fake2', self.fake_dir),
                    mock.call('mount', '-o', 'bind', '/dev',
                              self.fake_dir + '/dev'),
                    mock.call('mount', '-o', 'bind', '/proc',
                              self.fake_dir + '/proc'),
                    mock.call('mount', '-o', 'bind', '/run',
                              self.fake_dir + '/run'),
                    mock.call('mount', '-t', 'sysfs', 'none',
                              self.fake_dir + '/sys'),
                    mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('mount', '/dev/md/esp',
                              self.fake_dir + '/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c "grub-install"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub-install --removable"' %
                               self.fake_dir), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call(
                        'umount', self.fake_dir + '/boot/efi',
                        attempts=3, delay_on_retry=True),
                    mock.call('mount', '/dev/md/esp',
                              '/tmp/fake-dir/boot/efi'),
                    mock.call(('chroot %s /bin/sh -c '
                               '"grub-mkconfig -o '
                               '/boot/grub/grub.cfg"' % self.fake_dir),
                              shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                                  'GRUB_DISABLE_OS_PROBER': 'true',
                                  'GRUB_SAVEDEFAULT': 'true'},
                              use_standard_locale=True),
                    mock.call('umount', self.fake_dir + '/boot/efi',
                              attempts=3, delay_on_retry=True),
                    mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                              (self.fake_dir)), shell=True,
                              env_variables={
                                  'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
                    mock.call('umount', self.fake_dir + '/dev',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/proc',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/run',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir + '/sys',
                              attempts=3, delay_on_retry=True),
                    mock.call('umount', self.fake_dir, attempts=3,
                              delay_on_retry=True)]
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_efi_system_part_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_prepare.assert_called_once_with(self.fake_dev,
                                             ['/dev/sda', '/dev/sdb'],
                                             self.fake_efi_system_part, 'uefi')
        mock_restart.assert_called_once_with(self.fake_dev)
        mock_holder.assert_called_once_with(self.fake_dev)
        mock_dracut.assert_called_once_with(self.fake_dir)

    @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
    @mock.patch.object(hardware, 'is_md_device', autospec=True)
    @mock.patch.object(hardware, 'md_restart', autospec=True)
    @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
    @mock.patch.object(hardware, 'get_holder_disks', autospec=True,
                       return_value=['/dev/sda', '/dev/sdb'])
    @mock.patch.object(os, 'environ', autospec=True)
    @mock.patch.object(os, 'makedirs', autospec=True)
    @mock.patch.object(partition_utils, 'get_partition', autospec=True)
    @mock.patch.object(raid_utils, 'prepare_boot_partitions_for_softraid',
                       autospec=True,
                       return_value=[])
    @mock.patch.object(image, '_has_dracut',
                       autospec=True,
                       return_value=False)
    def test__install_grub2_softraid_bios(
            self, mock_dracut,
            mock_prepare, mock_get_part_uuid, mkdir_mock, environ_mock,
            mock_holder, mock_md_get_raid_devices, mock_restart,
            mock_is_md_device,
            mock_execute, mock_dispatch):

        mock_get_part_uuid.side_effect = [self.fake_root_part,
                                          self.fake_efi_system_part]
        environ_mock.get.return_value = '/sbin'
        mock_is_md_device.return_value = True
        mock_md_get_raid_devices.return_value = {}

        image._install_grub2(
            self.fake_dev, root_uuid=self.fake_root_uuid,
            efi_system_part_uuid=None,
            target_boot_mode='bios')

        expected = [
            mock.call('mount', '/dev/fake2', self.fake_dir),
            mock.call('mount', '-o', 'bind', '/dev',
                      self.fake_dir + '/dev'),
            mock.call('mount', '-o', 'bind', '/proc',
                      self.fake_dir + '/proc'),
            mock.call('mount', '-o', 'bind', '/run',
                      self.fake_dir + '/run'),
            mock.call('mount', '-t', 'sysfs', 'none',
                      self.fake_dir + '/sys'),
            mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
                       (self.fake_dir)), shell=True,
                      env_variables={
                          'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
            mock.call(('chroot %s /bin/sh -c '
                       '"grub-install %s"' %
                       (self.fake_dir, '/dev/sda')), shell=True,
                      env_variables={
                          'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
            mock.call(('chroot %s /bin/sh -c '
                       '"grub-install %s"' %
                       (self.fake_dir, '/dev/sdb')), shell=True,
                      env_variables={
                          'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
            mock.call(('chroot %s /bin/sh -c '
                       '"grub-mkconfig -o '
                       '/boot/grub/grub.cfg"' % self.fake_dir),
                      shell=True,
                      env_variables={
                          'PATH': '/sbin:/bin:/usr/sbin:/sbin',
                          'GRUB_DISABLE_OS_PROBER': 'true',
                          'GRUB_SAVEDEFAULT': 'true'},
                      use_standard_locale=True),
            mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
                      (self.fake_dir)), shell=True,
                      env_variables={
                          'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
            mock.call('umount', self.fake_dir + '/dev',
                      attempts=3, delay_on_retry=True),
            mock.call('umount', self.fake_dir + '/proc',
                      attempts=3, delay_on_retry=True),
            mock.call('umount', self.fake_dir + '/run',
                      attempts=3, delay_on_retry=True),
            mock.call('umount', self.fake_dir + '/sys',
                      attempts=3, delay_on_retry=True),
            mock.call('umount', self.fake_dir, attempts=3,
                      delay_on_retry=True)]
        self.assertFalse(mkdir_mock.called)
        mock_execute.assert_has_calls(expected)
        mock_get_part_uuid.assert_any_call(self.fake_dev,
                                           uuid=self.fake_root_uuid)
        self.assertFalse(mock_dispatch.called)
        mock_prepare.assert_called_once_with(self.fake_dev,
                                             ['/dev/sda', '/dev/sdb'],
                                             None, 'bios')
        mock_restart.assert_called_once_with(self.fake_dev)
        mock_holder.assert_called_once_with(self.fake_dev)
        mock_dracut.assert_called_once_with(self.fake_dir)

    def test__is_bootloader_loaded(self, mock_execute,
                                   mock_dispatch):
        mock_dispatch.return_value = hardware.BootInfo(
            current_boot_mode='bios')
        parted_output = ('BYT;\n'
                         '/dev/loop1:46.1MB:loopback:512:512:gpt:Loopback '
                         'device:;\n'
                         '15:1049kB:9437kB:8389kB:::boot;\n'
                         '1:9437kB:46.1MB:36.7MB:ext3::;\n')
        disk_file_output = ('/dev/loop1: partition 1: ID=0xee, starthead 0, '
                            'startsector 1, 90111 sectors, extended '
                            'partition table (last)\011, code offset 0x48')

        part_file_output = ('/dev/loop1p15: x86 boot sector, mkdosfs boot '
                            'message display, code offset 0x3c, OEM-ID '
                            '"mkfs.fat", sectors/cluster 8, root entries '
                            '512, sectors 16384 (volumes <=32 MB) , Media '
                            'descriptor 0xf8, sectors/FAT 8, heads 255, '
                            'serial number 0x23a08feb, unlabeled, '
                            'FAT (12 bit)')

        # NOTE(TheJulia): File evaluates this data, so it is pointless to
        # try and embed the raw bytes in the test.
        dd_output = ('')

        file_output = ('/dev/loop1: DOS executable (COM)\n')

        mock_execute.side_effect = iter([(parted_output, ''),
                                         (disk_file_output, ''),
                                         (part_file_output, ''),
                                         (dd_output, ''),
                                         (file_output, '')])

        result = image._is_bootloader_loaded(self.fake_dev)
        self.assertTrue(result)

    def test__is_bootloader_loaded_not_bootable(self,
                                                mock_execute,
                                                mock_dispatch):
        parted_output = ('BYT;\n'
                         '/dev/loop1:46.1MB:loopback:512:512:gpt:Loopback '
                         'device:;\n'
                         '15:1049kB:9437kB:8389kB:::;\n'
                         '1:9437kB:46.1MB:36.7MB:ext3::;\n')
        mock_execute.return_value = (parted_output, '')
        result = image._is_bootloader_loaded(self.fake_dev)
        self.assertFalse(result)

    def test__is_bootloader_loaded_empty(self,
                                         mock_execute,
                                         mock_dispatch):
        parted_output = ('BYT;\n'
                         '/dev/loop1:46.1MB:loopback:512:512:gpt:Loopback '
                         'device:;\n')
        mock_execute.return_value = (parted_output, '')
        result = image._is_bootloader_loaded(self.fake_dev)
        self.assertFalse(result)

    def test__is_bootloader_loaded_uefi_mode(self, mock_execute,
                                             mock_dispatch):

        mock_dispatch.return_value = hardware.BootInfo(
            current_boot_mode='uefi')
        result = image._is_bootloader_loaded(self.fake_dev)
        self.assertFalse(result)
        mock_dispatch.assert_any_call('get_boot_info')
        self.assertEqual(0, mock_execute.call_count)

    @mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
    @mock.patch.object(os.path, 'exists', lambda *_: True)
    def test__append_uefi_to_fstab_handles_error(self, mock_execute,
                                                 mock_dispatch,
                                                 mock_list_blk_devs):
        with mock.patch('builtins.open', mock.mock_open()) as mock_open:
            mock_open.side_effect = OSError('boom')
            image._append_uefi_to_fstab(
                self.fake_dir, 'abcd-efgh')
