
from __future__ import absolute_import
from __future__ import division

import libvirt

from vdsm.config import config
from vdsm.virt.vmdevices import lookup
from vdsm.virt.vmdevices import storage


class ImprobableResizeRequestError(RuntimeError):
    pass


class DriveMonitor(object):

    def __init__(self, vm, log, enabled=True):
        self._vm = vm
        self._log = log
        self._enabled = enabled
        self._events_enabled = config.getboolean(
            'irs', 'enable_block_threshold_event')

    def events_enabled(self):
        return self._events_enabled

    def enabled(self):
        return self._enabled

    def enable(self):
        self._enabled = True
        self._log.info('Enabling drive monitoring')

    def disable(self):
        self._enabled = False
        self._log.info('Disabling drive monitoring')

    def monitoring_needed(self):
        return self._enabled and bool(self.monitored_drives())

    def set_threshold(self, drive, apparentsize):
        if not self._events_enabled:
            return

        threshold = max(1, apparentsize - drive.watermarkLimit)

        self._log.info(
            'setting block threshold to %d bytes for drive %r '
            '(apparentsize %d)',
            threshold, drive.name, apparentsize
        )
        try:
            self._vm._dom.setBlockThreshold(drive.name, threshold)
        except libvirt.libvirtError as exc:
            drive.threshold_state = storage.BLOCK_THRESHOLD.UNSET
            self._log.error(
                'Failed to set block threshold on %r (%s): %s',
                drive.name, drive.path, exc)
        else:
            drive.threshold_state = storage.BLOCK_THRESHOLD.SET

    def clear_threshold(self, drive, index=None):
        if not self._events_enabled:
            return

        if index is None:
            target = drive.name
        else:
            target = '%s[%d]' % (drive.name, index)
        self._log.info('clearing block threshold for drive %r', target)
        self._vm._dom.setBlockThreshold(target, 0)

    def on_block_threshold(self, dev, path, threshold, excess):
        self._log.info('block threshold %d exceeded on %r (%s)',
                       threshold, dev, path)
        try:
            drive = lookup.drive_by_name(self._vm.getDiskDevices()[:], dev)
        except LookupError:
            self._log.warning(
                'Unknown drive %r for vm %s - ignored block threshold event',
                dev, self._vm.id)
        else:
            drive.on_block_threshold(path)

    def monitored_drives(self):
        return [drive for drive in self._vm.getDiskDevices()
                if drive.needs_monitoring(self._events_enabled)]

    def should_extend_volume(self, drive, volumeID, capacity, alloc, physical):
        nextPhysSize = drive.getNextVolumeSize(physical, capacity)

        if alloc > nextPhysSize:
            msg = ("Improbable extension request for volume %s on domain "
                   "%s, pausing the VM to avoid corruptions (capacity: %s, "
                   "allocated: %s, physical: %s, next physical size: %s)" %
                   (volumeID, drive.domainID, capacity, alloc, physical,
                    nextPhysSize))
            self._log.error(msg)
            self._vm.pause(pauseCode='EOTHER')
            raise ImprobableResizeRequestError(msg)

        if physical >= drive.getMaxVolumeSize(capacity):
            return False

        if physical - alloc < drive.watermarkLimit:
            return True
        return False

    def update_threshold_state_exceeded(self, drive):
        if (drive.threshold_state != storage.BLOCK_THRESHOLD.EXCEEDED and
                self.events_enabled()):
            drive.threshold_state = storage.BLOCK_THRESHOLD.EXCEEDED
            self._log.info(
                "Drive %s needs to be extended, forced threshold_state "
                "to exceeded", drive.name)
