# Copyright (c) 2012 NTT DOCOMO, INC.
# Copyright (c) 2011-2014 OpenStack Foundation
# 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.

"""
Ironic host manager.

This host manager will consume all cpu's, disk space, and
ram from a host / node as it is supporting Baremetal hosts, which can not be
subdivided into multiple instances.
"""
from oslo_log import log as logging
from oslo_log import versionutils

import nova.conf
from nova import context as context_module
from nova import objects
from nova.objects import fields as obj_fields
from nova.scheduler import host_manager

CONF = nova.conf.CONF

LOG = logging.getLogger(__name__)


class IronicNodeState(host_manager.HostState):
    """Mutable and immutable information tracked for a host.
    This is an attempt to remove the ad-hoc data structures
    previously used and lock down access.
    """

    def _update_from_compute_node(self, compute):
        """Update information about a host from a ComputeNode object."""

        # NOTE(jichenjc): if the compute record is just created but not updated
        # some field such as free_disk_gb can be None
        if 'free_disk_gb' not in compute or compute.free_disk_gb is None:
            LOG.debug('Ignoring compute node %s as its usage has not been '
                      'updated yet.', compute.uuid)
            return

        self.vcpus_total = compute.vcpus
        self.vcpus_used = compute.vcpus_used

        self.free_ram_mb = compute.free_ram_mb
        self.total_usable_ram_mb = compute.memory_mb
        self.free_disk_mb = compute.free_disk_gb * 1024

        self.stats = compute.stats or {}
        self.uuid = compute.uuid

        self.total_usable_disk_gb = compute.local_gb
        self.hypervisor_type = compute.hypervisor_type
        self.hypervisor_version = compute.hypervisor_version
        self.hypervisor_hostname = compute.hypervisor_hostname
        self.cpu_info = compute.cpu_info
        if compute.supported_hv_specs:
            self.supported_instances = [spec.to_list() for spec
                                        in compute.supported_hv_specs]
        else:
            self.supported_instances = []

        # update allocation ratios given by the ComputeNode object
        self.cpu_allocation_ratio = compute.cpu_allocation_ratio
        self.ram_allocation_ratio = compute.ram_allocation_ratio
        self.disk_allocation_ratio = compute.disk_allocation_ratio

        self.updated = compute.updated_at

    def _locked_consume_from_request(self, spec_obj):
        """Consume nodes entire resources regardless of instance request."""
        self.free_ram_mb = 0
        self.free_disk_mb = 0
        self.vcpus_used = self.vcpus_total


class IronicHostManager(host_manager.HostManager):
    """Ironic HostManager class."""

    def __init__(self):
        super(IronicHostManager, self).__init__()
        msg = ('The IronicHostManager is deprecated and may be removed as '
               'early as the 18.0.0 Rocky release. Deployments need to '
               'schedule based on resource classes.')
        versionutils.report_deprecated_feature(LOG, msg)

    @staticmethod
    def _is_ironic_compute(compute):
        ht = compute.hypervisor_type if 'hypervisor_type' in compute else None
        return ht == obj_fields.HVType.IRONIC

    def _load_filters(self):
        if CONF.filter_scheduler.use_baremetal_filters:
            return CONF.filter_scheduler.baremetal_enabled_filters
        return super(IronicHostManager, self)._load_filters()

    def host_state_cls(self, host, node, cell, **kwargs):
        """Factory function/property to create a new HostState."""
        compute = kwargs.get('compute')
        if compute and self._is_ironic_compute(compute):
            return IronicNodeState(host, node, cell)
        else:
            return host_manager.HostState(host, node, cell)

    def _init_instance_info(self, computes_by_cell=None):
        """Ironic hosts should not pass instance info."""
        context = context_module.RequestContext()
        self._load_cells(context)
        if not computes_by_cell:
            computes_by_cell = {}
            for cell in self.cells:
                with context_module.target_cell(context, cell) as cctxt:
                    computes_by_cell[cell] = (
                        objects.ComputeNodeList.get_all(cctxt).objects)

        non_ironic_computes = {cell: [c for c in compute_nodes
                                      if not self._is_ironic_compute(c)]
                               for cell, compute_nodes in
                               computes_by_cell.items()}
        super(IronicHostManager, self)._init_instance_info(non_ironic_computes)

    def _get_instance_info(self, context, compute):
        """Ironic hosts should not pass instance info."""

        if compute and self._is_ironic_compute(compute):
            return {}
        else:
            return super(IronicHostManager, self)._get_instance_info(context,
                                                                     compute)
