# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Author: <NAME> <<EMAIL>>
#
# 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.
#
# Copied: nova.notifications
import abc

from oslo_config import cfg
from oslo_log import log as logging
import six

from designate.plugin import DriverPlugin
from designate import objects
from designate import rpc

LOG = logging.getLogger(__name__)

notify_opts = [
    cfg.BoolOpt('notify_api_faults', default=False,
                help='Send notifications if there\'s a failure in the API.'),
    cfg.StrOpt('notification-plugin', default='default',
               help='The notification plugin to use'),
]

CONF = cfg.CONF
CONF.register_opts(notify_opts)
NOTIFICATION_PLUGIN = None


def send_api_fault(context, url, status, exception):
    """Send an api.fault notification."""

    if not CONF.notify_api_faults:
        return

    payload = {'url': url, 'exception': str(exception), 'status': status}

    rpc.get_notifier('api').error(context, 'dns.api.fault', payload)


def init_notification_plugin():
    LOG.debug("Loading notification plugin: %s", cfg.CONF.notification_plugin)
    cls = NotificationPlugin.get_driver(cfg.CONF.notification_plugin)

    global NOTIFICATION_PLUGIN
    NOTIFICATION_PLUGIN = cls()


def get_plugin():
    if NOTIFICATION_PLUGIN is None:
        init_notification_plugin()
    return NOTIFICATION_PLUGIN


class NotificationPlugin(DriverPlugin):
    """Base class for Notification Driver implementations"""
    __plugin_type__ = 'notification'
    __plugin_ns__ = 'designate.notification.plugin'

    def __init__(self):
        super(NotificationPlugin, self).__init__()

    @abc.abstractmethod
    def emit(self, notification_type, context, result, *args, **kwargs):
        """Return a payload to emit as part of the notification"""


class Default(NotificationPlugin):
    """Returns the result, as implemented in the base class"""
    __plugin_name__ = 'default'

    def emit(self, notification_type, context, result, *args, **kwargs):
        """Return the result of the function called"""
        return [result]


class Audit(NotificationPlugin):
    """Grabs Zone/Recordset names and RRData changes"""
    __plugin_name__ = 'audit'

    def zone_name(self, arglist, result):
        for arg in arglist + [result]:
            if isinstance(arg, objects.Zone):
                if arg.name is not None:
                    return arg.name
            if hasattr(arg, 'zone_name'):
                if arg.zone_name is not None:
                    return arg.zone_name

        return None

    def zone_id(self, arglist, result):
        for arg in arglist + [result]:
            if isinstance(arg, objects.Zone):
                if arg.id is not None:
                    return arg.id
            if hasattr(arg, 'zone_id'):
                if arg.zone_id is not None:
                    return arg.zone_id

        return None

    def recordset_name(self, arglist, result):
        for arg in arglist + [result]:
            if isinstance(arg, objects.RecordSet):
                if arg.name is not None:
                    return arg.name

        return None

    def recordset_data(self, arglist, result):
        if not isinstance(result, objects.RecordSet):
            return []

        for arg in arglist:
            if isinstance(arg, objects.RecordSet):
                if 'records' not in arg.obj_what_changed():
                    return []
                original_rrs = arg.obj_get_original_value('records')

                old_value = ' '.join(
                    [obj['data'] for obj in original_rrs])

                new_value = ' '.join(
                    [rr.data for rr in result.records])

                if old_value == new_value:
                    return []

                return [{
                    'change': 'records',
                    'old_value': old_value,
                    'new_value': new_value,
                }]

        return []

    def other_data(self, arglist, result):
        changes = []

        for arg in arglist:
            if isinstance(arg, objects.DesignateObject):
                for change in arg.obj_what_changed():
                    if change != 'records':
                        old_value = arg.obj_get_original_value(change)
                        new_value = getattr(arg, change)

                        # Just in case something odd makes it here
                        if any(not isinstance(val,
                                              (int, float, bool,
                                               six.string_types, type(None)))
                               for val in (old_value, new_value)):
                            LOG.warning("Nulling notification values after "
                                        "unexpected values (%s, %s)",
                                        old_value, new_value)
                            old_value, new_value = None, None

                        if old_value == new_value:
                            continue

                        changes.append({
                            'change': change,
                            'old_value': str(old_value),
                            'new_value': str(new_value),
                        })

        return changes

    def blank_event(self):
        return [{
            'change': None,
            'old_value': None,
            'new_value': None,
        }]

    def gather_changes(self, arglist, result, notification_type):
        changes = []

        if 'update' in notification_type:
            changes.extend(self.other_data(arglist, result))
            if notification_type == 'dns.recordset.update':
                changes.extend(self.recordset_data(arglist, result))
        elif 'create' in notification_type:
            if notification_type == 'dns.recordset.create':
                changes.extend(self.recordset_data(arglist, result))
            else:
                changes.extend(self.blank_event())
        else:
            changes.extend(self.blank_event())

        return changes

    def emit(self, notification_type, context, result, *args, **kwargs):
        arglist = []
        for item in args:
            if isinstance(item, tuple) or isinstance(item, list):
                arglist.extend(item)
            if isinstance(item, dict):
                arglist.extend(list(item.values()))

        payloads = []
        for change in self.gather_changes(arglist, result, notification_type):
            payloads.append({
                'zone_name': self.zone_name(arglist, result),
                'zone_id': self.zone_id(arglist, result),
                'recordset_name': self.recordset_name(arglist, result),
                'old_data': change['old_value'],
                'new_data': change['new_value'],
                'changed_field': change['change'],
            })

        return payloads

# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Author: <NAME> <<EMAIL>>
#
# 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.
#
# Copied: nova.notifications
import abc

from oslo_config import cfg
from oslo_log import log as logging
import six

from designate.plugin import DriverPlugin
from designate import objects
from designate import rpc

LOG = logging.getLogger(__name__)

notify_opts = [
    cfg.BoolOpt('notify_api_faults', default=False,
                help='Send notifications if there\'s a failure in the API.'),
    cfg.StrOpt('notification-plugin', default='default',
               help='The notification plugin to use'),
]

CONF = cfg.CONF
CONF.register_opts(notify_opts)
NOTIFICATION_PLUGIN = None


def send_api_fault(context, url, status, exception):
    """Send an api.fault notification."""

    if not CONF.notify_api_faults:
        return

    payload = {'url': url, 'exception': str(exception), 'status': status}

    rpc.get_notifier('api').error(context, 'dns.api.fault', payload)


def init_notification_plugin():
    LOG.debug("Loading notification plugin: %s", cfg.CONF.notification_plugin)
    cls = NotificationPlugin.get_driver(cfg.CONF.notification_plugin)

    global NOTIFICATION_PLUGIN
    NOTIFICATION_PLUGIN = cls()


def get_plugin():
    if NOTIFICATION_PLUGIN is None:
        init_notification_plugin()
    return NOTIFICATION_PLUGIN


class NotificationPlugin(DriverPlugin):
    """Base class for Notification Driver implementations"""
    __plugin_type__ = 'notification'
    __plugin_ns__ = 'designate.notification.plugin'

    def __init__(self):
        super(NotificationPlugin, self).__init__()

    @abc.abstractmethod
    def emit(self, notification_type, context, result, *args, **kwargs):
        """Return a payload to emit as part of the notification"""


class Default(NotificationPlugin):
    """Returns the result, as implemented in the base class"""
    __plugin_name__ = 'default'

    def emit(self, notification_type, context, result, *args, **kwargs):
        """Return the result of the function called"""
        return [result]


class Audit(NotificationPlugin):
    """Grabs Zone/Recordset names and RRData changes"""
    __plugin_name__ = 'audit'

    def zone_name(self, arglist, result):
        for arg in arglist + [result]:
            if isinstance(arg, objects.Zone):
                if arg.name is not None:
                    return arg.name
            if hasattr(arg, 'zone_name'):
                if arg.zone_name is not None:
                    return arg.zone_name

        return None

    def zone_id(self, arglist, result):
        for arg in arglist + [result]:
            if isinstance(arg, objects.Zone):
                if arg.id is not None:
                    return arg.id
            if hasattr(arg, 'zone_id'):
                if arg.zone_id is not None:
                    return arg.zone_id

        return None

    def recordset_name(self, arglist, result):
        for arg in arglist + [result]:
            if isinstance(arg, objects.RecordSet):
                if arg.name is not None:
                    return arg.name

        return None

    def recordset_data(self, arglist, result):
        if not isinstance(result, objects.RecordSet):
            return []

        for arg in arglist:
            if isinstance(arg, objects.RecordSet):
                if 'records' not in arg.obj_what_changed():
                    return []
                original_rrs = arg.obj_get_original_value('records')

                old_value = ' '.join(
                    [obj['data'] for obj in original_rrs])

                new_value = ' '.join(
                    [rr.data for rr in result.records])

                if old_value == new_value:
                    return []

                return [{
                    'change': 'records',
                    'old_value': old_value,
                    'new_value': new_value,
                }]

        return []

    def other_data(self, arglist, result):
        changes = []

        for arg in arglist:
            if isinstance(arg, objects.DesignateObject):
                for change in arg.obj_what_changed():
                    if change != 'records':
                        old_value = arg.obj_get_original_value(change)
                        new_value = getattr(arg, change)

                        # Just in case something odd makes it here
                        if any(not isinstance(val,
                                              (int, float, bool,
                                               six.string_types, type(None)))
                               for val in (old_value, new_value)):
                            LOG.warning("Nulling notification values after "
                                        "unexpected values (%s, %s)",
                                        old_value, new_value)
                            old_value, new_value = None, None

                        if old_value == new_value:
                            continue

                        changes.append({
                            'change': change,
                            'old_value': str(old_value),
                            'new_value': str(new_value),
                        })

        return changes

    def blank_event(self):
        return [{
            'change': None,
            'old_value': None,
            'new_value': None,
        }]

    def gather_changes(self, arglist, result, notification_type):
        changes = []

        if 'update' in notification_type:
            changes.extend(self.other_data(arglist, result))
            if notification_type == 'dns.recordset.update':
                changes.extend(self.recordset_data(arglist, result))
        elif 'create' in notification_type:
            if notification_type == 'dns.recordset.create':
                changes.extend(self.recordset_data(arglist, result))
            else:
                changes.extend(self.blank_event())
        else:
            changes.extend(self.blank_event())

        return changes

    def emit(self, notification_type, context, result, *args, **kwargs):
        arglist = []
        for item in args:
            if isinstance(item, tuple) or isinstance(item, list):
                arglist.extend(item)
            if isinstance(item, dict):
                arglist.extend(list(item.values()))

        payloads = []
        for change in self.gather_changes(arglist, result, notification_type):
            payloads.append({
                'zone_name': self.zone_name(arglist, result),
                'zone_id': self.zone_id(arglist, result),
                'recordset_name': self.recordset_name(arglist, result),
                'old_data': change['old_value'],
                'new_data': change['new_value'],
                'changed_field': change['change'],
            })

        return payloads

# Copyright 2013 Hewlett-Packard Development Company, L.P. # # Author: <NAME> <<EMAIL>> # # 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. # # Copied: nova.notifications Send an api.fault notification. Base class for Notification Driver implementations Return a payload to emit as part of the notification Returns the result, as implemented in the base class Return the result of the function called Grabs Zone/Recordset names and RRData changes # Just in case something odd makes it here