#    Copyright (c) 2012 NTT DOCOMO, INC.
#    Copyright 2011 OpenStack Foundation
#    Copyright 2011 Ilya Alekseyev
#
#    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 tempfile
from unittest import mock

import fixtures
from oslo_config import cfg
from oslo_utils import fileutils
from oslo_utils import uuidutils

from ironic.common import boot_devices
from ironic.common import checksum_utils
from ironic.common import exception
from ironic.common import faults
from ironic.common import image_service
from ironic.common import states
from ironic.common import utils as common_utils
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import deploy_utils as utils
from ironic.drivers.modules import fake
from ironic.drivers.modules import image_cache
from ironic.drivers.modules.network import common as n_common
from ironic.drivers.modules import pxe
from ironic.drivers.modules.storage import cinder
from ironic.drivers import utils as driver_utils
from ironic.tests import base as tests_base
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils

INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()

_PXECONF_DEPLOY = b"""
default deploy

label deploy
kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3

label boot_whole_disk
COM32 chain.c32
append mbr:{{ DISK_IDENTIFIER }}
"""

_PXECONF_BOOT_WHOLE_DISK = """
default boot_whole_disk

label deploy
kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3

label boot_whole_disk
COM32 chain.c32
append mbr:0x12345678
"""

_IPXECONF_DEPLOY = b"""
#!ipxe

dhcp

goto deploy

:deploy
kernel deploy_kernel
initrd deploy_ramdisk
boot

:boot_whole_disk
kernel chain.c32
append mbr:{{ DISK_IDENTIFIER }}
boot
"""

_IPXECONF_BOOT_WHOLE_DISK = """
#!ipxe

dhcp

goto boot_whole_disk

:deploy
kernel deploy_kernel
initrd deploy_ramdisk
boot

:boot_whole_disk
kernel chain.c32
append mbr:0x12345678
boot
"""

_IPXECONF_BOOT_ISCSI_NO_CONFIG = """
#!ipxe

dhcp

goto boot_iscsi

:deploy
kernel deploy_kernel
initrd deploy_ramdisk
boot

:boot_whole_disk
kernel chain.c32
append mbr:{{ DISK_IDENTIFIER }}
boot
"""

_UEFI_PXECONF_DEPLOY = b"""
default=deploy

image=deploy_kernel
        label=deploy
        initrd=deploy_ramdisk
        append="ro text"

image=chain.c32
        label=boot_whole_disk
        append="mbr:{{ DISK_IDENTIFIER }}"
"""

_UEFI_PXECONF_BOOT_WHOLE_DISK = """
default=boot_whole_disk

image=deploy_kernel
        label=deploy
        initrd=deploy_ramdisk
        append="ro text"

image=chain.c32
        label=boot_whole_disk
        append="mbr:0x12345678"
"""

_UEFI_PXECONF_DEPLOY_GRUB = b"""
set default=deploy
set timeout=5
set hidden_timeout_quiet=false

menuentry "deploy"  {
    linux deploy_kernel "ro text"
    initrd deploy_ramdisk
}

menuentry "boot_whole_disk"  {
    linux chain.c32 mbr:(( DISK_IDENTIFIER ))
}
"""

_UEFI_PXECONF_BOOT_WHOLE_DISK_GRUB = """
set default=boot_whole_disk
set timeout=5
set hidden_timeout_quiet=false

menuentry "deploy"  {
    linux deploy_kernel "ro text"
    initrd deploy_ramdisk
}

menuentry "boot_whole_disk"  {
    linux chain.c32 mbr:0x12345678
}
"""


class SwitchPxeConfigTestCase(tests_base.TestCase):

    # NOTE(TheJulia): Remove elilo support after the deprecation period,
    # in the Queens release.
    def _create_config(self, ipxe=False, boot_mode=None, boot_loader='elilo'):
        (fd, fname) = tempfile.mkstemp()
        if boot_mode == 'uefi' and not ipxe:
            if boot_loader == 'grub':
                pxe_cfg = _UEFI_PXECONF_DEPLOY_GRUB
            else:
                pxe_cfg = _UEFI_PXECONF_DEPLOY
        else:
            pxe_cfg = _IPXECONF_DEPLOY if ipxe else _PXECONF_DEPLOY
        os.write(fd, pxe_cfg)
        os.close(fd)
        self.addCleanup(os.unlink, fname)
        return fname

    def test_switch_pxe_config_whole_disk_image(self):
        boot_mode = 'bios'
        fname = self._create_config()
        utils.switch_pxe_config(fname,
                                '0x12345678',
                                boot_mode,
                                True)
        with open(fname, 'r') as f:
            pxeconf = f.read()
        self.assertEqual(_PXECONF_BOOT_WHOLE_DISK, pxeconf)

    def test_switch_ipxe_config_whole_disk_image(self):
        boot_mode = 'bios'
        fname = self._create_config(ipxe=True)
        utils.switch_pxe_config(fname,
                                '0x12345678',
                                boot_mode,
                                True,
                                ipxe_enabled=True)
        with open(fname, 'r') as f:
            pxeconf = f.read()
        self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)

    # NOTE(TheJulia): Remove elilo support after the deprecation period,
    # in the Queens release.
    def test_switch_uefi_elilo_config_whole_disk_image(self):
        boot_mode = 'uefi'
        fname = self._create_config(boot_mode=boot_mode)
        utils.switch_pxe_config(fname,
                                '0x12345678',
                                boot_mode,
                                True)
        with open(fname, 'r') as f:
            pxeconf = f.read()
        self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK, pxeconf)

    def test_switch_uefi_grub_config_whole_disk_image(self):
        boot_mode = 'uefi'
        fname = self._create_config(boot_mode=boot_mode, boot_loader='grub')
        utils.switch_pxe_config(fname,
                                '0x12345678',
                                boot_mode,
                                True)
        with open(fname, 'r') as f:
            pxeconf = f.read()
        self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK_GRUB, pxeconf)

    def test_switch_uefi_ipxe_config_whole_disk_image(self):
        boot_mode = 'uefi'
        fname = self._create_config(boot_mode=boot_mode, ipxe=True)
        utils.switch_pxe_config(fname,
                                '0x12345678',
                                boot_mode,
                                True,
                                ipxe_enabled=True)
        with open(fname, 'r') as f:
            pxeconf = f.read()
        self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)

    def test_switch_ipxe_iscsi_boot(self):
        boot_mode = 'iscsi'
        fname = self._create_config(boot_mode=boot_mode, ipxe=True)
        utils.switch_pxe_config(fname,
                                '0x12345678',
                                boot_mode,
                                is_whole_disk_image=False,
                                iscsi_boot=True,
                                ipxe_enabled=True)
        with open(fname, 'r') as f:
            pxeconf = f.read()
        self.assertEqual(_IPXECONF_BOOT_ISCSI_NO_CONFIG, pxeconf)


class GetPxeBootConfigTestCase(db_base.DbTestCase):

    def setUp(self):
        super(GetPxeBootConfigTestCase, self).setUp()
        self.node = obj_utils.get_test_node(self.context,
                                            driver='fake-hardware')
        self.config(pxe_bootfile_name='bios-bootfile', group='pxe')
        self.config(uefi_pxe_bootfile_name='uefi-bootfile', group='pxe')
        self.config(pxe_config_template='bios-template', group='pxe')
        self.config(uefi_pxe_config_template='uefi-template', group='pxe')
        self.bootfile_by_arch = {'aarch64': 'aarch64-bootfile',
                                 'ppc64': 'ppc64-bootfile'}
        self.template_by_arch = {'aarch64': 'aarch64-template',
                                 'ppc64': 'ppc64-template'}

    def test_get_pxe_boot_file_bios_without_by_arch(self):
        properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
        self.node.properties = properties
        self.config(pxe_bootfile_name_by_arch={}, group='pxe')
        result = utils.get_pxe_boot_file(self.node)
        self.assertEqual('bios-bootfile', result)

    def test_get_pxe_config_template_bios_without_by_arch(self):
        properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
        self.node.properties = properties
        self.config(pxe_config_template_by_arch={}, group='pxe')
        result = utils.get_pxe_config_template(self.node)
        self.assertEqual('bios-template', result)

    def test_get_pxe_boot_file_uefi_without_by_arch(self):
        properties = {'cpu_arch': 'x86_64', 'capabilities': 'boot_mode:uefi'}
        self.node.properties = properties
        self.config(pxe_bootfile_name_by_arch={}, group='pxe')
        result = utils.get_pxe_boot_file(self.node)
        self.assertEqual('uefi-bootfile', result)

    def test_get_pxe_config_template_uefi_without_by_arch(self):
        properties = {'cpu_arch': 'x86_64', 'capabilities': 'boot_mode:uefi'}
        self.node.properties = properties
        self.config(pxe_config_template_by_arch={}, group='pxe')
        result = utils.get_pxe_config_template(self.node)
        self.assertEqual('uefi-template', result)

    def test_get_pxe_boot_file_cpu_not_in_by_arch(self):
        properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
        self.node.properties = properties
        self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
                    group='pxe')
        result = utils.get_pxe_boot_file(self.node)
        self.assertEqual('bios-bootfile', result)

    def test_get_pxe_config_template_cpu_not_in_by_arch(self):
        properties = {'cpu_arch': 'x86', 'capabilities': 'boot_mode:bios'}
        self.node.properties = properties
        self.config(pxe_config_template_by_arch=self.template_by_arch,
                    group='pxe')
        result = utils.get_pxe_config_template(self.node)
        self.assertEqual('bios-template', result)

    def test_get_pxe_boot_file_cpu_in_by_arch(self):
        properties = {'cpu_arch': 'aarch64', 'capabilities': 'boot_mode:uefi'}
        self.node.properties = properties
        self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
                    group='pxe')
        result = utils.get_pxe_boot_file(self.node)
        self.assertEqual('aarch64-bootfile', result)

    def test_get_pxe_config_template_cpu_in_by_arch(self):
        properties = {'cpu_arch': 'aarch64', 'capabilities': 'boot_mode:uefi'}
        self.node.properties = properties
        self.config(pxe_config_template_by_arch=self.template_by_arch,
                    group='pxe')
        result = utils.get_pxe_config_template(self.node)
        self.assertEqual('aarch64-template', result)

    def test_get_pxe_boot_file_emtpy_property(self):
        self.node.properties = {}
        self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
                    group='pxe')
        result = utils.get_pxe_boot_file(self.node)
        self.assertEqual('uefi-bootfile', result)

    def test_get_pxe_boot_file_emtpy_property_bios_default(self):
        self.config(default_boot_mode='bios', group='deploy')
        self.node.properties = {}
        self.config(pxe_bootfile_name_by_arch=self.bootfile_by_arch,
                    group='pxe')
        result = utils.get_pxe_boot_file(self.node)
        self.assertEqual('bios-bootfile', result)

    def test_get_ipxe_boot_uefi(self):
        self.config(uefi_ipxe_bootfile_name='meow', group='pxe')
        result = utils.get_ipxe_boot_file(self.node)
        self.assertEqual('meow', result)

    def test_get_ipxe_boot_bios(self):
        self.config(default_boot_mode='bios', group='deploy')
        self.config(ipxe_bootfile_name='meow', group='pxe')
        result = utils.get_ipxe_boot_file(self.node)
        self.assertEqual('meow', result)

    def test_get_ipxe_boot_file_uefi(self):
        self.config(uefi_ipxe_bootfile_name='ipxe-uefi-bootfile', group='pxe')
        properties = {'capabilities': 'boot_mode:uefi'}
        self.node.properties = properties
        result = utils.get_ipxe_boot_file(self.node)
        self.assertEqual('ipxe-uefi-bootfile', result)

    def test_get_ipxe_boot_file_other_arch(self):
        arch_names = {'aarch64': 'ipxe-aa64.efi',
                      'x86_64': 'ipxe.kpxe'}
        self.config(ipxe_bootfile_name_by_arch=arch_names, group='pxe')
        properties = {'cpu_arch': 'aarch64', 'capabilities': 'boot_mode:uefi'}
        self.node.properties = properties
        result = utils.get_ipxe_boot_file(self.node)
        self.assertEqual('ipxe-aa64.efi', result)

    def test_get_ipxe_boot_file_fallback(self):
        self.config(ipxe_bootfile_name=None, group='pxe')
        self.config(uefi_ipxe_bootfile_name=None, group='pxe')
        self.config(pxe_bootfile_name=None, group='pxe')
        self.config(uefi_pxe_bootfile_name='lolcat', group='pxe')
        result = utils.get_ipxe_boot_file(self.node)
        self.assertEqual('lolcat', result)

    def test_get_ipxe_boot_file_fallback_bios(self):
        self.config(default_boot_mode='bios', group='deploy')
        self.config(ipxe_bootfile_name=None, group='pxe')
        self.config(uefi_ipxe_bootfile_name=None, group='pxe')
        self.config(pxe_bootfile_name='lolcat', group='pxe')
        result = utils.get_ipxe_boot_file(self.node)
        self.assertEqual('lolcat', result)

    def test_get_pxe_config_template_emtpy_property(self):
        self.node.properties = {}
        self.config(pxe_config_template_by_arch=self.template_by_arch,
                    group='pxe')
        result = utils.get_pxe_config_template(self.node)
        self.assertEqual('uefi-template', result)

    def test_get_pxe_config_template_emtpy_property_bios(self):
        self.config(default_boot_mode='bios', group='deploy')
        self.node.properties = {}
        self.config(pxe_config_template_by_arch=self.template_by_arch,
                    group='pxe')
        result = utils.get_pxe_config_template(self.node)
        self.assertEqual('bios-template', result)

    def test_get_pxe_config_template_per_node(self):
        node = obj_utils.create_test_node(
            self.context, driver='fake-hardware',
            driver_info={"pxe_template": "fake-template"},
        )
        result = utils.get_pxe_config_template(node)
        self.assertEqual('fake-template', result)

    def test_get_ipxe_config_template(self):
        node = obj_utils.create_test_node(
            self.context, driver='fake-hardware')
        self.assertIn('ipxe_config.template',
                      utils.get_ipxe_config_template(node))

    def test_get_ipxe_config_template_none(self):
        self.config(ipxe_config_template=None, group='pxe')
        self.config(uefi_pxe_config_template='magical_bootloader',
                    group='pxe')
        node = obj_utils.create_test_node(
            self.context, driver='fake-hardware')
        self.assertEqual('magical_bootloader',
                         utils.get_ipxe_config_template(node))

    def test_get_ipxe_config_template_none_bios(self):
        self.config(default_boot_mode='bios', group='deploy')
        self.config(ipxe_config_template=None, group='pxe')
        self.config(pxe_config_template='magical_bootloader',
                    group='pxe')
        node = obj_utils.create_test_node(
            self.context, driver='fake-hardware')
        self.assertEqual('magical_bootloader',
                         utils.get_ipxe_config_template(node))

    def test_get_ipxe_config_template_override_pxe_fallback(self):
        node = obj_utils.create_test_node(
            self.context, driver='fake-hardware',
            driver_info={'pxe_template': 'magical'})
        self.assertEqual('magical',
                         utils.get_ipxe_config_template(node))


@mock.patch('time.sleep', lambda sec: None)
class OtherFunctionTestCase(db_base.DbTestCase):

    def setUp(self):
        super(OtherFunctionTestCase, self).setUp()
        self.node = obj_utils.create_test_node(self.context,
                                               boot_interface='pxe')

    @mock.patch.object(utils, 'LOG', autospec=True)
    @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
    @mock.patch.object(manager_utils, 'deploying_error_handler', autospec=True)
    def _test_set_failed_state(self, mock_error, mock_power, mock_log,
                               event_value=None, power_value=None,
                               log_calls=None, poweroff=True,
                               collect_logs=True):
        err_msg = 'some failure'
        mock_error.side_effect = event_value
        mock_power.side_effect = power_value
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            if collect_logs:
                utils.set_failed_state(task, err_msg)
            else:
                utils.set_failed_state(task, err_msg,
                                       collect_logs=collect_logs)
            mock_error.assert_called_once_with(task, err_msg, err_msg,
                                               clean_up=False)
            if poweroff:
                mock_power.assert_called_once_with(task, states.POWER_OFF)
            else:
                self.assertFalse(mock_power.called)
            self.assertEqual(err_msg, task.node.last_error)
            if (log_calls and poweroff):
                mock_log.exception.assert_has_calls(log_calls)
            else:
                self.assertFalse(mock_log.called)

    @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
    def test_set_failed_state(self, mock_collect):
        exc_state = exception.InvalidState('invalid state')
        exc_param = exception.InvalidParameterValue('invalid parameter')
        mock_call = mock.call(mock.ANY)
        self._test_set_failed_state()
        calls = [mock_call]
        self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
                                    log_calls=calls)
        calls = [mock_call]
        self._test_set_failed_state(power_value=iter([exc_param] * len(calls)),
                                    log_calls=calls)
        calls = [mock_call, mock_call]
        self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
                                    power_value=iter([exc_param] * len(calls)),
                                    log_calls=calls)
        self.assertEqual(4, mock_collect.call_count)

    @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
    def test_set_failed_state_no_poweroff(self, mock_collect):
        cfg.CONF.set_override('power_off_after_deploy_failure', False,
                              'deploy')
        exc_state = exception.InvalidState('invalid state')
        exc_param = exception.InvalidParameterValue('invalid parameter')
        mock_call = mock.call(mock.ANY)
        self._test_set_failed_state(poweroff=False)
        calls = [mock_call]
        self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
                                    log_calls=calls, poweroff=False)
        calls = [mock_call]
        self._test_set_failed_state(power_value=iter([exc_param] * len(calls)),
                                    log_calls=calls, poweroff=False)
        calls = [mock_call, mock_call]
        self._test_set_failed_state(event_value=iter([exc_state] * len(calls)),
                                    power_value=iter([exc_param] * len(calls)),
                                    log_calls=calls, poweroff=False)
        self.assertEqual(4, mock_collect.call_count)

    @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
    def test_set_failed_state_collect_deploy_logs(self, mock_collect):
        for opt in ('always', 'on_failure'):
            cfg.CONF.set_override('deploy_logs_collect', opt, 'agent')
            self._test_set_failed_state()
            mock_collect.assert_called_once_with(mock.ANY)
            mock_collect.reset_mock()

    @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
    def test_set_failed_state_collect_deploy_logs_never(self, mock_collect):
        cfg.CONF.set_override('deploy_logs_collect', 'never', 'agent')
        self._test_set_failed_state()
        self.assertFalse(mock_collect.called)

    @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
    def test_set_failed_state_collect_deploy_logs_overide(self, mock_collect):
        cfg.CONF.set_override('deploy_logs_collect', 'always', 'agent')
        self._test_set_failed_state(collect_logs=False)
        self.assertFalse(mock_collect.called)

    def test_get_boot_option_default_value(self):
        self.node.instance_info = {}
        result = utils.get_boot_option(self.node)
        self.assertEqual("local", result)

    @mock.patch.object(utils, 'is_anaconda_deploy', autospec=True)
    def test_get_boot_option_anaconda_deploy(self, mock_is_anaconda_deploy):
        mock_is_anaconda_deploy.return_value = True
        result = utils.get_boot_option(self.node)
        self.assertEqual("kickstart", result)

    def test_is_anaconda_deploy(self):
        self.node.deploy_interface = 'anaconda'
        result = utils.is_anaconda_deploy(self.node)
        self.assertTrue(result)

    def test_is_anaconda_deploy_false(self):
        result = utils.is_anaconda_deploy(self.node)
        self.assertFalse(result)

    def test_is_software_raid(self):
        self.node.target_raid_config = {
            "logical_disks": [
                {
                    "size_gb": 100,
                    "raid_level": "1",
                    "controller": "software",
                }
            ]
        }
        result = utils.is_software_raid(self.node)
        self.assertTrue(result)

    def test_is_software_raid_false(self):
        self.node.target_raid_config = {}
        result = utils.is_software_raid(self.node)
        self.assertFalse(result)

    @mock.patch.object(image_cache, 'clean_up_caches', autospec=True)
    def test_fetch_images(self, mock_clean_up_caches):

        mock_cache = mock.MagicMock(
            spec_set=['fetch_image', 'master_dir'], master_dir='master_dir')
        utils.fetch_images(None, mock_cache, [('uuid', 'path')])
        mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
                                                     [('uuid', 'path')],
                                                     None)
        mock_cache.fetch_image.assert_called_once_with(
            'uuid', 'path',
            ctx=None,
            force_raw=True,
            expected_format=None,
            expected_checksum=None,
            expected_checksum_algo=None,
            image_auth_data=None)

    @mock.patch.object(image_cache, 'clean_up_caches', autospec=True)
    def test_fetch_images_with_auth(self, mock_clean_up_caches):

        mock_cache = mock.MagicMock(
            spec_set=['fetch_image', 'master_dir'], master_dir='master_dir')
        utils.fetch_images(None, mock_cache, [('uuid', 'path')],
                           image_auth_data='meow')
        mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
                                                     [('uuid', 'path')],
                                                     'meow')
        mock_cache.fetch_image.assert_called_once_with(
            'uuid', 'path',
            ctx=None,
            force_raw=True,
            expected_format=None,
            expected_checksum=None,
            expected_checksum_algo=None,
            image_auth_data='meow')

    @mock.patch.object(image_cache, 'clean_up_caches', autospec=True)
    def test_fetch_images_checksum(self, mock_clean_up_caches):

        mock_cache = mock.MagicMock(
            spec_set=['fetch_image', 'master_dir'], master_dir='master_dir')
        utils.fetch_images(None, mock_cache, [('uuid', 'path')],
                           force_raw=True,
                           expected_format='qcow2',
                           expected_checksum='f00',
                           expected_checksum_algo='sha256')
        mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
                                                     [('uuid', 'path')],
                                                     None)
        mock_cache.fetch_image.assert_called_once_with(
            'uuid', 'path',
            ctx=None,
            force_raw=True,
            expected_format='qcow2',
            expected_checksum='f00',
            expected_checksum_algo='sha256',
            image_auth_data=None)

    @mock.patch.object(image_cache, 'clean_up_caches', autospec=True)
    def test_fetch_images_fail(self, mock_clean_up_caches):

        exc = exception.InsufficientDiskSpace(path='a',
                                              required=2,
                                              actual=1)

        mock_cache = mock.MagicMock(
            spec_set=['master_dir'], master_dir='master_dir')
        mock_clean_up_caches.side_effect = [exc]
        self.assertRaises(exception.InstanceDeployFailure,
                          utils.fetch_images,
                          None,
                          mock_cache,
                          [('uuid', 'path')])
        mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
                                                     [('uuid', 'path')],
                                                     None)

    @mock.patch('ironic.common.keystone.get_auth', autospec=True)
    @mock.patch.object(utils, '_get_ironic_session', autospec=True)
    @mock.patch('ironic.common.keystone.get_adapter', autospec=True)
    def test_get_ironic_api_url_from_keystone(self, mock_ka, mock_ks,
                                              mock_auth):
        mock_sess = mock.Mock()
        mock_ks.return_value = mock_sess
        fake_api_url = 'http://foo/'
        mock_ka.return_value.get_endpoint.return_value = fake_api_url
        # NOTE(pas-ha) endpoint_override is None by default
        url = utils.get_ironic_api_url()
        # also checking for stripped trailing slash
        self.assertEqual(fake_api_url[:-1], url)
        mock_ka.assert_called_with('service_catalog', session=mock_sess,
                                   auth=mock_auth.return_value)
        mock_ka.return_value.get_endpoint.assert_called_once_with()

    @mock.patch('ironic.common.keystone.get_auth', autospec=True)
    @mock.patch.object(utils, '_get_ironic_session', autospec=True)
    @mock.patch('ironic.common.keystone.get_adapter', autospec=True)
    def test_get_ironic_api_url_fail(self, mock_ka, mock_ks, mock_auth):
        mock_sess = mock.Mock()
        mock_ks.return_value = mock_sess
        mock_ka.return_value.get_endpoint.side_effect = (
            exception.KeystoneFailure())
        self.assertRaises(exception.InvalidParameterValue,
                          utils.get_ironic_api_url)

    @mock.patch('ironic.common.keystone.get_auth', autospec=True)
    @mock.patch.object(utils, '_get_ironic_session', autospec=True)
    @mock.patch('ironic.common.keystone.get_adapter', autospec=True)
    def test_get_ironic_api_url_none(self, mock_ka, mock_ks, mock_auth):
        mock_sess = mock.Mock()
        mock_ks.return_value = mock_sess
        mock_ka.return_value.get_endpoint.return_value = None
        self.assertRaises(exception.InvalidParameterValue,
                          utils.get_ironic_api_url)


class GetSingleNicTestCase(db_base.DbTestCase):

    def setUp(self):
        super(GetSingleNicTestCase, self).setUp()
        self.node = obj_utils.create_test_node(self.context)

    def test_get_single_nic_with_vif_port_id(self):
        obj_utils.create_test_port(
            self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
            uuid=uuidutils.generate_uuid(),
            internal_info={n_common.NetType.TENANT.vif_key: 'test-vif-A'})
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            address = utils.get_single_nic_with_vif_port_id(task)
            self.assertEqual('aa:bb:cc:dd:ee:ff', address)

    def test_get_single_nic_with_cleaning_vif_port_id(self):
        obj_utils.create_test_port(
            self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
            uuid=uuidutils.generate_uuid(),
            internal_info={n_common.NetType.CLEANING.vif_key: 'test-vif-A'})
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            address = utils.get_single_nic_with_vif_port_id(task)
            self.assertEqual('aa:bb:cc:dd:ee:ff', address)

    def test_get_single_nic_with_provisioning_vif_port_id(self):
        obj_utils.create_test_port(
            self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
            uuid=uuidutils.generate_uuid(),
            internal_info={
                n_common.NetType.PROVISIONING.vif_key: 'test-vif-A'})
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            address = utils.get_single_nic_with_vif_port_id(task)
            self.assertEqual('aa:bb:cc:dd:ee:ff', address)

    def test_get_single_nic_with_rescuing_vif_port_id(self):
        obj_utils.create_test_port(
            self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
            uuid=uuidutils.generate_uuid(),
            internal_info={
                n_common.NetType.RESCUING.vif_key: 'test-vif-A'})
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            address = utils.get_single_nic_with_vif_port_id(task)
            self.assertEqual('aa:bb:cc:dd:ee:ff', address)

    def test_get_single_nic_with_inspecting_vif_port_id(self):
        obj_utils.create_test_port(
            self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
            uuid=uuidutils.generate_uuid(),
            internal_info={
                n_common.NetType.INSPECTION.vif_key: 'test-vif-A'})
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            address = utils.get_single_nic_with_vif_port_id(task)
            self.assertEqual('aa:bb:cc:dd:ee:ff', address)

    def test_get_single_nic_with_servicing_vif_port_id(self):
        obj_utils.create_test_port(
            self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff',
            uuid=uuidutils.generate_uuid(),
            internal_info={
                n_common.NetType.SERVICING.vif_key: 'test-vif-A'})
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            address = utils.get_single_nic_with_vif_port_id(task)
            self.assertEqual('aa:bb:cc:dd:ee:ff', address)


class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):

    def setUp(self):
        super(ParseInstanceInfoCapabilitiesTestCase, self).setUp()
        self.node = obj_utils.get_test_node(self.context,
                                            driver='fake-hardware')

    def test_parse_instance_info_capabilities_string(self):
        self.node.instance_info = {'capabilities': '{"cat": "meow"}'}
        expected_result = {"cat": "meow"}
        result = utils.parse_instance_info_capabilities(self.node)
        self.assertEqual(expected_result, result)

    def test_parse_instance_info_capabilities(self):
        self.node.instance_info = {'capabilities': {"dog": "wuff"}}
        expected_result = {"dog": "wuff"}
        result = utils.parse_instance_info_capabilities(self.node)
        self.assertEqual(expected_result, result)

    def test_parse_instance_info_invalid_type(self):
        self.node.instance_info = {'capabilities': 'not-a-dict'}
        self.assertRaises(exception.InvalidParameterValue,
                          utils.parse_instance_info_capabilities, self.node)

    def test_is_secure_boot_requested_true(self):
        self.node.instance_info = {'capabilities': {"secure_boot": "tRue"}}
        self.assertTrue(utils.is_secure_boot_requested(self.node))

    def test_is_secure_boot_requested_false(self):
        self.node.instance_info = {'capabilities': {"secure_boot": "false"}}
        self.assertFalse(utils.is_secure_boot_requested(self.node))

    def test_is_secure_boot_requested_invalid(self):
        self.node.instance_info = {'capabilities': {"secure_boot": "invalid"}}
        self.assertFalse(utils.is_secure_boot_requested(self.node))

    def test_validate_boot_mode_capability(self):
        prop = {'capabilities': 'boot_mode:uefi,cap2:value2'}
        self.node.properties = prop

        result = utils.validate_capabilities(self.node)
        self.assertIsNone(result)

    def test_validate_boot_mode_capability_with_exc(self):
        prop = {'capabilities': 'boot_mode:UEFI,cap2:value2'}
        self.node.properties = prop

        self.assertRaises(exception.InvalidParameterValue,
                          utils.validate_capabilities, self.node)

    def test_validate_boot_mode_capability_instance_info(self):
        inst_info = {'capabilities': {"boot_mode": "uefi", "cap2": "value2"}}
        self.node.instance_info = inst_info

        result = utils.validate_capabilities(self.node)
        self.assertIsNone(result)

    def test_validate_boot_mode_capability_instance_info_with_exc(self):
        inst_info = {'capabilities': {"boot_mode": "UEFI", "cap2": "value2"}}
        self.node.instance_info = inst_info

        self.assertRaises(exception.InvalidParameterValue,
                          utils.validate_capabilities, self.node)

    def test_all_supported_capabilities(self):
        self.assertEqual(('bios', 'uefi'),
                         utils.SUPPORTED_CAPABILITIES['boot_mode'])
        self.assertEqual(('true', 'false'),
                         utils.SUPPORTED_CAPABILITIES['secure_boot'])

    def test_get_disk_label(self):
        inst_info = {'capabilities': {'disk_label': 'gpt', 'foo': 'bar'}}
        self.node.instance_info = inst_info
        result = utils.get_disk_label(self.node)
        self.assertEqual('gpt', result)

    def test_get_disk_label_nothing_set(self):
        inst_info = {'capabilities': {'cat': 'meows'}}
        self.node.instance_info = inst_info
        result = utils.get_disk_label(self.node)
        self.assertEqual('gpt', result)

    def test_get_disk_label_nothing_set_bios_mode(self):
        self.config(default_boot_mode='bios', group='deploy')
        inst_info = {'capabilities': {'cat': 'meows'}}
        self.node.instance_info = inst_info
        result = utils.get_disk_label(self.node)
        self.assertIsNone(result)

    def test_get_disk_label_uefi_mode(self):
        inst_info = {'capabilities': {'cat': 'meows'}}
        properties = {'capabilities': 'boot_mode:uefi'}
        self.node.instance_info = inst_info
        self.node.properties = properties
        result = utils.get_disk_label(self.node)
        self.assertEqual('gpt', result)


class TrySetBootDeviceTestCase(db_base.DbTestCase):

    def setUp(self):
        super(TrySetBootDeviceTestCase, self).setUp()
        self.node = obj_utils.create_test_node(self.context,
                                               driver="fake-hardware")

    @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
    def test_try_set_boot_device_okay(self, node_set_boot_device_mock):
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            utils.try_set_boot_device(task, boot_devices.DISK,
                                      persistent=True)
            node_set_boot_device_mock.assert_called_once_with(
                task, boot_devices.DISK, persistent=True)

    @mock.patch.object(utils, 'LOG', autospec=True)
    @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
    def test_try_set_boot_device_ipmifailure_uefi(
            self, node_set_boot_device_mock, log_mock):
        self.node.properties = {'capabilities': 'boot_mode:uefi'}
        self.node.save()
        node_set_boot_device_mock.side_effect = exception.IPMIFailure(cmd='a')
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            utils.try_set_boot_device(task, boot_devices.DISK,
                                      persistent=True)
            node_set_boot_device_mock.assert_called_once_with(
                task, boot_devices.DISK, persistent=True)
            log_mock.warning.assert_called_once_with(mock.ANY, self.node.uuid)

    @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
    def test_try_set_boot_device_ipmifailure_bios(
            self, node_set_boot_device_mock):
        self.config(default_boot_mode='bios', group='deploy')
        node_set_boot_device_mock.side_effect = exception.IPMIFailure(cmd='a')
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            self.assertRaises(exception.IPMIFailure,
                              utils.try_set_boot_device,
                              task, boot_devices.DISK, persistent=True)
            node_set_boot_device_mock.assert_called_once_with(
                task, boot_devices.DISK, persistent=True)

    @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
    def test_try_set_boot_device_some_other_exception(
            self, node_set_boot_device_mock):
        exc = exception.IloOperationError(operation="qwe", error="error")
        node_set_boot_device_mock.side_effect = exc
        with task_manager.acquire(self.context, self.node.uuid,
                                  shared=False) as task:
            self.assertRaises(exception.IloOperationError,
                              utils.try_set_boot_device,
                              task, boot_devices.DISK, persistent=True)
            node_set_boot_device_mock.assert_called_once_with(
                task, boot_devices.DISK, persistent=True)


class AgentMethodsTestCase(db_base.DbTestCase):

    def setUp(self):
        super(AgentMethodsTestCase, self).setUp()

        self.clean_steps = {
            'deploy': [
                {'interface': 'deploy',
                 'step': 'erase_devices',
                 'priority': 20},
                {'interface': 'deploy',
                 'step': 'update_firmware',
                 'priority': 30}
            ],
            'raid': [
                {'interface': 'raid',
                 'step': 'create_configuration',
                 'priority': 10}
            ]
        }
        n = {'boot_interface': 'pxe',
             'deploy_interface': 'direct',
             'driver_internal_info': {
                 'agent_cached_clean_steps': self.clean_steps}}
        self.node = obj_utils.create_test_node(self.context, **n)
        self.ports = [obj_utils.create_test_port(self.context,
                                                 node_id=self.node.id)]

    def test_agent_add_clean_params(self):
        cfg.CONF.set_override('shred_random_overwrite_iterations', 2, 'deploy')
        cfg.CONF.set_override('shred_final_overwrite_with_zeros', False,
                              'deploy')
        cfg.CONF.set_override('continue_if_disk_secure_erase_fails', True,
                              'deploy')
        cfg.CONF.set_override('enable_ata_secure_erase', False, 'deploy')
        cfg.CONF.set_override('disk_erasure_concurrency', 8, 'deploy')
        cfg.CONF.set_override('enable_nvme_secure_erase', False, 'deploy')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            utils.agent_add_clean_params(task)
            self.assertEqual(2, task.node.driver_internal_info[
                'agent_erase_devices_iterations'])
            self.assertIs(False, task.node.driver_internal_info[
                'agent_erase_devices_zeroize'])
            self.assertIs(True, task.node.driver_internal_info[
                'agent_continue_if_secure_erase_failed'])
            self.assertIs(False, task.node.driver_internal_info[
                'agent_enable_ata_secure_erase'])
            self.assertEqual(8, task.node.driver_internal_info[
                'disk_erasure_concurrency'])
            self.assertIs(False, task.node.driver_internal_info[
                'agent_enable_nvme_secure_erase'])

    @mock.patch('ironic.conductor.utils.is_fast_track', autospec=True)
    @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    @mock.patch.object(utils, 'build_agent_options', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'add_cleaning_network', autospec=True)
    def _test_prepare_inband_cleaning(
            self, add_cleaning_network_mock,
            build_options_mock, power_mock, prepare_ramdisk_mock,
            is_fast_track_mock, manage_boot=True, fast_track=False):
        build_options_mock.return_value = {'a': 'b'}
        is_fast_track_mock.return_value = fast_track
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            result = utils.prepare_inband_cleaning(task,
                                                   manage_boot=manage_boot)
            add_cleaning_network_mock.assert_called_once_with(
                task.driver.network, task)
            if not fast_track:
                self.assertEqual(states.CLEANWAIT, result)
                power_mock.assert_called_once_with(task, states.REBOOT)
            else:
                self.assertFalse(power_mock.called)
                self.assertIsNone(result)
            self.assertEqual(1, task.node.driver_internal_info[
                             'agent_erase_devices_iterations'])
            self.assertIs(True, task.node.driver_internal_info[
                          'agent_erase_devices_zeroize'])
            if manage_boot:
                prepare_ramdisk_mock.assert_called_once_with(
                    mock.ANY, mock.ANY, {'a': 'b'})
                build_options_mock.assert_called_once_with(task.node)
            else:
                self.assertFalse(prepare_ramdisk_mock.called)
                self.assertFalse(build_options_mock.called)

    def test_prepare_inband_cleaning(self):
        self._test_prepare_inband_cleaning()

    def test_prepare_inband_cleaning_manage_boot_false(self):
        self._test_prepare_inband_cleaning(manage_boot=False)

    def test_prepare_inband_cleaning_fast_track(self):
        self._test_prepare_inband_cleaning(fast_track=True)

    @mock.patch('ironic.conductor.utils.power_on_node_if_needed',
                autospec=True)
    @mock.patch('ironic.conductor.utils.is_fast_track', autospec=True)
    @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    @mock.patch.object(utils, 'build_agent_options', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'add_cleaning_network', autospec=True)
    def test_prepare_inband_cleaning_broken_fast_track(
            self, add_cleaning_network_mock,
            build_options_mock, power_mock, prepare_ramdisk_mock,
            is_fast_track_mock, power_on_if_needed_mock):
        build_options_mock.return_value = {'a': 'b'}
        is_fast_track_mock.side_effect = [True, False]
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertEqual(
                states.CLEANWAIT,
                utils.prepare_inband_cleaning(task))
            add_cleaning_network_mock.assert_called_once_with(
                task.driver.network, task)
            power_mock.assert_called_once_with(task, states.REBOOT)
            self.assertEqual(1, task.node.driver_internal_info[
                             'agent_erase_devices_iterations'])
            self.assertIs(True, task.node.driver_internal_info[
                          'agent_erase_devices_zeroize'])
            prepare_ramdisk_mock.assert_called_once_with(
                mock.ANY, mock.ANY, {'a': 'b'})
            build_options_mock.assert_called_once_with(task.node)
            self.assertFalse(power_on_if_needed_mock.called)

    @mock.patch.object(pxe.PXEBoot, 'clean_up_instance', autospec=True)
    @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    @mock.patch.object(utils, 'build_agent_options', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'add_servicing_network', autospec=True)
    def _test_prepare_inband_service(
            self, add_service_network_mock,
            build_options_mock, power_mock, prepare_ramdisk_mock,
            clean_up_instance_mock,
            manage_boot=True):
        build_options_mock.return_value = {'a': 'b'}
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            result = utils.prepare_inband_service(task)
            add_service_network_mock.assert_called_once_with(
                task.driver.network, task)
            self.assertEqual(states.SERVICEWAIT, result)
            power_mock.assert_has_calls([
                mock.call(task, states.POWER_OFF),
                mock.call(task, states.POWER_ON)])
            prepare_ramdisk_mock.assert_called_once_with(
                mock.ANY, mock.ANY, {'a': 'b'})
            build_options_mock.assert_called_once_with(task.node)
            clean_up_instance_mock.assert_called_once_with(mock.ANY, task)

    def test_prepare_inband_service(self):
        self._test_prepare_inband_service()

    @mock.patch('ironic.conductor.utils.is_fast_track', autospec=True)
    @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'remove_cleaning_network', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    def _test_tear_down_inband_cleaning(
            self, power_mock, remove_cleaning_network_mock,
            clean_up_ramdisk_mock, is_fast_track_mock,
            manage_boot=True, fast_track=False, cleaning_error=False,
            disable_power_off=False):
        is_fast_track_mock.return_value = fast_track
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            task.node.disable_power_off = disable_power_off
            if cleaning_error:
                task.node.fault = faults.CLEAN_FAILURE
            utils.tear_down_inband_cleaning(task, manage_boot=manage_boot)
            if not (fast_track or cleaning_error):
                if disable_power_off:
                    power_mock.assert_called_once_with(task, states.REBOOT)
                else:
                    power_mock.assert_called_once_with(task, states.POWER_OFF)
            else:
                power_mock.assert_not_called()
            remove_cleaning_network_mock.assert_called_once_with(
                task.driver.network, task)
            if manage_boot:
                clean_up_ramdisk_mock.assert_called_once_with(
                    task.driver.boot, task)
            else:
                self.assertFalse(clean_up_ramdisk_mock.called)

    def test_tear_down_inband_cleaning(self):
        self._test_tear_down_inband_cleaning(manage_boot=True)

    def test_tear_down_inband_cleaning_manage_boot_false(self):
        self._test_tear_down_inband_cleaning(manage_boot=False)

    def test_tear_down_inband_cleaning_fast_track(self):
        self._test_tear_down_inband_cleaning(fast_track=True)

    def test_tear_down_inband_cleaning_cleaning_error(self):
        self._test_tear_down_inband_cleaning(cleaning_error=True)

    def test_tear_down_inband_cleaning_disable_power_off(self):
        self._test_tear_down_inband_cleaning(disable_power_off=True)

    def test_tear_down_inband_cleaning_disable_power_off_and_fast_track(self):
        self._test_tear_down_inband_cleaning(disable_power_off=True,
                                             fast_track=True)

    @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
    @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'remove_servicing_network', autospec=True)
    @mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    def test_tear_down_inband_service(
            self, power_mock, update_neutron_port_mock,
            remove_service_network_mock,
            clean_up_ramdisk_mock, prepare_instance_mock):
        # NOTE(TheJulia): This should be back to servicing upon a heartbeat
        # operation, before we go back to WAIT. We wouldn't know to teardown
        # in a wait state anyway.
        self.node.provision_state = states.SERVICING
        self.node.save()

        self.ports[0].internal_info = {
            n_common.NetType.TENANT.vif_key: 'fake-vif-id'
        }
        self.ports[0].save()
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            utils.tear_down_inband_service(task)
            power_mock.assert_has_calls([
                mock.call(task, states.POWER_OFF),
                mock.call(task, states.POWER_ON),
            ])
            remove_service_network_mock.assert_called_once_with(
                task.driver.network, task)
            clean_up_ramdisk_mock.assert_called_once_with(
                task.driver.boot, task)
            prepare_instance_mock.assert_called_once_with(task.driver.boot,
                                                          task)

    @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'remove_servicing_network', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    def test_tear_down_inband_service_service_error(
            self, power_mock, remove_service_network_mock,
            clean_up_ramdisk_mock):
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            task.node.fault = faults.SERVICE_FAILURE
            utils.tear_down_inband_service(task)
            self.assertFalse(power_mock.called)
            remove_service_network_mock.assert_not_called()
            clean_up_ramdisk_mock.assert_called_once_with(
                task.driver.boot, task)

    @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
    @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
    @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
                'remove_servicing_network', autospec=True)
    @mock.patch('ironic.common.neutron.update_neutron_port', autospec=True)
    @mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
    def test_tear_down_inband_service_disable_power_off(
            self, power_mock, update_neutron_port_mock,
            remove_service_network_mock,
            clean_up_ramdisk_mock, prepare_instance_mock):
        # NOTE(TheJulia): This should be back to servicing upon a heartbeat
        # operation, before we go back to WAIT. We wouldn't know to teardown
        # in a wait state anyway.
        self.node.provision_state = states.SERVICING
        self.node.disable_power_off = True
        self.node.save()

        self.ports[0].internal_info = {
            n_common.NetType.TENANT.vif_key: 'fake-vif-id'
        }
        self.ports[0].save()
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            utils.tear_down_inband_service(task)
            power_mock.assert_called_once_with(task, states.REBOOT)
            remove_service_network_mock.assert_called_once_with(
                task.driver.network, task)
            clean_up_ramdisk_mock.assert_called_once_with(
                task.driver.boot, task)
            prepare_instance_mock.assert_called_once_with(task.driver.boot,
                                                          task)

    def test_build_agent_options_conf(self):
        self.config(endpoint_override='https://api-url',
                    group='service_catalog')
        options = utils.build_agent_options(self.node)
        self.assertEqual('https://api-url', options['ipa-api-url'])

    @mock.patch.object(utils, '_get_ironic_session', autospec=True)
    def test_build_agent_options_keystone(self, session_mock):
        sess = mock.Mock()
        sess.get_endpoint.return_value = 'https://api-url'
        session_mock.return_value = sess
        options = utils.build_agent_options(self.node)
        self.assertEqual('https://api-url', options['ipa-api-url'])

    @mock.patch.object(utils, 'build_agent_options', autospec=True)
    def test_prepare_agent_boot(self, mock_build_opts):
        mock_build_opts.return_value = {'ipa-api-url': 'https://api-url'}

        with task_manager.acquire(self.context, self.node.uuid) as task:
            mock_pr = mock.Mock()
            task.driver.boot.prepare_ramdisk = mock_pr
            utils.prepare_agent_boot(task)
            # Verify attempt tracking is set
            self.assertTrue(
                task.node.driver_internal_info.get('agent_start_attempted'))

            # Verify boot preparation was called
            mock_pr.assert_called_once_with(
                task, {'ipa-api-url': 'https://api-url'})

    def test_direct_deploy_should_convert_raw_image_true(self):
        cfg.CONF.set_override('force_raw_images', True)
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        internal_info = self.node.driver_internal_info
        internal_info['is_whole_disk_image'] = True
        self.node.driver_internal_info = internal_info
        self.assertTrue(
            utils.direct_deploy_should_convert_raw_image(self.node))

    def test_direct_deploy_should_convert_raw_image_no_force_raw(self):
        cfg.CONF.set_override('force_raw_images', False)
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        internal_info = self.node.driver_internal_info
        internal_info['is_whole_disk_image'] = True
        self.node.driver_internal_info = internal_info
        self.assertFalse(
            utils.direct_deploy_should_convert_raw_image(self.node))

    def test_direct_deploy_should_convert_raw_image_no_stream(self):
        cfg.CONF.set_override('force_raw_images', True)
        cfg.CONF.set_override('stream_raw_images', False, group='agent')
        internal_info = self.node.driver_internal_info
        internal_info['is_whole_disk_image'] = True
        self.node.driver_internal_info = internal_info
        self.assertFalse(
            utils.direct_deploy_should_convert_raw_image(self.node))

    def test_direct_deploy_should_convert_raw_image_partition(self):
        cfg.CONF.set_override('force_raw_images', True)
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        internal_info = self.node.driver_internal_info
        internal_info['is_whole_disk_image'] = False
        self.node.driver_internal_info = internal_info
        self.assertTrue(
            utils.direct_deploy_should_convert_raw_image(self.node))


class CheckImageSizeTestCase(db_base.DbTestCase):
    """Tests for check_image_size function."""

    def setUp(self):
        super().setUp()
        self.node = obj_utils.create_test_node(
            self.context, boot_interface='pxe',
            instance_info=INST_INFO_DICT,
            driver_info=DRV_INFO_DICT,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )

    def test_check_image_size(self):
        image_info = {
            'size': 10 * 1024 * 1024,
            'disk_format': 'qcow2',
        }
        self.node.properties['memory_mb'] = 20
        utils.check_image_size(self.node, image_info)

    def test_check_image_size_without_memory_mb(self):
        image_info = {
            'size': 10 * 1024 * 1024,
            'disk_format': 'qcow2',
        }
        self.node.properties.pop('memory_mb', None)
        # Should not raise any exception
        utils.check_image_size(self.node, image_info)

    def test_check_image_size_fail(self):
        """Test check fails when image is larger than available memory."""
        cfg.CONF.set_override('force_raw_images', False)
        image_info = {
            'size': 11 * 1024 * 1024,  # 11 MiB
            'disk_format': 'qcow2',
        }
        self.node.properties['memory_mb'] = 10
        self.assertRaises(exception.InvalidParameterValue,
                          utils.check_image_size,
                          self.node, image_info)

    def test_check_image_size_fail_by_agent_consumed_memory(self):
        """Test check fails when accounting for agent memory consumption."""
        cfg.CONF.set_override('force_raw_images', False)
        cfg.CONF.set_override('memory_consumed_by_agent', 2, group='agent')
        image_info = {
            'size': 9 * 1024 * 1024,  # 9 MiB
            'disk_format': 'qcow2',
        }
        self.node.properties['memory_mb'] = 10
        # 9 MiB image + 2 MiB agent > 10 MiB memory
        self.assertRaises(exception.InvalidParameterValue,
                          utils.check_image_size,
                          self.node, image_info)

    def test_check_image_size_raw_stream_enabled(self):
        """Test check is skipped for raw images when streaming is enabled."""
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        image_info = {
            'size': 15 * 1024 * 1024,  # 15 MiB - larger than memory
            'disk_format': 'raw',
        }
        self.node.properties['memory_mb'] = 10
        # Should not raise - raw image will be streamed
        utils.check_image_size(self.node, image_info)

    def test_check_image_size_raw_stream_enabled_format_raw(self):
        """Test check skipped when instance_info specifies raw format."""
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        image_info = {
            'size': 15 * 1024 * 1024,
        }
        self.node.properties['memory_mb'] = 10
        self.node.instance_info['image_disk_format'] = 'raw'
        # Should not raise - raw image will be streamed
        utils.check_image_size(self.node, image_info)

    def test_check_image_size_raw_stream_enabled_format_qcow2(self):
        """Test check fails for qcow2 image even with streaming enabled."""
        cfg.CONF.set_override('force_raw_images', False)
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        image_info = {
            'size': 15 * 1024 * 1024,
        }
        self.node.properties['memory_mb'] = 10
        self.node.instance_info['image_disk_format'] = 'qcow2'
        # Should raise - qcow2 won't be streamed
        self.assertRaises(exception.InvalidParameterValue,
                          utils.check_image_size,
                          self.node, image_info)

    def test_check_image_size_raw_stream_disabled(self):
        """Test check fails for raw images when streaming is disabled."""
        cfg.CONF.set_override('stream_raw_images', False, group='agent')
        image_info = {
            'size': 15 * 1024 * 1024,
            'disk_format': 'raw',
        }
        self.node.properties['memory_mb'] = 10
        # Should raise - streaming disabled, image too large
        self.assertRaises(exception.InvalidParameterValue,
                          utils.check_image_size,
                          self.node, image_info)

    def test_check_image_size_conductor_converts_raw(self):
        """Test check is skipped when conductor converts image to raw."""
        cfg.CONF.set_override('force_raw_images', True)
        cfg.CONF.set_override('stream_raw_images', True, group='agent')
        image_info = {
            'size': 15 * 1024 * 1024,  # 15 MiB - larger than memory
            'disk_format': 'qcow2',
        }
        self.node.properties['memory_mb'] = 10
        # When conductor converts to raw, agent will stream it
        utils.check_image_size(self.node, image_info)


class ValidateImagePropertiesTestCase(db_base.DbTestCase):

    def setUp(self):
        super().setUp()
        self.node = obj_utils.create_test_node(
            self.context, boot_interface='pxe',
            instance_info=INST_INFO_DICT,
            driver_info=DRV_INFO_DICT,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.task = mock.Mock(context=self.context, node=self.node,
                              spec=['context', 'node'])

    def test_validate_image_properties_local_boot(self):
        inst_info = utils.get_image_instance_info(self.node)
        utils.validate_image_properties(self.task, inst_info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service, 'get_image_service', autospec=True)
    def test_validate_image_properties_glance_image_missing_stage2_id(
            self, image_service_mock, boot_options_mock):
        inst_info = utils.get_image_instance_info(self.node)
        image_service_mock.return_value.show.return_value = {
            'properties': {'kernel_id': '1111', 'ramdisk_id': '2222'},
        }

        self.assertRaises(exception.MissingParameterValue,
                          utils.validate_image_properties,
                          self.task, inst_info)
        image_service_mock.assert_called_once_with(
            self.node.instance_info['image_source'], context=self.context
        )

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service, 'get_image_service', autospec=True)
    def test_validate_image_properties_glance_image_not_authorized(
            self, image_service_mock, boot_options_mock):
        inst_info = {'image_source': 'uuid'}
        show_mock = image_service_mock.return_value.show
        show_mock.side_effect = exception.ImageNotAuthorized(image_id='uuid')
        self.assertRaises(exception.InvalidParameterValue,
                          utils.validate_image_properties, self.task,
                          inst_info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service, 'get_image_service', autospec=True)
    def test_validate_image_properties_glance_image_not_found(
            self, image_service_mock, boot_options_mock):
        inst_info = {'image_source': 'uuid'}
        show_mock = image_service_mock.return_value.show
        show_mock.side_effect = exception.ImageNotFound(image_id='uuid')
        self.assertRaises(exception.InvalidParameterValue,
                          utils.validate_image_properties, self.task,
                          inst_info)

    def test_validate_image_properties_invalid_image_href(self):
        inst_info = {'image_source': 'emule://uuid'}
        self.assertRaises(exception.InvalidParameterValue,
                          utils.validate_image_properties, self.task,
                          inst_info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    def test_validate_image_properties_nonglance_image(
            self, boot_options_mock):
        instance_info = {
            'image_source': 'http://ubuntu',
            'kernel': 'kernel_uuid',
            'ramdisk': 'file://initrd',
            'root_gb': 100,
        }
        self.node.instance_info = instance_info
        inst_info = utils.get_image_instance_info(self.node)
        utils.validate_image_properties(self.task, inst_info)

    def test_validate_image_properties_boot_iso_conflict(self):
        instance_info = {
            'image_source': 'http://ubuntu',
            'boot_iso': 'http://ubuntu.iso',
        }
        expected_error = ("An 'image_source' and 'boot_iso' "
                          "parameter may not be specified at "
                          "the same time.")
        error = self.assertRaises(exception.InvalidParameterValue,
                                  utils.validate_image_properties,
                                  self.task,
                                  instance_info)
        self.assertEqual(expected_error, str(error))

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    @mock.patch.object(image_service.HttpImageService, 'show', autospec=True)
    def test_validate_image_properties_ramdisk_deploy(
            self, image_service_show_mock, boot_options_mock):
        instance_info = {
            'kernel': 'file://kernel',
            'ramdisk': 'file://initrd',
        }
        self.node.instance_info = instance_info
        inst_info = utils.get_image_instance_info(self.node)
        utils.validate_image_properties(self.task, inst_info)
        image_service_show_mock.assert_not_called()

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    @mock.patch.object(image_service, 'get_image_service', autospec=True)
    def test_validate_image_properties_ramdisk_deploy_glance(
            self, image_service_mock, boot_options_mock):
        """Ramdisk deploy with Glance image_source validates via Glance."""
        instance_info = {
            'image_source': 'glance://image-uuid',
        }
        self.node.instance_info = instance_info
        inst_info = utils.get_image_instance_info(self.node)
        image_service_mock.return_value.show.return_value = {
            'properties': {
                'kernel_id': 'kernel-uuid',
                'ramdisk_id': 'ramdisk-uuid',
            },
        }
        utils.validate_image_properties(self.task, inst_info)
        image_service_mock.assert_called_once_with(
            'glance://image-uuid', context=self.context
        )

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    @mock.patch.object(image_service, 'get_image_service', autospec=True)
    def test_validate_image_properties_ramdisk_glance_boot_iso_id(
            self, image_service_mock, boot_options_mock):
        """Ramdisk deploy with Glance boot_iso_id passes validation."""
        instance_info = {
            'image_source': 'glance://image-uuid',
        }
        self.node.instance_info = instance_info
        inst_info = utils.get_image_instance_info(self.node)
        image_service_mock.return_value.show.return_value = {
            'properties': {
                'boot_iso_id': 'boot-iso-uuid',
            },
        }
        utils.validate_image_properties(self.task, inst_info)
        image_service_mock.assert_called_once_with(
            'glance://image-uuid', context=self.context
        )

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service, 'get_image_service', autospec=True)
    def test_validate_image_properties_non_ramdisk_boot_iso_id_fails(
            self, image_service_mock, boot_options_mock):
        """Non-ramdisk deploy with only boot_iso_id fails validation."""
        instance_info = {
            'image_source': 'glance://image-uuid',
        }
        self.node.instance_info = instance_info
        inst_info = utils.get_image_instance_info(self.node)
        image_service_mock.return_value.show.return_value = {
            'properties': {
                'boot_iso_id': 'boot-iso-uuid',
            },
        }
        self.assertRaises(
            exception.MissingParameterValue,
            utils.validate_image_properties,
            self.task, inst_info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service.HttpImageService, 'show', autospec=True)
    def test_validate_image_properties_anaconda_deploy_image_source(
            self, image_service_show_mock, boot_options_mock):
        d_ii = self.node.driver_internal_info
        d_ii.pop('is_whole_disk_image')
        d_ii['is_source_a_path'] = True
        instance_info = {
            'kernel': 'file://kernel',
            'ramdisk': 'file://initrd',
            'image_source': 'http://foo/bar/'
        }
        self.node.instance_info = instance_info
        inst_info = utils.get_image_instance_info(self.node)
        utils.validate_image_properties(self.task, inst_info)
        image_service_show_mock.assert_not_called()


class ValidateParametersTestCase(db_base.DbTestCase):

    def _test__get_img_instance_info(
            self, instance_info=INST_INFO_DICT,
            driver_info=DRV_INFO_DICT,
            driver_internal_info=DRV_INTERNAL_INFO_DICT):
        # make sure we get back the expected things
        node = obj_utils.create_test_node(
            self.context,
            boot_interface='pxe',
            instance_info=instance_info,
            driver_info=driver_info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )

        return utils.get_image_instance_info(node)

    def test__get_img_instance_info_good(self):
        info = self._test__get_img_instance_info()
        self.assertIsNotNone(info['image_source'])

    def test__get_img_instance_info_good_non_glance_image(self):
        instance_info = INST_INFO_DICT.copy()
        instance_info['image_source'] = 'http://image'

        info = self._test__get_img_instance_info(instance_info=instance_info)

        self.assertIsNotNone(info['image_source'])
        self.assertNotIn('kernel', info)
        self.assertNotIn('ramdisk', info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    def test__get_img_instance_info_good_non_glance_image_anaconda(
            self, mock_boot_opt):
        instance_info = INST_INFO_DICT.copy()
        instance_info['image_source'] = 'http://image'
        instance_info['kernel'] = 'http://kernel'
        instance_info['ramdisk'] = 'http://ramdisk'

        info = self._test__get_img_instance_info(instance_info=instance_info)

        self.assertIsNotNone(info['image_source'])
        self.assertIsNotNone(info['ramdisk'])
        self.assertIsNotNone(info['kernel'])

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    def test__get_img_instance_info_non_glance_image_missing_kernel(
            self, mock_boot_opt):
        instance_info = INST_INFO_DICT.copy()
        instance_info['image_source'] = 'http://image'
        instance_info['ramdisk'] = 'http://ramdisk'

        self.assertRaises(
            exception.MissingParameterValue,
            self._test__get_img_instance_info,
            instance_info=instance_info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    def test__get_img_instance_info_non_glance_image_missing_ramdisk(
            self, mock_boot_opt):
        instance_info = INST_INFO_DICT.copy()
        instance_info['image_source'] = 'http://image'
        instance_info['kernel'] = 'http://kernel'

        self.assertRaises(
            exception.MissingParameterValue,
            self._test__get_img_instance_info,
            instance_info=instance_info)

    def test__get_img_instance_info_missing_image_source(self):
        instance_info = INST_INFO_DICT.copy()
        del instance_info['image_source']

        self.assertRaises(
            exception.MissingParameterValue,
            self._test__get_img_instance_info,
            instance_info=instance_info)

    def test__get_img_instance_info_whole_disk_image(self):
        driver_internal_info = DRV_INTERNAL_INFO_DICT.copy()
        driver_internal_info['is_whole_disk_image'] = True

        info = self._test__get_img_instance_info(
            driver_internal_info=driver_internal_info)
        self.assertIsNotNone(info['image_source'])

    def test__get_img_instance_info_boot_iso_only(self):
        instance_info = {
            'boot_iso': 'http://iso'
        }

        info = self._test__get_img_instance_info(instance_info=instance_info)
        self.assertIsNotNone(info['boot_iso'])
        self.assertNotIn('image_source', info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    def test__get_img_instance_info_ramdisk_deploy(self, mock_boot_opt):
        instance_info = {
            'kernel': 'http://kernel',
            'ramdisk': 'http://ramdisk',
        }

        info = self._test__get_img_instance_info(instance_info=instance_info)
        self.assertIsNotNone(info['kernel'])
        self.assertIsNotNone(info['ramdisk'])
        self.assertNotIn('image_source', info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    def test__get_img_instance_info_ramdisk_with_image_source(
            self, mock_boot_opt):
        """Ramdisk boot with only image_source (Nova/Glance path)."""
        instance_info = {
            'image_source': 'glance://image-uuid',
        }

        info = self._test__get_img_instance_info(
            instance_info=instance_info)
        self.assertEqual('glance://image-uuid', info['image_source'])
        self.assertNotIn('kernel', info)
        self.assertNotIn('ramdisk', info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    def test__get_img_instance_info_ramdisk_with_kernel_ramdisk(
            self, mock_boot_opt):
        """Ramdisk boot with kernel+ramdisk takes precedence."""
        instance_info = {
            'image_source': 'glance://image-uuid',
            'kernel': 'http://kernel',
            'ramdisk': 'http://ramdisk',
        }

        info = self._test__get_img_instance_info(
            instance_info=instance_info)
        self.assertEqual('http://kernel', info['kernel'])
        self.assertEqual('http://ramdisk', info['ramdisk'])
        self.assertNotIn('image_source', info)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='ramdisk')
    def test__get_img_instance_info_ramdisk_nothing(
            self, mock_boot_opt):
        """Ramdisk boot with nothing set raises error."""
        instance_info = {}

        self.assertRaises(
            exception.MissingParameterValue,
            self._test__get_img_instance_info,
            instance_info=instance_info)


class InstanceInfoTestCase(db_base.DbTestCase):

    def test_parse_instance_info_good(self):
        # make sure we get back the expected things
        node = obj_utils.create_test_node(
            self.context, boot_interface='pxe',
            instance_info=INST_INFO_DICT,
            driver_internal_info=DRV_INTERNAL_INFO_DICT
        )
        info = utils.parse_instance_info(node)
        self.assertIsNotNone(info['image_source'])
        self.assertIsNotNone(info['root_gb'])
        self.assertEqual(0, info['ephemeral_gb'])
        self.assertIsNone(info['configdrive'])

    def test_parse_instance_info_missing_instance_source(self):
        # make sure error is raised when info is missing
        info = dict(INST_INFO_DICT)
        del info['image_source']
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.assertRaises(exception.MissingParameterValue,
                          utils.parse_instance_info,
                          node)

    def test_parse_instance_info_missing_root_gb(self):
        # make sure error is raised when info is missing
        info = dict(INST_INFO_DICT)
        del info['root_gb']

        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.assertRaises(exception.MissingParameterValue,
                          utils.parse_instance_info,
                          node)

    def test_parse_instance_info_invalid_root_gb(self):
        info = dict(INST_INFO_DICT)
        info['root_gb'] = 'foobar'
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.assertRaises(exception.InvalidParameterValue,
                          utils.parse_instance_info,
                          node)

    def test_parse_instance_info_valid_ephemeral_gb(self):
        ephemeral_gb = 10
        ephemeral_mb = 1024 * ephemeral_gb
        ephemeral_fmt = 'test-fmt'
        info = dict(INST_INFO_DICT)
        info['ephemeral_gb'] = ephemeral_gb
        info['ephemeral_format'] = ephemeral_fmt
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        data = utils.parse_instance_info(node)
        self.assertEqual(ephemeral_mb, data['ephemeral_mb'])
        self.assertEqual(ephemeral_fmt, data['ephemeral_format'])

    def test_parse_instance_info_unicode_swap_mb(self):
        swap_mb = u'10'
        swap_mb_int = 10
        info = dict(INST_INFO_DICT)
        info['swap_mb'] = swap_mb
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        data = utils.parse_instance_info(node)
        self.assertEqual(swap_mb_int, data['swap_mb'])

    def test_parse_instance_info_invalid_ephemeral_gb(self):
        info = dict(INST_INFO_DICT)
        info['ephemeral_gb'] = 'foobar'
        info['ephemeral_format'] = 'exttest'

        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.assertRaises(exception.InvalidParameterValue,
                          utils.parse_instance_info,
                          node)

    def test_parse_instance_info_valid_ephemeral_missing_format(self):
        ephemeral_gb = 10
        ephemeral_fmt = 'test-fmt'
        info = dict(INST_INFO_DICT)
        info['ephemeral_gb'] = ephemeral_gb
        info['ephemeral_format'] = None
        self.config(default_ephemeral_format=ephemeral_fmt, group='pxe')
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        instance_info = utils.parse_instance_info(node)
        self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])

    def test_parse_instance_info_valid_preserve_ephemeral_true(self):
        info = dict(INST_INFO_DICT)
        for opt in ['true', 'TRUE', 'True', 't',
                    'on', 'yes', 'y', '1']:
            info['preserve_ephemeral'] = opt

            node = obj_utils.create_test_node(
                self.context, uuid=uuidutils.generate_uuid(),
                instance_info=info,
                driver_internal_info=DRV_INTERNAL_INFO_DICT,
            )
            data = utils.parse_instance_info(node)
            self.assertTrue(data['preserve_ephemeral'])

    def test_parse_instance_info_valid_preserve_ephemeral_false(self):
        info = dict(INST_INFO_DICT)
        for opt in ['false', 'FALSE', 'False', 'f',
                    'off', 'no', 'n', '0']:
            info['preserve_ephemeral'] = opt
            node = obj_utils.create_test_node(
                self.context, uuid=uuidutils.generate_uuid(),
                instance_info=info,
                driver_internal_info=DRV_INTERNAL_INFO_DICT,
            )
            data = utils.parse_instance_info(node)
            self.assertFalse(data['preserve_ephemeral'])

    def test_parse_instance_info_invalid_preserve_ephemeral(self):
        info = dict(INST_INFO_DICT)
        info['preserve_ephemeral'] = 'foobar'
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.assertRaises(exception.InvalidParameterValue,
                          utils.parse_instance_info,
                          node)

    def test_parse_instance_info_invalid_ephemeral_disk(self):
        info = dict(INST_INFO_DICT)
        info['ephemeral_gb'] = 10
        info['swap_mb'] = 0
        info['root_gb'] = 20
        info['preserve_ephemeral'] = True
        drv_internal_dict = {'instance': {'ephemeral_gb': 9,
                                          'swap_mb': 0,
                                          'root_gb': 20}}
        drv_internal_dict.update(DRV_INTERNAL_INFO_DICT)
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=drv_internal_dict,
        )
        self.assertRaises(exception.InvalidParameterValue,
                          utils.parse_instance_info,
                          node)

    def test__check_disk_layout_unchanged_fails(self):
        info = dict(INST_INFO_DICT)
        info['ephemeral_gb'] = 10
        info['swap_mb'] = 0
        info['root_gb'] = 20
        info['preserve_ephemeral'] = True
        drv_internal_dict = {'instance': {'ephemeral_gb': 20,
                                          'swap_mb': 0,
                                          'root_gb': 20}}
        drv_internal_dict.update(DRV_INTERNAL_INFO_DICT)
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=drv_internal_dict,
        )
        self.assertRaises(exception.InvalidParameterValue,
                          utils._check_disk_layout_unchanged,
                          node, info)

    def test__check_disk_layout_unchanged(self):
        info = dict(INST_INFO_DICT)
        info['ephemeral_gb'] = 10
        info['swap_mb'] = 0
        info['root_gb'] = 20
        info['preserve_ephemeral'] = True
        drv_internal_dict = {'instance': {'ephemeral_gb': 10,
                                          'swap_mb': 0,
                                          'root_gb': 20}}
        drv_internal_dict.update(DRV_INTERNAL_INFO_DICT)
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=drv_internal_dict,
        )
        self.assertIsNone(utils._check_disk_layout_unchanged(node,
                                                             info))

    def test_parse_instance_info_configdrive(self):
        info = dict(INST_INFO_DICT)
        info['configdrive'] = 'http://1.2.3.4/cd'
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        instance_info = utils.parse_instance_info(node)
        self.assertEqual('http://1.2.3.4/cd', instance_info['configdrive'])

    def test_parse_instance_info_nonglance_image(self):
        info = INST_INFO_DICT.copy()
        info['image_source'] = 'file:///image.qcow2'
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        utils.parse_instance_info(node)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    def test_parse_instance_info_nonglance_image_anaconda(self, mock_boot_opt):
        info = INST_INFO_DICT.copy()
        info['image_source'] = 'file:///image.qcow2'
        info['kernel'] = 'file:///image.vmlinuz'
        info['ramdisk'] = 'file:///image.initrd'
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        utils.parse_instance_info(node)

    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    def test_parse_instance_info_nonglance_image_no_kernel(self,
                                                           mock_boot_opt):
        info = INST_INFO_DICT.copy()
        info['image_source'] = 'file:///image.qcow2'
        info['ramdisk'] = 'file:///image.initrd'
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=DRV_INTERNAL_INFO_DICT,
        )
        self.assertRaises(exception.MissingParameterValue,
                          utils.parse_instance_info, node)

    def test_parse_instance_info_whole_disk_image(self):
        driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
        driver_internal_info['is_whole_disk_image'] = True
        node = obj_utils.create_test_node(
            self.context, instance_info=INST_INFO_DICT,
            driver_internal_info=driver_internal_info,
        )
        instance_info = utils.parse_instance_info(node)
        self.assertIsNotNone(instance_info['image_source'])
        self.assertNotIn('root_mb', instance_info)
        self.assertNotIn('ephemeral_mb', instance_info)
        self.assertNotIn('swap_mb', instance_info)
        self.assertIsNone(instance_info['configdrive'])

    def test_parse_instance_info_whole_disk_image_missing_root(self):
        driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
        driver_internal_info['is_whole_disk_image'] = True
        info = dict(INST_INFO_DICT)
        del info['root_gb']
        node = obj_utils.create_test_node(
            self.context, instance_info=info,
            driver_internal_info=driver_internal_info
        )

        instance_info = utils.parse_instance_info(node)
        self.assertIsNotNone(instance_info['image_source'])
        self.assertNotIn('root_mb', instance_info)
        self.assertNotIn('ephemeral_mb', instance_info)
        self.assertNotIn('swap_mb', instance_info)

    def test_parse_instance_info_non_image_deploy(self):
        driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
        driver_internal_info['is_whole_disk_image'] = None
        instance_info = {'image_source': 'http://cat/meow/',
                         'kernel': 'corgi',
                         'ramdisk': 'mushroom'}
        node = obj_utils.create_test_node(
            self.context, instance_info=instance_info,
            driver_internal_info=driver_internal_info,
            deploy_interface='anaconda'
        )
        instance_info = utils.parse_instance_info(node, image_deploy=False)
        self.assertIsNotNone(instance_info['image_source'])
        self.assertNotIn('root_mb', instance_info)
        self.assertNotIn('ephemeral_mb', instance_info)
        self.assertNotIn('swap_mb', instance_info)
        self.assertIn('kernel', instance_info)
        self.assertIn('ramdisk', instance_info)


class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
    def setUp(self):
        super(TestBuildInstanceInfoForDeploy, self).setUp()
        self.node = obj_utils.create_test_node(self.context,
                                               boot_interface='pxe',
                                               deploy_interface='direct')
        cfg.CONF.set_override('image_download_source', 'swift', group='agent')

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def test_build_instance_info_for_deploy_glance_image(self, glance_mock,
                                                         validate_mock,
                                                         mock_cache_image):
        # NOTE(TheJulia): For humans later: This test is geared towards the
        # swift backed glance path where temprul will be used.
        i_info = self.node.instance_info
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['image_url'] = 'invalid'
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = True
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                      'os_hash_algo': 'sha512', 'os_hash_value': 'fake-sha512',
                      'container_format': 'bare', 'properties': {}}
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        glance_mock.return_value.swift_temp_url.return_value = (
            'http://temp-url')
        expected_info = {
            'configdrive': 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=',
            'foo': 'bar',
            'image_checksum': 'aa',
            'image_container_format': 'bare',
            'image_disk_format': 'qcow2',
            'image_os_hash_algo': 'sha512',
            'image_os_hash_value': 'fake-sha512',
            'image_properties': {},
            'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810',
            'image_tags': [],
            'image_type': 'whole-disk',
            'image_url': 'http://temp-url'
        }
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            glance_mock.return_value.swift_temp_url.assert_called_once_with(
                image_info)
            validate_mock.assert_called_once_with(mock.ANY, 'http://temp-url',
                                                  secret=True)
            self.assertEqual(expected_info, info)
            mock_cache_image.assert_not_called()

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def test_build_instance_info_for_deploy_glance_image_checked(
            self, glance_mock, validate_mock, mock_cache_image):
        # NOTE(TheJulia): For humans later: This test is geared towards the
        # swift backed glance path where temprul will be used.
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        i_info = self.node.instance_info
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['image_url'] = 'invalid'
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = True
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                      'os_hash_algo': 'sha512', 'os_hash_value': 'fake-sha512',
                      'container_format': 'bare', 'properties': {}}
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        glance_mock.return_value.swift_temp_url.return_value = (
            'http://temp-url')
        expected_info = {
            'configdrive': 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=',
            'foo': 'bar',
            'image_checksum': 'aa',
            'image_container_format': 'bare',
            'image_disk_format': 'qcow2',
            'image_os_hash_algo': 'sha512',
            'image_os_hash_value': 'fake-sha512',
            'image_properties': {},
            'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810',
            'image_tags': [],
            'image_type': 'whole-disk',
            'image_url': 'http://temp-url'
        }
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            glance_mock.return_value.swift_temp_url.assert_called_once_with(
                image_info)
            validate_mock.assert_called_once_with(mock.ANY, 'http://temp-url',
                                                  secret=True)
            self.assertEqual(expected_info, info)
            mock_cache_image.assert_called_once_with(
                mock.ANY, mock.ANY, force_raw=False, expected_format='qcow2')

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(utils, 'parse_instance_info', autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def test_build_instance_info_for_deploy_glance_partition_image(
            self, glance_mock, parse_instance_info_mock, validate_mock,
            mock_cache_image):
        # NOTE(TheJulia): For humans later: This test is geared towards the
        # swift backed glance path where temprul will be used.
        i_info = {}
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['image_type'] = 'partition'
        i_info['root_gb'] = 5
        i_info['swap_mb'] = 4
        i_info['ephemeral_gb'] = 0
        i_info['ephemeral_format'] = None
        i_info['configdrive'] = 'configdrive'
        i_info['image_url'] = 'invalid'
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = False
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                      'os_hash_algo': 'sha512', 'os_hash_value': 'fake-sha512',
                      'container_format': 'bare',
                      'properties': {'kernel_id': 'kernel',
                                     'ramdisk_id': 'ramdisk'}}
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        glance_obj_mock = glance_mock.return_value
        glance_obj_mock.swift_temp_url.return_value = 'http://temp-url'
        parse_instance_info_mock.return_value = {'swap_mb': 4}
        image_source = '733d1c44-a2ea-414b-aca7-69decf20d810'
        expected_i_info = {'root_gb': 5,
                           'swap_mb': 4,
                           'ephemeral_gb': 0,
                           'ephemeral_format': None,
                           'configdrive': 'configdrive',
                           'image_source': image_source,
                           'image_url': 'http://temp-url',
                           'image_type': 'partition',
                           'image_tags': [],
                           'image_properties': {'kernel_id': 'kernel',
                                                'ramdisk_id': 'ramdisk'},
                           'image_checksum': 'aa',
                           'image_os_hash_algo': 'sha512',
                           'image_os_hash_value': 'fake-sha512',
                           'image_container_format': 'bare',
                           'image_disk_format': 'qcow2'}
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            glance_mock.return_value.swift_temp_url.assert_called_once_with(
                image_info)
            validate_mock.assert_called_once_with(
                mock.ANY, 'http://temp-url', secret=True)
            image_type = task.node.instance_info['image_type']
            self.assertEqual('partition', image_type)
            self.assertEqual(expected_i_info, info)
            parse_instance_info_mock.assert_called_once_with(task.node)
            mock_cache_image.assert_not_called()

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(utils, 'parse_instance_info', autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def test_build_instance_info_for_deploy_glance_partition_image_checked(
            self, glance_mock, parse_instance_info_mock, validate_mock,
            mock_cache_image):
        # NOTE(TheJulia): For humans later: This test is geared towards the
        # swift backed glance path where temprul will be used.
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        i_info = {}
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['image_type'] = 'partition'
        i_info['root_gb'] = 5
        i_info['swap_mb'] = 4
        i_info['ephemeral_gb'] = 0
        i_info['ephemeral_format'] = None
        i_info['configdrive'] = 'configdrive'
        i_info['image_url'] = 'invalid'
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = False
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                      'os_hash_algo': 'sha512', 'os_hash_value': 'fake-sha512',
                      'container_format': 'bare',
                      'properties': {'kernel_id': 'kernel',
                                     'ramdisk_id': 'ramdisk'}}
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        glance_obj_mock = glance_mock.return_value
        glance_obj_mock.swift_temp_url.return_value = 'http://temp-url'
        parse_instance_info_mock.return_value = {'swap_mb': 4}
        image_source = '733d1c44-a2ea-414b-aca7-69decf20d810'
        expected_i_info = {'root_gb': 5,
                           'swap_mb': 4,
                           'ephemeral_gb': 0,
                           'ephemeral_format': None,
                           'configdrive': 'configdrive',
                           'image_source': image_source,
                           'image_url': 'http://temp-url',
                           'image_type': 'partition',
                           'image_tags': [],
                           'image_properties': {'kernel_id': 'kernel',
                                                'ramdisk_id': 'ramdisk'},
                           'image_checksum': 'aa',
                           'image_os_hash_algo': 'sha512',
                           'image_os_hash_value': 'fake-sha512',
                           'image_container_format': 'bare',
                           'image_disk_format': 'qcow2'}
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            glance_mock.return_value.swift_temp_url.assert_called_once_with(
                image_info)
            validate_mock.assert_called_once_with(
                mock.ANY, 'http://temp-url', secret=True)
            image_type = task.node.instance_info['image_type']
            self.assertEqual('partition', image_type)
            self.assertEqual(expected_i_info, info)
            parse_instance_info_mock.assert_called_once_with(task.node)
            mock_cache_image.assert_called_once_with(
                mock.ANY, mock.ANY, force_raw=False, expected_format='qcow2')

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(utils, 'parse_instance_info', autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def test_build_instance_info_for_deploy_glance_anaconda(
            self, glance_mock, parse_instance_info_mock, validate_mock,
            boot_opt_mock, mock_cache_image):
        i_info = {}
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['kernel'] = '13ce5a56-1de3-4916-b8b2-be778645d003'
        i_info['ramdisk'] = 'a5a370a8-1b39-433f-be63-2c7d708e4b4e'
        i_info['root_gb'] = 5
        i_info['swap_mb'] = 4
        i_info['ephemeral_gb'] = 0
        i_info['ephemeral_format'] = None
        i_info['configdrive'] = 'configdrive'
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = False
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                      'os_hash_algo': 'sha512', 'os_hash_value': 'fake-sha512',
                      'container_format': 'bare',
                      'properties': {'kernel_id': 'kernel',
                                     'ramdisk_id': 'ramdisk'}}
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        glance_obj_mock = glance_mock.return_value
        glance_obj_mock.swift_temp_url.return_value = 'http://temp-url'
        parse_instance_info_mock.return_value = {'swap_mb': 4}
        image_source = '733d1c44-a2ea-414b-aca7-69decf20d810'
        expected_i_info = {'root_gb': 5,
                           'swap_mb': 4,
                           'ephemeral_gb': 0,
                           'ephemeral_format': None,
                           'configdrive': 'configdrive',
                           'image_source': image_source,
                           'image_url': 'http://temp-url',
                           'kernel': 'kernel',
                           'ramdisk': 'ramdisk',
                           'image_type': 'partition',
                           'image_tags': [],
                           'image_properties': {'kernel_id': 'kernel',
                                                'ramdisk_id': 'ramdisk'},
                           'image_checksum': 'aa',
                           'image_os_hash_algo': 'sha512',
                           'image_os_hash_value': 'fake-sha512',
                           'image_container_format': 'bare',
                           'image_disk_format': 'qcow2'}
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            glance_mock.return_value.swift_temp_url.assert_called_once_with(
                image_info)
            validate_mock.assert_called_once_with(
                mock.ANY, 'http://temp-url', secret=True)
            image_type = task.node.instance_info['image_type']
            self.assertEqual('partition', image_type)
            self.assertEqual('kernel', info['kernel'])
            self.assertEqual('ramdisk', info['ramdisk'])
            self.assertEqual(expected_i_info, info)
            parse_instance_info_mock.assert_called_once_with(task.node)
            mock_cache_image.assert_not_called()

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(utils, 'get_boot_option', autospec=True,
                       return_value='kickstart')
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(utils, 'parse_instance_info', autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def test_build_instance_info_for_deploy_glance_anaconda_img_checked(
            self, glance_mock, parse_instance_info_mock, validate_mock,
            boot_opt_mock, mock_cache_image):
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        i_info = {}
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['kernel'] = '13ce5a56-1de3-4916-b8b2-be778645d003'
        i_info['ramdisk'] = 'a5a370a8-1b39-433f-be63-2c7d708e4b4e'
        i_info['root_gb'] = 5
        i_info['swap_mb'] = 4
        i_info['ephemeral_gb'] = 0
        i_info['ephemeral_format'] = None
        i_info['configdrive'] = 'configdrive'
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = False
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                      'os_hash_algo': 'sha512', 'os_hash_value': 'fake-sha512',
                      'container_format': 'bare',
                      'properties': {'kernel_id': 'kernel',
                                     'ramdisk_id': 'ramdisk'}}
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        glance_obj_mock = glance_mock.return_value
        glance_obj_mock.swift_temp_url.return_value = 'http://temp-url'
        parse_instance_info_mock.return_value = {'swap_mb': 4}
        image_source = '733d1c44-a2ea-414b-aca7-69decf20d810'
        expected_i_info = {'root_gb': 5,
                           'swap_mb': 4,
                           'ephemeral_gb': 0,
                           'ephemeral_format': None,
                           'configdrive': 'configdrive',
                           'image_source': image_source,
                           'image_url': 'http://temp-url',
                           'kernel': 'kernel',
                           'ramdisk': 'ramdisk',
                           'image_type': 'partition',
                           'image_tags': [],
                           'image_properties': {'kernel_id': 'kernel',
                                                'ramdisk_id': 'ramdisk'},
                           'image_checksum': 'aa',
                           'image_os_hash_algo': 'sha512',
                           'image_os_hash_value': 'fake-sha512',
                           'image_container_format': 'bare',
                           'image_disk_format': 'qcow2'}
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            glance_mock.return_value.swift_temp_url.assert_called_once_with(
                image_info)
            validate_mock.assert_called_once_with(
                mock.ANY, 'http://temp-url', secret=True)
            image_type = task.node.instance_info['image_type']
            self.assertEqual('partition', image_type)
            self.assertEqual('kernel', info['kernel'])
            self.assertEqual('ramdisk', info['ramdisk'])
            self.assertEqual(expected_i_info, info)
            parse_instance_info_mock.assert_called_once_with(task.node)
            mock_cache_image.assert_called_once_with(
                mock.ANY, mock.ANY, force_raw=False, expected_format='qcow2')

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.OciImageService,
                       'set_image_auth',
                       autospec=True)
    @mock.patch.object(image_service.OciImageService,
                       'identify_specific_image',
                       autospec=True)
    @mock.patch.object(image_service.OciImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_oci_url_remote_download(
            self, validate_href_mock, identify_image_mock,
            set_image_auth_mock, mock_cache_image):
        cfg.CONF.set_override('image_download_source', 'http', group='agent')
        specific_url = 'https://host/user/container/blobs/sha256/f00'
        specific_source = 'oci://host/user/container@sha256:f00'
        identify_image_mock.return_value = {
            'image_url': specific_url,
            'oci_image_manifest_url': specific_source
        }
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'oci://host/user/container'
        i_info['image_pull_secret'] = 'meow'
        i_info['image_url'] = 'prior_failed_url'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertIn('oci_image_manifest_url', info)
            self.assertEqual(specific_url,
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, specific_source, False)
            mock_cache_image.assert_not_called()
            identify_image_mock.assert_called_with(
                mock.ANY, 'oci://host/user/container', 'http',
                'x86_64')
            self.assertEqual(specific_source,
                             task.node.driver_internal_info['image_source'])
            set_image_auth_mock.assert_called_with(
                mock.ANY,
                specific_source,
                {'username': '', 'password': 'meow'})

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.OciImageService,
                       'set_image_auth',
                       autospec=True)
    @mock.patch.object(image_service.OciImageService,
                       'identify_specific_image',
                       autospec=True)
    @mock.patch.object(image_service.OciImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_oci_url_remote_download_rebuild(
            self, validate_href_mock, identify_image_mock,
            set_image_auth_mock, mock_cache_image):
        # There is some special case handling in the method for rebuilds or bad
        # image_disk_info, the intent of this test is to just make sure it is
        # addressed.
        cfg.CONF.set_override('image_download_source', 'http', group='agent')
        specific_url = 'https://host/user/container/blobs/sha256/f00'
        specific_source = 'oci://host/user/container@sha256:f00'
        identify_image_mock.return_value = {
            'image_url': specific_url,
            'oci_image_manifest_url': specific_source,
            'image_disk_format': 'unknown'
        }
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'oci://host/user/container'
        i_info['image_pull_secret'] = 'meow'
        i_info['image_url'] = 'prior_failed_url'
        i_info['image_disk_format'] = 'raw'
        driver_internal_info['is_whole_disk_image'] = True
        driver_internal_info['image_source'] = 'foo'
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertIn('oci_image_manifest_url', info)
            self.assertEqual(specific_url,
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, specific_source, False)
            mock_cache_image.assert_not_called()
            identify_image_mock.assert_called_with(
                mock.ANY, 'oci://host/user/container', 'http',
                'x86_64')
            self.assertEqual(specific_source,
                             task.node.driver_internal_info['image_source'])
            set_image_auth_mock.assert_called_with(
                mock.ANY,
                specific_source,
                {'username': '', 'password': 'meow'})
            self.assertNotIn('image_disk_format', task.node.instance_info)

    @mock.patch.object(utils, '_cache_and_convert_image', autospec=True)
    @mock.patch.object(image_service.OciImageService,
                       'identify_specific_image',
                       autospec=True)
    @mock.patch.object(image_service.OciImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_oci_url_local_download(
            self, validate_href_mock, identify_image_mock,
            mock_cache_image):
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        specific_url = 'https://host/user/container/blobs/sha256/f00'
        specific_source = 'oci://host/user/container@sha256:f00'
        identify_image_mock.return_value = {
            'image_url': specific_url,
            'oci_image_manifest_url': specific_source,
            'image_checksum': 'a' * 64,
            'image_disk_format': 'raw'
        }
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        props = self.node.properties
        i_info['image_source'] = 'oci://host/user/container'
        i_info['image_url'] = 'prior_failed_url'
        driver_internal_info['is_whole_disk_image'] = True
        props['cpu_arch'] = 'aarch64'
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.properties = props
        self.node.save()
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertIn('oci_image_manifest_url', info)
            self.assertEqual(specific_url,
                             info['image_url'])
            validate_href_mock.assert_not_called()
            mock_cache_image.assert_called_once_with(
                mock.ANY, task.node.instance_info)
            identify_image_mock.assert_called_once_with(
                mock.ANY, 'oci://host/user/container', 'local',
                'aarch64')
            self.assertEqual('oci://host/user/container',
                             task.node.instance_info.get('image_source'))
            self.assertEqual(
                specific_source,
                task.node.driver_internal_info.get('image_source'))

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_nonglance_image(
            self, validate_href_mock, mock_cache_image):
        validate_href_mock.return_value.url = 'http://image-ref'
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['image_checksum'] = 'aa'
        i_info['image_url'] = 'prior_failed_url'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(self.node.instance_info['image_source'],
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-ref', False)
            mock_cache_image.assert_not_called()

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_nonglance_image_fmt_checked(
            self, validate_href_mock, mock_cache_image):
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        validate_href_mock.return_value.url = 'http://image-ref'
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['image_checksum'] = 'aa'
        i_info['image_url'] = 'prior_failed_url'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(self.node.instance_info['image_source'],
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-ref', False)
            mock_cache_image.assert_called_once_with(
                mock.ANY, mock.ANY, force_raw=False, expected_format=None)

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_nonglance_image_fmt_not_checked(
            self, validate_href_mock, mock_cache_image):
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        cfg.CONF.set_override('disable_deep_image_inspection', True,
                              group='conductor')
        validate_href_mock.return_value.url = 'http://image-ref'
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['image_checksum'] = 'aa'
        i_info['image_url'] = 'prior_failed_url'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(self.node.instance_info['image_source'],
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-ref', False)
            mock_cache_image.assert_not_called()

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(utils, 'parse_instance_info', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_nonglance_part_img_checked(
            self, validate_href_mock, parse_instance_info_mock,
            mock_cache_image):
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        i_info = {}
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['kernel'] = 'http://kernel-ref'
        i_info['ramdisk'] = 'http://ramdisk-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['configdrive'] = 'configdrive'
        i_info['image_url'] = 'invalid'
        driver_internal_info['is_whole_disk_image'] = False
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        validate_href_mock.side_effect = [
            mock.Mock(url='http://image-ref'),
            mock.Mock(url='http://kernel-ref'),
            mock.Mock(url='http://ramdisk-ref'),
        ]
        parse_instance_info_mock.return_value = {'swap_mb': 5}
        expected_i_info = {'image_disk_format': 'qcow2',
                           'image_source': 'http://image-ref',
                           'image_url': 'http://image-ref',
                           'image_type': 'partition',
                           'kernel': 'http://kernel-ref',
                           'ramdisk': 'http://ramdisk-ref',
                           'image_checksum': 'aa',
                           'root_gb': 10,
                           'swap_mb': 5,
                           'configdrive': 'configdrive'}
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(self.node.instance_info['image_source'],
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-ref', False)
            self.assertEqual('partition', info['image_type'])
            self.assertEqual(expected_i_info, info)
            parse_instance_info_mock.assert_called_once_with(task.node)
            mock_cache_image.assert_called_once_with(
                mock.ANY, mock.ANY, force_raw=False, expected_format=None)

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(utils, 'parse_instance_info', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_nonglance_partition_image(
            self, validate_href_mock, parse_instance_info_mock,
            mock_cache_image):
        i_info = {}
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['kernel'] = 'http://kernel-ref'
        i_info['ramdisk'] = 'http://ramdisk-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['configdrive'] = 'configdrive'
        i_info['image_url'] = 'invalid'
        driver_internal_info['is_whole_disk_image'] = False
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        validate_href_mock.side_effect = [
            mock.Mock(url='http://image-ref'),
            mock.Mock(url='http://kernel-ref'),
            mock.Mock(url='http://ramdisk-ref'),
        ]
        parse_instance_info_mock.return_value = {'swap_mb': 5}
        expected_i_info = {'image_source': 'http://image-ref',
                           'image_url': 'http://image-ref',
                           'image_type': 'partition',
                           'kernel': 'http://kernel-ref',
                           'ramdisk': 'http://ramdisk-ref',
                           'image_checksum': 'aa',
                           'root_gb': 10,
                           'swap_mb': 5,
                           'configdrive': 'configdrive'}
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(self.node.instance_info['image_source'],
                             info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-ref', False)
            self.assertEqual('partition', info['image_type'])
            self.assertEqual(expected_i_info, info)
            parse_instance_info_mock.assert_called_once_with(task.node)
            mock_cache_image.assert_not_called()

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_image_not_found(
            self, validate_href_mock):
        validate_href_mock.side_effect = exception.ImageRefValidationFailed(
            image_href='http://img.qcow2', reason='fail')
        i_info = self.node.instance_info
        i_info['image_source'] = 'http://img.qcow2'
        i_info['image_checksum'] = 'aa'
        i_info['image_url'] = 'invalid'
        self.node.instance_info = i_info
        self.node.save()

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            self.assertRaises(exception.ImageRefValidationFailed,
                              utils.build_instance_info_for_deploy, task)

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_source_is_a_path(
            self, validate_href_mock):
        validate_href_mock.return_value.url = 'http://image-url/folder/'
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-url/folder/'
        i_info['image_url'] = 'invalid'
        driver_internal_info.pop('is_whole_disk_image', None)
        driver_internal_info['is_source_a_path'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(self.node.instance_info['image_source'],
                             info['image_url'])
            self.assertNotIn('root_gb', info)
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-url/folder/', False)

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_source_redirect(
            self, validate_href_mock):
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        url = 'http://image-url/folder'
        r_url = url + '/'
        i_info['image_source'] = 'http://image-url/folder'
        driver_internal_info.pop('is_whole_disk_image', None)
        driver_internal_info['is_source_a_path'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()
        validate_href_mock.return_value = mock.Mock(url=r_url)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertNotEqual(self.node.instance_info['image_source'],
                                info['image_url'])
            self.assertEqual(r_url, info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-url/folder', False)

    @mock.patch.object(utils, 'cache_instance_image', autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_for_deploy_source_redirect_not_path(
            self, validate_href_mock, mock_cache_image):
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        mock_cache_image.return_value = ('fake', '/tmp/foo', 'qcow2')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        url = 'http://image-url/file'
        r_url = 'https://image-url/file'
        i_info['image_source'] = url
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()
        validate_href_mock.return_value = mock.Mock(url=r_url)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertNotEqual(self.node.instance_info['image_source'],
                                info['image_url'])
            self.assertEqual(r_url, info['image_url'])
            validate_href_mock.assert_called_once_with(
                mock.ANY, 'http://image-url/file', False)
            mock_cache_image.assert_called_once_with(mock.ANY,
                                                     task.node,
                                                     force_raw=False,
                                                     expected_format=None)
            self.assertEqual('https://image-url/file',
                             task.node.instance_info['image_url'])
            self.assertEqual('https://image-url/file',
                             task.node.instance_info['image_source'])


class TestBuildInstanceInfoForHttpProvisioning(db_base.DbTestCase):
    def setUp(self):
        super(TestBuildInstanceInfoForHttpProvisioning, self).setUp()
        self.node = obj_utils.create_test_node(self.context,
                                               boot_interface='pxe',
                                               deploy_interface='direct')
        i_info = self.node.instance_info
        i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
        i_info['root_gb'] = 100
        driver_internal_info = self.node.driver_internal_info
        driver_internal_info['is_whole_disk_image'] = True
        self.node.driver_internal_info = driver_internal_info
        self.node.instance_info = i_info
        self.node.save()

        self.checksum_mock = self.useFixture(fixtures.MockPatchObject(
            fileutils, 'compute_file_checksum', autospec=True)).mock
        self.checksum_mock.return_value = 'fake-checksum'
        self.cache_image_mock = self.useFixture(fixtures.MockPatchObject(
            utils, 'cache_instance_image', autospec=True)).mock
        self.fake_path = '/var/lib/ironic/images/{}/disk'.format(
            self.node.uuid)
        self.cache_image_mock.return_value = (
            '733d1c44-a2ea-414b-aca7-69decf20d810',
            self.fake_path,
            'qcow2')
        self.ensure_tree_mock = self.useFixture(fixtures.MockPatchObject(
            utils.fileutils, 'ensure_tree', autospec=True)).mock
        self.create_link_mock = self.useFixture(fixtures.MockPatchObject(
            common_utils, 'create_link_without_raise', autospec=True)).mock

        cfg.CONF.set_override('http_url', 'http://172.172.24.10:8080',
                              group='deploy')
        cfg.CONF.set_override('image_download_source', 'http', group='agent')

        self.expected_url = '/'.join([cfg.CONF.deploy.http_url,
                                     cfg.CONF.deploy.http_image_subdir,
                                     self.node.uuid])
        self.image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
                           'os_hash_algo': 'sha512',
                           'os_hash_value': 'fake-sha512',
                           'container_format': 'bare', 'properties': {},
                           'size': 1024 * 1024 * 1024}

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
    def _test_build_instance_info(self, glance_mock, validate_mock,
                                  image_info={}, expect_raw=False,
                                  expect_format='qcow2',
                                  expect_checksum='fake-sha512',
                                  expect_checksum_algo='sha512'):
        glance_mock.return_value.show = mock.MagicMock(spec_set=[],
                                                       return_value=image_info)
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            instance_info = utils.build_instance_info_for_deploy(task)
            glance_mock.assert_called_once_with(context=task.context)
            glance_mock.return_value.show.assert_called_once_with(
                self.node.instance_info['image_source'])
            self.cache_image_mock.assert_called_once_with(
                task.context,
                task.node,
                force_raw=expect_raw,
                expected_format=expect_format,
                expected_checksum=expect_checksum,
                expected_checksum_algo=expect_checksum_algo)
            symlink_dir = utils._get_http_image_symlink_dir_path()
            symlink_file = utils._get_http_image_symlink_file_path(
                self.node.uuid)
            image_path = utils._get_image_file_path(self.node.uuid)
            self.ensure_tree_mock.assert_called_once_with(symlink_dir)
            self.create_link_mock.assert_called_once_with(image_path,
                                                          symlink_file)
            validate_mock.assert_called_once_with(mock.ANY, self.expected_url,
                                                  secret=False)
            return image_path, instance_info

    def test_build_instance_info_no_force_raw(self):
        cfg.CONF.set_override('force_raw_images', False)
        _, instance_info = self._test_build_instance_info(
            image_info=self.image_info, expect_raw=False)

        self.assertEqual(instance_info['image_checksum'], 'aa')
        self.assertEqual(instance_info['image_disk_format'], 'qcow2')
        self.assertEqual(instance_info['image_os_hash_algo'], 'sha512')
        self.assertEqual(instance_info['image_os_hash_value'],
                         'fake-sha512')
        self.checksum_mock.assert_not_called()

    def test_build_instance_info_force_raw(self):
        cfg.CONF.set_override('force_raw_images', True)
        image_path, instance_info = self._test_build_instance_info(
            image_info=self.image_info, expect_raw=True)

        self.assertIsNone(instance_info['image_checksum'])
        self.assertEqual(instance_info['image_os_hash_algo'], 'sha512')
        self.assertEqual(instance_info['image_os_hash_value'],
                         'fake-checksum')
        self.assertEqual(instance_info['image_disk_format'], 'raw')
        self.checksum_mock.assert_called_once_with(image_path,
                                                   algorithm='sha512')

    def test_build_instance_info_already_raw(self):
        cfg.CONF.set_override('force_raw_images', True)
        self.image_info['disk_format'] = 'raw'
        image_path, instance_info = self._test_build_instance_info(
            image_info=self.image_info, expect_raw=True, expect_format='raw')

        self.assertEqual(instance_info['image_checksum'], 'aa')
        self.assertEqual(instance_info['image_os_hash_algo'], 'sha512')
        self.assertEqual(instance_info['image_os_hash_value'],
                         'fake-sha512')
        self.assertEqual(instance_info['image_disk_format'], 'raw')
        self.checksum_mock.assert_not_called()

    def test_build_instance_info_force_raw_drops_md5(self):
        cfg.CONF.set_override('force_raw_images', True)
        self.image_info['os_hash_algo'] = 'md5'
        image_path, instance_info = self._test_build_instance_info(
            image_info=self.image_info, expect_raw=True,
            expect_checksum_algo='md5')

        self.assertIsNone(instance_info['image_checksum'])
        self.assertEqual(instance_info['image_disk_format'], 'raw')
        calls = [mock.call(image_path, algorithm='sha256')]
        self.checksum_mock.assert_has_calls(calls)

    def test_build_instance_info_already_raw_keeps_md5(self):
        cfg.CONF.set_override('force_raw_images', True)
        self.image_info['os_hash_algo'] = 'md5'
        self.image_info['disk_format'] = 'raw'
        image_path, instance_info = self._test_build_instance_info(
            image_info=self.image_info, expect_raw=True, expect_format='raw',
            expect_checksum_algo='md5')

        self.assertEqual(instance_info['image_checksum'], 'aa')
        self.assertEqual(instance_info['image_disk_format'], 'raw')
        self.checksum_mock.assert_not_called()

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_file_image(self, validate_href_mock):
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'file://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('sha256', info['image_os_hash_algo'])
            self.assertEqual('fake-checksum', info['image_os_hash_value'])
            self.assertEqual('raw', info['image_disk_format'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format=None,
                expected_checksum='aa',
                expected_checksum_algo=None)
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_local_image(self, validate_href_mock):
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        validate_href_mock.return_value.url = 'http://image-ref'
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('sha256', info['image_os_hash_algo'])
            self.assertEqual('fake-checksum', info['image_os_hash_value'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format=None,
                expected_checksum='aa',
                expected_checksum_algo=None)
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_local_image_via_iinfo(self,
                                                       validate_href_mock):
        cfg.CONF.set_override('image_download_source', 'http', group='agent')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['image_download_source'] = 'local'
        i_info['image_disk_format'] = 'qcow2'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('sha256', info['image_os_hash_algo'])
            self.assertEqual('fake-checksum', info['image_os_hash_value'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format='qcow2',
                expected_checksum='aa',
                expected_checksum_algo=None)
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_remote_image_via_http_verified(
            self, validate_href_mock):
        cfg.CONF.set_override('stream_raw_images', False, group='agent')
        cfg.CONF.set_override('image_download_source', 'http', group='agent')
        cfg.CONF.set_override('conductor_always_validates_images', True,
                              group='conductor')
        validate_href_mock.return_value.url = 'http://image-ref'
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['image_download_source'] = 'local'
        i_info['image_disk_format'] = 'qcow2'
        # NOTE(TheJulia): This is the override ability, and we need to
        # explicitly exercise the alternate path
        del i_info['image_download_source']
        del i_info['image_url']
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = 'http://image-ref'

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(expected_url, info['image_url'])
            # Image is not extracted, checksum is not changed,
            # due to not being forced to raw.
            self.assertNotIn('image_os_hash_algo', info)
            self.assertNotIn('image_os_hash_value', info)
            self.cache_image_mock.assert_called_once_with(
                mock.ANY, mock.ANY, force_raw=False,
                expected_format='qcow2')
            self.checksum_mock.assert_not_called()
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)
            self.assertEqual(expected_url,
                             task.node.instance_info['image_url'])

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_local_image_via_dinfo(self,
                                                       validate_href_mock):
        cfg.CONF.set_override('image_download_source', 'http', group='agent')
        i_info = self.node.instance_info
        d_info = self.node.driver_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        d_info['image_download_source'] = 'local'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_info = d_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)

            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('sha256', info['image_os_hash_algo'])
            self.assertEqual('fake-checksum', info['image_os_hash_value'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format=None,
                expected_checksum='aa',
                expected_checksum_algo=None)
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)

    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_local_image_already_raw(self,
                                                         validate_href_mock):
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'aa'
        i_info['root_gb'] = 10
        i_info['image_disk_format'] = 'raw'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('aa', info['image_checksum'])
            self.assertEqual('raw', info['image_disk_format'])
            self.assertIsNone(info['image_os_hash_algo'])
            self.assertIsNone(info['image_os_hash_value'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format='raw', expected_checksum='aa',
                expected_checksum_algo=None)
            self.checksum_mock.assert_not_called()
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)

    @mock.patch.object(image_service.HttpImageService, 'get',
                       autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_remote_checksum_image(self,
                                                       validate_href_mock,
                                                       get_mock):
        # Test case where we would download both the image and the checksum
        # and ultimately convert the image.
        get_mock.return_value = 'd8e8fca2dc0f896fd7cb4cb0031ba249'
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'http://image-ref'
        i_info['image_checksum'] = 'http://image-checksum'
        i_info['root_gb'] = 10
        i_info['image_disk_format'] = 'qcow2'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('raw', info['image_disk_format'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format='qcow2',
                expected_checksum='d8e8fca2dc0f896fd7cb4cb0031ba249',
                expected_checksum_algo=None)
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)
            get_mock.assert_called_once_with('http://image-checksum')

    @mock.patch.object(image_service.HttpImageService, 'get',
                       autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_remote_checksum_sha256(self,
                                                        validate_href_mock,
                                                        get_mock):
        # Test case where we would download both the image and the checksum
        # and ultimately convert the image.
        get_mock.return_value = 'a' * 64 + '\n'
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'https://image-ref'
        i_info['image_checksum'] = 'https://image-checksum'
        i_info['root_gb'] = 10
        i_info['image_disk_format'] = 'qcow2'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('raw', info['image_disk_format'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format='qcow2',
                expected_checksum='a' * 64,
                expected_checksum_algo='sha256')
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)
            get_mock.assert_called_once_with('https://image-checksum')

    @mock.patch.object(image_service.HttpImageService, 'get',
                       autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_remote_checksum_sha512(self,
                                                        validate_href_mock,
                                                        get_mock):
        # Test case where we would download both the image and the checksum
        # and ultimately convert the image.
        get_mock.return_value = 'a' * 128 + '\n'
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'https://image-ref'
        i_info['image_checksum'] = 'https://image-checksum'
        i_info['root_gb'] = 10
        i_info['image_disk_format'] = 'qcow2'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        expected_url = (
            'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            info = utils.build_instance_info_for_deploy(task)
            self.assertEqual(expected_url, info['image_url'])
            self.assertEqual('raw', info['image_disk_format'])
            self.cache_image_mock.assert_called_once_with(
                task.context, task.node, force_raw=True,
                expected_format='qcow2',
                expected_checksum='a' * 128,
                expected_checksum_algo='sha512')
            self.checksum_mock.assert_called_once_with(
                self.fake_path, algorithm='sha256')
            validate_href_mock.assert_called_once_with(
                mock.ANY, expected_url, False)
            get_mock.assert_called_once_with('https://image-checksum')

    @mock.patch.object(checksum_utils, 'get_checksum_from_url',
                       autospec=True)
    @mock.patch.object(image_service.HttpImageService, 'validate_href',
                       autospec=True)
    def test_build_instance_info_md5_not_permitted(
            self,
            validate_href_mock,
            get_from_url_mock):
        cfg.CONF.set_override('allow_md5_checksum', False, group='agent')
        # Test case where we would download both the image and the checksum
        # and ultimately convert the image.
        cfg.CONF.set_override('image_download_source', 'local', group='agent')
        i_info = self.node.instance_info
        driver_internal_info = self.node.driver_internal_info
        i_info['image_source'] = 'image-ref'
        i_info['image_checksum'] = 'ad606d6a24a2dec982bc2993aaaf9160'
        i_info['root_gb'] = 10
        i_info['image_disk_format'] = 'qcow2'
        driver_internal_info['is_whole_disk_image'] = True
        self.node.instance_info = i_info
        self.node.driver_internal_info = driver_internal_info
        self.node.save()

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:

            self.assertRaisesRegex(exception.ImageChecksumAlgorithmFailure,
                                   'The requested image checksum algorithm '
                                   'cannot be loaded',
                                   utils.build_instance_info_for_deploy,
                                   task)

            self.cache_image_mock.assert_not_called()
            self.checksum_mock.assert_not_called()
            validate_href_mock.assert_not_called()
            get_from_url_mock.assert_not_called()


class TestStorageInterfaceUtils(db_base.DbTestCase):
    def setUp(self):
        super(TestStorageInterfaceUtils, self).setUp()
        self.node = obj_utils.create_test_node(self.context,
                                               driver='fake-hardware')
        self.config(enabled_storage_interfaces=['noop', 'fake', 'cinder'])

    def test_check_interface_capability(self):
        class fake_driver(object):
            capabilities = ['foo', 'bar']

        self.assertTrue(utils.check_interface_capability(fake_driver, 'foo'))
        self.assertFalse(utils.check_interface_capability(fake_driver, 'baz'))

    def test_get_remote_boot_volume(self):
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=1, volume_id='4321')
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=0, volume_id='1234', uuid=uuidutils.generate_uuid())
        self.node.storage_interface = 'cinder'
        self.node.save()

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            volume = utils.get_remote_boot_volume(task)
            self.assertEqual('1234', volume['volume_id'])

    def test_get_remote_boot_volume_none(self):
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertIsNone(utils.get_remote_boot_volume(task))
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=1, volume_id='4321')
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertIsNone(utils.get_remote_boot_volume(task))

    @mock.patch.object(fake.FakeBoot, 'capabilities',
                       ['iscsi_volume_boot'], create=True)
    @mock.patch.object(fake.FakeDeploy, 'capabilities',
                       ['iscsi_volume_deploy'], create=True)
    @mock.patch.object(cinder.CinderStorage, 'should_write_image',
                       autospec=True)
    def test_populate_storage_driver_internal_info_iscsi(self,
                                                         mock_should_write):
        mock_should_write.return_value = True
        vol_uuid = uuidutils.generate_uuid()
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=0, volume_id='1234', uuid=vol_uuid)
        # NOTE(TheJulia): Since the default for the storage_interface
        # is a noop interface, we need to define another driver that
        # can be loaded by driver_manager in order to create the task
        # to test this method.
        self.node.storage_interface = "cinder"
        self.node.save()

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            driver_utils.add_node_capability(task,
                                             'iscsi_boot',
                                             'True')
            utils.populate_storage_driver_internal_info(task)
            self.assertEqual(
                vol_uuid,
                task.node.driver_internal_info.get('boot_from_volume', None))
            self.assertEqual(
                vol_uuid,
                task.node.driver_internal_info.get('boot_from_volume_deploy',
                                                   None))

    @mock.patch.object(fake.FakeBoot, 'capabilities',
                       ['fibre_channel_volume_boot'], create=True)
    @mock.patch.object(fake.FakeDeploy, 'capabilities',
                       ['fibre_channel_volume_deploy'], create=True)
    @mock.patch.object(cinder.CinderStorage, 'should_write_image',
                       autospec=True)
    def test_populate_storage_driver_internal_info_fc(self,
                                                      mock_should_write):
        mock_should_write.return_value = True
        self.node.storage_interface = "cinder"
        self.node.save()

        vol_uuid = uuidutils.generate_uuid()
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='fibre_channel',
            boot_index=0, volume_id='1234', uuid=vol_uuid)
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            driver_utils.add_node_capability(task,
                                             'fibre_channel_boot',
                                             'True')
            utils.populate_storage_driver_internal_info(task)
            self.assertEqual(
                vol_uuid,
                task.node.driver_internal_info.get('boot_from_volume', None))
            self.assertEqual(
                vol_uuid,
                task.node.driver_internal_info.get('boot_from_volume_deploy',
                                                   None))

    @mock.patch.object(fake.FakeBoot, 'capabilities',
                       ['fibre_channel_volume_boot'], create=True)
    @mock.patch.object(fake.FakeDeploy, 'capabilities',
                       ['fibre_channel_volume_deploy'], create=True)
    def test_populate_storage_driver_internal_info_error(self):
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=0, volume_id='1234')

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertRaises(exception.StorageError,
                              utils.populate_storage_driver_internal_info,
                              task)

    def test_tear_down_storage_configuration(self):
        vol_uuid = uuidutils.generate_uuid()
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=0, volume_id='1234', uuid=vol_uuid)
        d_i_info = self.node.driver_internal_info
        d_i_info['boot_from_volume'] = vol_uuid
        d_i_info['boot_from_volume_deploy'] = vol_uuid
        self.node.driver_internal_info = d_i_info
        self.node.save()

        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            node = task.node

            self.assertEqual(1, len(task.volume_targets))
            self.assertEqual(
                vol_uuid,
                node.driver_internal_info.get('boot_from_volume'))
            self.assertEqual(
                vol_uuid,
                node.driver_internal_info.get('boot_from_volume_deploy'))

            utils.tear_down_storage_configuration(task)

            node.refresh()
            self.assertIsNone(
                node.driver_internal_info.get('boot_from_volume'))
            self.assertIsNone(
                node.driver_internal_info.get('boot_from_volume_deploy'))
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertEqual(0, len(task.volume_targets))

    def test_is_iscsi_boot(self):
        vol_id = uuidutils.generate_uuid()
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='iscsi',
            boot_index=0, volume_id='1234', uuid=vol_id)
        self.node.driver_internal_info = {'boot_from_volume': vol_id}
        self.node.save()
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertTrue(utils.is_iscsi_boot(task))

    def test_is_iscsi_boot_exception(self):
        self.node.driver_internal_info = {
            'boot_from_volume': uuidutils.generate_uuid()}
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertFalse(utils.is_iscsi_boot(task))

    def test_is_iscsi_boot_false(self):
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertFalse(utils.is_iscsi_boot(task))

    def test_is_iscsi_boot_false_fc_target(self):
        vol_id = uuidutils.generate_uuid()
        obj_utils.create_test_volume_target(
            self.context, node_id=self.node.id, volume_type='fibre_channel',
            boot_index=0, volume_id='3214', uuid=vol_id)
        self.node.driver_internal_info.update({'boot_from_volume': vol_id})
        self.node.save()
        with task_manager.acquire(
                self.context, self.node.uuid, shared=False) as task:
            self.assertFalse(utils.is_iscsi_boot(task))


class InstanceImageCacheTestCase(db_base.DbTestCase):
    @mock.patch.object(fileutils, 'ensure_tree', autospec=True)
    def test_with_master_path(self, mock_ensure_tree):
        self.config(instance_master_path='/fake/path', group='pxe')
        self.config(image_cache_size=500, group='pxe')
        self.config(image_cache_ttl=30, group='pxe')

        cache = utils.InstanceImageCache()

        mock_ensure_tree.assert_called_once_with('/fake/path')
        self.assertEqual(500 * 1024 * 1024, cache._cache_size)
        self.assertEqual(30 * 60, cache._cache_ttl)

    @mock.patch.object(fileutils, 'ensure_tree', autospec=True)
    def test_without_master_path(self, mock_ensure_tree):
        self.config(instance_master_path='', group='pxe')
        self.config(image_cache_size=500, group='pxe')
        self.config(image_cache_ttl=30, group='pxe')

        cache = utils.InstanceImageCache()

        mock_ensure_tree.assert_not_called()
        self.assertEqual(500 * 1024 * 1024, cache._cache_size)
        self.assertEqual(30 * 60, cache._cache_ttl)


class AsyncStepTestCase(db_base.DbTestCase):

    def setUp(self):
        super(AsyncStepTestCase, self).setUp()
        self.node = obj_utils.create_test_node(self.context,
                                               driver="fake-hardware")

    def test_get_async_step_return_state_cleaning(self):
        self.node.clean_step = {'step': 'create_configuration',
                                'interface': 'raid'}
        self.node.save()
        result = utils.get_async_step_return_state(self.node)
        self.assertEqual(states.CLEANWAIT, result)

    def test_get_async_step_return_state_servicing(self):
        self.node.service_step = {'step': 'create_configuration',
                                  'interface': 'raid'}
        self.node.save()
        result = utils.get_async_step_return_state(self.node)
        self.assertEqual(states.SERVICEWAIT, result)

    def test_get_async_step_return_state_deploying(self):
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.save()
        result = utils.get_async_step_return_state(self.node)
        self.assertEqual(states.DEPLOYWAIT, result)

    def test_get_async_step_return_state_no_step(self):
        self.assertRaises(exception.InvalidState,
                          utils.get_async_step_return_state,
                          self.node)

    def test_set_async_step_flags_cleaning_set_all(self):
        self.node.clean_step = {'step': 'create_configuration',
                                'interface': 'raid'}
        self.node.driver_internal_info = {}
        expected = {'cleaning_reboot': True,
                    'cleaning_polling': True,
                    'skip_current_clean_step': True}
        self.node.save()
        utils.set_async_step_flags(self.node, reboot=True,
                                   skip_current_step=True,
                                   polling=True)
        self.assertEqual(expected, self.node.driver_internal_info)

    def test_set_async_step_flags_cleaning_set_one(self):
        self.node.clean_step = {'step': 'create_configuration',
                                'interface': 'raid'}
        self.node.driver_internal_info = {}
        self.node.save()
        utils.set_async_step_flags(self.node, reboot=True)
        self.assertEqual({'cleaning_reboot': True},
                         self.node.driver_internal_info)

    def test_set_async_step_flags_deploying_set_all(self):
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.driver_internal_info = {
            'agent_secret_token': 'test',
            'agent_secret_token_pregenerated': True}
        expected = {'deployment_reboot': True,
                    'deployment_polling': True,
                    'skip_current_deploy_step': True,
                    'agent_secret_token': 'test',
                    'agent_secret_token_pregenerated': True}
        self.node.save()
        utils.set_async_step_flags(self.node, reboot=True,
                                   skip_current_step=True,
                                   polling=True)
        self.assertEqual(expected, self.node.driver_internal_info)

    def test_set_async_step_flags_deploying_set_one(self):
        self.node.deploy_step = {'step': 'create_configuration',
                                 'interface': 'raid'}
        self.node.driver_internal_info = {}
        self.node.save()
        utils.set_async_step_flags(self.node, reboot=True)
        self.assertEqual({'deployment_reboot': True},
                         self.node.driver_internal_info)

    def test_set_async_step_flags_clears_non_pregenerated_token(self):
        self.node.clean_step = {'step': 'create_configuration',
                                'interface': 'raid'}
        self.node.driver_internal_info = {'agent_secret_token': 'test'}
        expected = {'cleaning_reboot': True,
                    'cleaning_polling': True,
                    'skip_current_clean_step': True}
        self.node.save()
        utils.set_async_step_flags(self.node, reboot=True,
                                   skip_current_step=True,
                                   polling=True)
        self.assertEqual(expected, self.node.driver_internal_info)

    def test_set_async_step_flags_clears_polling_if_not_set(self):
        self.node.clean_step = {'step': 'create_configuration',
                                'interface': 'raid'}
        self.node.driver_internal_info = {'agent_secret_token': 'test',
                                          'cleaning_polling': True}
        expected = {'cleaning_reboot': True,
                    'skip_current_clean_step': True}
        self.node.save()
        utils.set_async_step_flags(self.node, reboot=True,
                                   skip_current_step=True)
        self.assertEqual(expected, self.node.driver_internal_info)
