# Copyright (c) 2011-2012 OpenStack, LLC.
# 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.
"""
Least Cost is an algorithm for choosing which host machines to
provision a set of resources to. The input is a WeightedHost object which
is decided upon by a set of objective-functions, called the 'cost-functions'.
The WeightedHost contains a combined weight for each cost-function.

The cost-function and weights are tabulated, and the host with the least cost
is then selected for provisioning.

NOTE(comstud): This is deprecated. One should use the RAMWeigher and/or
create other weight modules.
"""

from nova import exception
from nova.openstack.common import cfg
from nova.openstack.common import importutils
from nova.openstack.common import log as logging


LOG = logging.getLogger(__name__)

least_cost_opts = [
    cfg.ListOpt('least_cost_functions',
                default=None,
                help='Which cost functions the LeastCostScheduler should use'),
    cfg.FloatOpt('noop_cost_fn_weight',
                 default=1.0,
                 help='How much weight to give the noop cost function'),
    cfg.FloatOpt('compute_fill_first_cost_fn_weight',
                 default=None,
                 help='How much weight to give the fill-first cost function. '
                      'A negative value will reverse behavior: '
                      'e.g. spread-first'),
    ]

CONF = cfg.CONF
CONF.register_opts(least_cost_opts)


def noop_cost_fn(host_state, weight_properties):
    """Return a pre-weight cost of 1 for each host"""
    return 1


def compute_fill_first_cost_fn(host_state, weight_properties):
    """Higher weights win, so we should return a lower weight
    when there's more free ram available.

    Note: the weight modifier for this function in default configuration
    is -1.0. With -1.0 this function runs in reverse, so systems
    with the most free memory will be preferred.
    """
    return -host_state.free_ram_mb


def _get_cost_functions():
    """Returns a list of tuples containing weights and cost functions to
    use for weighing hosts
    """
    cost_fns_conf = CONF.least_cost_functions
    if cost_fns_conf is None:
        # The old default.  This will get fixed up below.
        fn_str = 'nova.scheduler.least_cost.compute_fill_first_cost_fn'
        cost_fns_conf = [fn_str]
    cost_fns = []
    for cost_fn_str in cost_fns_conf:
        short_name = cost_fn_str.split('.')[-1]
        if not (short_name.startswith('compute_') or
                short_name.startswith('noop')):
            continue
        # Fix up any old paths to the new paths
        if cost_fn_str.startswith('nova.scheduler.least_cost.'):
            cost_fn_str = ('nova.scheduler.weights.least_cost' +
                       cost_fn_str[25:])
        try:
            # NOTE: import_class is somewhat misnamed since
            # the weighing function can be any non-class callable
            # (i.e., no 'self')
            cost_fn = importutils.import_class(cost_fn_str)
        except ImportError:
            raise exception.SchedulerCostFunctionNotFound(
                    cost_fn_str=cost_fn_str)

        try:
            flag_name = "%s_weight" % cost_fn.__name__
            weight = getattr(CONF, flag_name)
        except AttributeError:
            raise exception.SchedulerWeightFlagNotFound(
                    flag_name=flag_name)
        # Set the original default.
        if (flag_name == 'compute_fill_first_cost_fn_weight' and
                weight is None):
            weight = -1.0
        cost_fns.append((weight, cost_fn))
    return cost_fns


def get_least_cost_weighers():
    cost_functions = _get_cost_functions()

    # Unfortunately we need to import this late so we don't have an
    # import loop.
    from nova.scheduler import weights

    class _LeastCostWeigher(weights.BaseHostWeigher):
        def weigh_objects(self, weighted_hosts, weight_properties):
            for host in weighted_hosts:
                host.weight = sum(weight * fn(host.obj, weight_properties)
                            for weight, fn in cost_functions)

    return [_LeastCostWeigher]
