# Copyright (c) 2015 Mirantis, Inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
Module with hook interface for actions performed by share driver.

All available hooks are placed in manila/share/hooks dir.

Hooks are used by share services and can serve several use cases such as
any kind of notification and performing additional backend-specific actions.
"""

import abc

from oslo_config import cfg
from oslo_log import log
import six

from manila import context as ctxt


hook_options = [
    cfg.BoolOpt(
        "enable_pre_hooks",
        default=False,
        help="Whether to enable pre hooks or not."),
    cfg.BoolOpt(
        "enable_post_hooks",
        default=False,
        help="Whether to enable post hooks or not."),
    cfg.BoolOpt(
        "enable_periodic_hooks",
        default=False,
        help="Whether to enable periodic hooks or not."),
    cfg.BoolOpt(
        "suppress_pre_hooks_errors",
        default=False,
        help="Whether to suppress pre hook errors (allow driver perform "
             "actions) or not."),
    cfg.BoolOpt(
        "suppress_post_hooks_errors",
        default=False,
        help="Whether to suppress post hook errors (allow driver's results "
             "to pass through) or not."),
    cfg.FloatOpt(
        "periodic_hooks_interval",
        default=300.0,
        help="Interval in seconds between execution of periodic hooks. "
             "Used when option 'enable_periodic_hooks' is set to True. "
             "Default is 300."),
]

CONF = cfg.CONF
CONF.register_opts(hook_options)
LOG = log.getLogger(__name__)


@six.add_metaclass(abc.ABCMeta)
class HookBase(object):

    def get_config_option(self, key):
        if self.configuration:
            return self.configuration.safe_get(key)
        return CONF.get(key)

    def __init__(self, configuration, host):
        self.host = host
        self.configuration = configuration
        if self.configuration:
            self.configuration.append_config_values(hook_options)

        self.pre_hooks_enabled = self.get_config_option("enable_pre_hooks")
        self.post_hooks_enabled = self.get_config_option("enable_post_hooks")
        self.periodic_hooks_enabled = self.get_config_option(
            "enable_periodic_hooks")
        self.suppress_pre_hooks_errors = self.get_config_option(
            "suppress_pre_hooks_errors")
        self.suppress_post_hooks_errors = self.get_config_option(
            "suppress_post_hooks_errors")

    def execute_pre_hook(self, context=None, func_name=None, *args, **kwargs):
        """Hook called before driver's action."""
        if not self.pre_hooks_enabled:
            return
        LOG.debug("Running 'pre hook'.")
        context = context or ctxt.get_admin_context()
        try:
            pre_data = self._execute_pre_hook(
                context=context,
                func_name=func_name,
                *args, **kwargs)
        except Exception as e:
            if self.suppress_pre_hooks_errors:
                LOG.warning("\nSuppressed exception in pre hook. %s\n", e)
                pre_data = e
            else:
                raise
        return pre_data

    def execute_post_hook(self, context=None, func_name=None,
                          pre_hook_data=None, driver_action_results=None,
                          *args, **kwargs):
        """Hook called after driver's action."""
        if not self.post_hooks_enabled:
            return
        LOG.debug("Running 'post hook'.")
        context = context or ctxt.get_admin_context()

        try:
            post_data = self._execute_post_hook(
                context=context,
                func_name=func_name,
                pre_hook_data=pre_hook_data,
                driver_action_results=driver_action_results,
                *args, **kwargs)
        except Exception as e:
            if self.suppress_post_hooks_errors:
                LOG.warning(
                    "\nSuppressed exception in post hook. %s\n", e)
                post_data = e
            else:
                raise
        return post_data

    def execute_periodic_hook(self, context, periodic_hook_data,
                              *args, **kwargs):
        """Hook called on periodic basis."""
        if not self.periodic_hooks_enabled:
            return
        LOG.debug("Running 'periodic hook'.")
        context = context or ctxt.get_admin_context()
        return self._execute_periodic_hook(
            context, periodic_hook_data, *args, **kwargs)

    @abc.abstractmethod
    def _execute_pre_hook(self, context, func_name, *args, **kwargs):
        """Redefine this method for pre hook action."""

    @abc.abstractmethod
    def _execute_post_hook(self, context, func_name, pre_hook_data,
                           driver_action_results, *args, **kwargs):
        """Redefine this method for post hook action."""

    @abc.abstractmethod
    def _execute_periodic_hook(self, context, periodic_hook_data,
                               *args, **kwargs):
        """Redefine this method for periodic hook action."""
