# Copyright 2018 Hewlett Packard Enterprise Development LP
#
# 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.

"""
iLO5 RAID specific methods
"""

from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils

from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import raid
from ironic.common import states
from ironic.conductor import utils as manager_utils
from ironic import conf
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common


LOG = logging.getLogger(__name__)
CONF = conf.CONF
METRICS = metrics_utils.get_metrics_logger(__name__)

ilo_error = importutils.try_import('proliantutils.exception')


class Ilo5RAID(base.RAIDInterface):
    """Implementation of OOB RAIDInterface for iLO5."""

    def get_properties(self):
        """Return the properties of the interface."""
        return ilo_common.REQUIRED_PROPERTIES

    def _set_clean_failed(self, task, msg, exc):
        LOG.error("RAID configuration job failed for node %(node)s. "
                  "Message: '%(message)s'.",
                  {'node': task.node.uuid, 'message': msg})
        task.node.last_error = msg
        task.process_event('fail')

    def _set_driver_internal_true_value(self, task, *keys):
        driver_internal_info = task.node.driver_internal_info
        for key in keys:
            driver_internal_info[key] = True
        task.node.driver_internal_info = driver_internal_info
        task.node.save()

    def _set_driver_internal_false_value(self, task, *keys):
        driver_internal_info = task.node.driver_internal_info
        for key in keys:
            driver_internal_info[key] = False
        task.node.driver_internal_info = driver_internal_info
        task.node.save()

    def _pop_driver_internal_values(self, task, *keys):
        driver_internal_info = task.node.driver_internal_info
        for key in keys:
            driver_internal_info.pop(key, None)
        task.node.driver_internal_info = driver_internal_info
        task.node.save()

    def _prepare_for_read_raid(self, task, raid_step):
        deploy_opts = deploy_utils.build_agent_options(task.node)
        task.driver.boot.prepare_ramdisk(task, deploy_opts)
        manager_utils.node_power_action(task, states.REBOOT)
        if raid_step == 'create_raid':
            self._set_driver_internal_true_value(
                task, 'ilo_raid_create_in_progress')
        else:
            self._set_driver_internal_true_value(
                task, 'ilo_raid_delete_in_progress')
        self._set_driver_internal_true_value(task, 'cleaning_reboot')
        self._set_driver_internal_false_value(task, 'skip_current_clean_step')

    @METRICS.timer('Ilo5RAID.create_configuration')
    @base.clean_step(priority=0, abortable=False, argsinfo={
        'create_root_volume': {
            'description': (
                'This specifies whether to create the root volume. '
                'Defaults to `True`.'
            ),
            'required': False
        },
        'create_nonroot_volumes': {
            'description': (
                'This specifies whether to create the non-root volumes. '
                'Defaults to `True`.'
            ),
            'required': False
        }
    })
    def create_configuration(self, task, create_root_volume=True,
                             create_nonroot_volumes=True):
        """Create a RAID configuration on a bare metal using agent ramdisk.

        This method creates a RAID configuration on the given node.

        :param task: a TaskManager instance.
        :param create_root_volume: If True, a root volume is created
            during RAID configuration. Otherwise, no root volume is
            created. Default is True.
        :param create_nonroot_volumes: If True, non-root volumes are
            created. If False, no non-root volumes are created. Default
            is True.
        :raises: MissingParameterValue, if node.target_raid_config is missing
            or was found to be empty after skipping root volume and/or non-root
            volumes.
        :raises: NodeCleaningFailure, on failure to execute step.
        """
        node = task.node
        target_raid_config = raid.filter_target_raid_config(
            node, create_root_volume=create_root_volume,
            create_nonroot_volumes=create_nonroot_volumes)
        driver_internal_info = node.driver_internal_info
        driver_internal_info['target_raid_config'] = target_raid_config
        LOG.debug("Calling OOB RAID create_configuration for node %(node)s "
                  "with the following target RAID configuration: %(target)s",
                  {'node': node.uuid, 'target': target_raid_config})
        ilo_object = ilo_common.get_ilo_object(node)

        try:
            # Raid configuration in progress, checking status
            if not driver_internal_info.get('ilo_raid_create_in_progress'):
                ilo_object.create_raid_configuration(target_raid_config)
                self._prepare_for_read_raid(task, 'create_raid')
                return states.CLEANWAIT
            else:
                # Raid configuration is done, updating raid_config
                raid_conf = (
                    ilo_object.read_raid_configuration(
                        raid_config=target_raid_config))
                if len(raid_conf['logical_disks']):
                    raid.update_raid_info(node, raid_conf)
                    LOG.debug("Node %(uuid)s raid create clean step is done.",
                              {'uuid': node.uuid})
                    self._pop_driver_internal_values(
                        task, 'ilo_raid_create_in_progress',
                        'cleaning_reboot', 'skip_current_clean_step')
                    node.driver_internal_info = driver_internal_info
                    node.save()
                else:
                    # Raid configuration failed
                    msg = "Unable to create raid"
                    self._pop_driver_internal_values(
                        task, 'ilo_raid_create_in_progress',
                        'cleaning_reboot', 'skip_current_clean_step')
                    node.driver_internal_info = driver_internal_info
                    node.save()
                    raise exception.NodeCleaningFailure(
                        "Clean step create_configuration failed "
                        "on node %(node)s with error: %(err)s" %
                        {'node': node.uuid, 'err': msg})
        except ilo_error.IloError as ilo_exception:
            operation = (_("Failed to create raid configuration on node %s")
                         % node.uuid)
            self._pop_driver_internal_values(task,
                                             'ilo_raid_create_in_progress',
                                             'cleaning_reboot',
                                             'skip_current_clean_step')
            node.driver_internal_info = driver_internal_info
            node.save()
            self._set_clean_failed(task, operation, ilo_exception)

    @METRICS.timer('Ilo5RAID.delete_configuration')
    @base.clean_step(priority=0, abortable=False)
    def delete_configuration(self, task):
        """Delete the RAID configuration.

        :param task: a TaskManager instance  containing the node to act on.
        :raises: NodeCleaningFailure, on failure to execute step.
        """
        node = task.node
        LOG.debug("OOB RAID delete_configuration invoked for node %s.",
                  node.uuid)
        driver_internal_info = node.driver_internal_info
        ilo_object = ilo_common.get_ilo_object(node)

        try:
            # Raid configuration in progress, checking status
            if not driver_internal_info.get('ilo_raid_delete_in_progress'):
                ilo_object.delete_raid_configuration()
                self._prepare_for_read_raid(task, 'delete_raid')
                return states.CLEANWAIT
            else:
                # Raid configuration is done, updating raid_config
                raid_conf = ilo_object.read_raid_configuration()
                if not len(raid_conf['logical_disks']):
                    node.raid_config = {}
                    LOG.debug("Node %(uuid)s raid delete clean step is done.",
                              {'uuid': node.uuid})
                    self._pop_driver_internal_values(
                        task, 'ilo_raid_delete_in_progress',
                        'cleaning_reboot', 'skip_current_clean_step')
                    node.driver_internal_info = driver_internal_info
                    node.save()
                else:
                    # Raid configuration failed
                    msg = ("Unable to delete this logical disks: %s" %
                           raid_conf['logical_disks'])
                    self._pop_driver_internal_values(
                        task, 'ilo_raid_delete_in_progress',
                        'cleaning_reboot', 'skip_current_clean_step')
                    node.driver_internal_info = driver_internal_info
                    node.save()
                    raise exception.NodeCleaningFailure(
                        "Clean step delete_configuration failed "
                        "on node %(node)s with error: %(err)s" %
                        {'node': node.uuid, 'err': msg})
        except ilo_error.IloLogicalDriveNotFoundError:
            LOG.info("No logical drive found to delete on node %(node)s",
                     {'node': node.uuid})
        except ilo_error.IloError as ilo_exception:
            operation = (_("Failed to delete raid configuration on node %s")
                         % node.uuid)
            self._pop_driver_internal_values(task,
                                             'ilo_raid_delete_in_progress',
                                             'cleaning_reboot',
                                             'skip_current_clean_step')
            node.driver_internal_info = driver_internal_info
            node.save()
            self._set_clean_failed(task, operation, ilo_exception)

[((34, 6, 34, 33), 'oslo_log.log.getLogger', 'logging.getLogger', ({(34, 24, 34, 32): '__name__'}, {}), '(__name__)', True, 'from oslo_log import log as logging\n'), ((36, 10, 36, 52), 'ironic_lib.metrics_utils.get_metrics_logger', 'metrics_utils.get_metrics_logger', ({(36, 43, 36, 51): '__name__'}, {}), '(__name__)', False, 'from ironic_lib import metrics_utils\n'), ((38, 12, 38, 61), 'oslo_utils.importutils.try_import', 'importutils.try_import', ({(38, 35, 38, 60): '"""proliantutils.exception"""'}, {}), "('proliantutils.exception')", False, 'from oslo_utils import importutils\n'), ((90, 5, 105, 6), 'ironic.drivers.base.clean_step', 'base.clean_step', (), '', False, 'from ironic.drivers import base\n'), ((179, 5, 179, 49), 'ironic.drivers.base.clean_step', 'base.clean_step', (), '', False, 'from ironic.drivers import base\n'), ((77, 22, 77, 65), 'ironic.drivers.modules.deploy_utils.build_agent_options', 'deploy_utils.build_agent_options', ({(77, 55, 77, 64): 'task.node'}, {}), '(task.node)', False, 'from ironic.drivers.modules import deploy_utils\n'), ((79, 8, 79, 60), 'ironic.conductor.utils.node_power_action', 'manager_utils.node_power_action', ({(79, 40, 79, 44): 'task', (79, 46, 79, 59): 'states.REBOOT'}, {}), '(task, states.REBOOT)', True, 'from ironic.conductor import utils as manager_utils\n'), ((125, 29, 127, 58), 'ironic.common.raid.filter_target_raid_config', 'raid.filter_target_raid_config', (), '', False, 'from ironic.common import raid\n'), ((133, 21, 133, 52), 'ironic.drivers.modules.ilo.common.get_ilo_object', 'ilo_common.get_ilo_object', ({(133, 47, 133, 51): 'node'}, {}), '(node)', True, 'from ironic.drivers.modules.ilo import common as ilo_common\n'), ((190, 21, 190, 52), 'ironic.drivers.modules.ilo.common.get_ilo_object', 'ilo_common.get_ilo_object', ({(190, 47, 190, 51): 'node'}, {}), '(node)', True, 'from ironic.drivers.modules.ilo import common as ilo_common\n'), ((147, 20, 147, 58), 'ironic.common.raid.update_raid_info', 'raid.update_raid_info', ({(147, 42, 147, 46): 'node', (147, 48, 147, 57): 'raid_conf'}, {}), '(node, raid_conf)', False, 'from ironic.common import raid\n'), ((163, 26, 166, 56), 'ironic.common.exception.NodeCleaningFailure', 'exception.NodeCleaningFailure', ({(164, 24, 166, 55): "('Clean step create_configuration failed on node %(node)s with error: %(err)s'\n     % {'node': node.uuid, 'err': msg})"}, {}), "(\n    'Clean step create_configuration failed on node %(node)s with error: %(err)s'\n     % {'node': node.uuid, 'err': msg})", False, 'from ironic.common import exception\n'), ((168, 25, 168, 76), 'ironic.common.i18n._', '_', ({(168, 27, 168, 75): '"""Failed to create raid configuration on node %s"""'}, {}), "('Failed to create raid configuration on node %s')", False, 'from ironic.common.i18n import _\n'), ((219, 26, 222, 56), 'ironic.common.exception.NodeCleaningFailure', 'exception.NodeCleaningFailure', ({(220, 24, 222, 55): "('Clean step delete_configuration failed on node %(node)s with error: %(err)s'\n     % {'node': node.uuid, 'err': msg})"}, {}), "(\n    'Clean step delete_configuration failed on node %(node)s with error: %(err)s'\n     % {'node': node.uuid, 'err': msg})", False, 'from ironic.common import exception\n'), ((227, 25, 227, 76), 'ironic.common.i18n._', '_', ({(227, 27, 227, 75): '"""Failed to delete raid configuration on node %s"""'}, {}), "('Failed to delete raid configuration on node %s')", False, 'from ironic.common.i18n import _\n')]