"""Misc cgroup utility functions."""

from __future__ import absolute_import

import errno
import signal
import os

import glob
import logging

from . import cgroups
from . import sysinfo

from .exc import TreadmillError
from .syscall import eventfd


_LOGGER = logging.getLogger(__name__)
NANOSECS_PER_SEC = 10**9


class TreadmillCgroupError(TreadmillError):
    """Treadmill Cgroup operation error."""
    pass


def set_memory_hardlimit(cgrp, limit):
    """Set the cgroup hard-limits to the desired value.

    The logic here is complicated since the ordering of operations depends on
    weither we are lowering or raising the value.
    """
    def _lower_memory_hardlimit(cgrp, limit):
        """Lower the cgroup memory hardlimit."""
        cgroups.set_value('memory', cgrp,
                          'memory.limit_in_bytes', limit)
        cgroups.set_value('memory', cgrp,
                          'memory.memsw.limit_in_bytes', limit)

    def _raise_memory_hardlimit(cgrp, limit):
        """Raise the cgroup memory hardlimit."""
        cgroups.set_value('memory', cgrp,
                          'memory.memsw.limit_in_bytes', limit)
        cgroups.set_value('memory', cgrp,
                          'memory.limit_in_bytes', limit)

    memory_hardlimit_funs = [_lower_memory_hardlimit, _raise_memory_hardlimit]
    while memory_hardlimit_funs:
        try:
            memory_hardlimit_fun = memory_hardlimit_funs.pop(0)
            memory_hardlimit_fun(cgrp, limit)
            break

        except IOError as err:
            if err.errno == errno.EBUSY:
                # Resource busy, this means the cgroup is already using more
                # then the limit we are trying to set.
                raise TreadmillCgroupError('Unable to set hard limit to %d. '
                                           'Cgroup %r memory over limit.' %
                                           (limit, cgrp))

            elif err.errno == errno.EINVAL:
                # This means we did the hard limit operation in the wrong
                # order. If we have a different ordering to try, go ahead.
                if memory_hardlimit_funs:
                    continue

            # For any other case, raise the exception
            raise


def create_treadmill_cgroups(system_cpu_shares,
                             treadmill_cpu_shares,
                             treadmill_core_cpu_shares,
                             treadmill_apps_cpu_shares,
                             treadmill_mem,
                             treadmill_core_mem):
    """This is the core cgroup setup. Should be applied to a cleaned env."""

    cgroups.create('cpu', 'system')
    cgroups.create('cpu', 'treadmill')
    cgroups.create('cpu', 'treadmill/core')
    cgroups.create('cpu', 'treadmill/apps')

    cgroups.set_value('cpu', 'treadmill',
                      'cpu.shares', treadmill_cpu_shares)
    cgroups.set_value('cpu', 'system',
                      'cpu.shares', system_cpu_shares)
    cgroups.set_value('cpu', 'treadmill/core',
                      'cpu.shares', treadmill_core_cpu_shares)
    cgroups.set_value('cpu', 'treadmill/apps',
                      'cpu.shares', treadmill_apps_cpu_shares)

    cgroups.create('cpuacct', 'system')
    cgroups.create('cpuacct', 'treadmill')
    cgroups.create('cpuacct', 'treadmill/core')
    cgroups.create('cpuacct', 'treadmill/apps')

    cgroups.create('memory', 'system')
    cgroups.create('memory', 'treadmill')
    if cgroups.get_value('memory', 'treadmill',
                         'memory.use_hierarchy').strip() != '1':
        cgroups.set_value('memory', 'treadmill', 'memory.use_hierarchy', '1')
    set_memory_hardlimit('treadmill', treadmill_mem)

    oom_value = 'oom_kill_disable 0\nunder_oom 0\n'
    if cgroups.get_value('memory', 'treadmill',
                         'memory.oom_control') != oom_value:
        cgroups.set_value('memory', 'treadmill',
                          'memory.oom_control', '0')

    cgroups.create('memory', 'treadmill/core')
    cgroups.create('memory', 'treadmill/apps')

    set_memory_hardlimit('treadmill/core', treadmill_core_mem)
    cgroups.set_value('memory', 'treadmill/core',
                      'memory.soft_limit_in_bytes', treadmill_core_mem)