# 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.

import os
import time

from oslo_log import log
import threading

from ironic.common.i18n import _
from ironic.common import metrics_utils
from ironic.common import states
from ironic.conf import CONF
from ironic.db import api as dbapi
from ironic.pxe_filter import dnsmasq

LOG = log.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)

_START_DELAY = 1.0


class PXEFilterManager:
    topic = 'ironic.pxe_filter'

    def __init__(self, host):
        self.host = host or CONF.host
        self._started = False

    def prepare_host(self):
        if not CONF.pxe_filter.dhcp_hostsdir:
            raise RuntimeError(_('The [pxe_filter]dhcp_hostsdir option '
                                 'is required'))
        if not os.path.isdir(CONF.pxe_filter.dhcp_hostsdir):
            # FIXME(dtantsur): should we try to create it? The permissions will
            # most likely be wrong.
            raise RuntimeError(_('The path in [pxe_filter]dhcp_hostsdir '
                                 'does not exist'))

    def init_host(self, admin_context):
        if self._started:
            raise RuntimeError(_('Attempt to start an already running '
                                 'PXE filter manager'))

        self._shutdown = threading.Event()

        # Delay startup by ``_START_DELAY`` seconds to mimic the former
        # ``eventlet.spawn_after`` behaviour.
        def _delayed_start():
            time.sleep(_START_DELAY)
            self._periodic_sync()

        self._thread = threading.Thread(
            target=_delayed_start, name="pxe-sync",
            daemon=True)

        self._thread.start()
        self._started = True

    def del_host(self):
        self._shutdown.set()
        time.sleep(0)
        self._thread.join()
        self._started = False

    def _periodic_sync(self):
        db = dbapi.get_instance()
        self._try_sync(db)
        while not self._shutdown.wait(timeout=CONF.pxe_filter.sync_period):
            self._try_sync(db)

    def _try_sync(self, db):
        try:
            return self._sync(db)
        except Exception:
            LOG.exception('Sync failed, will retry')

    @METRICS.timer('PXEFilterManager._sync')
    def _sync(self, db):
        LOG.debug('Starting periodic sync of the filter')
        ts = time.time()

        nodeinfo_list = db.get_nodeinfo_list(
            columns=['id', 'inspect_interface'],
            filters={
                'provision_state_in': [states.INSPECTWAIT, states.INSPECTING],
            })
        nodes_on_inspection = {
            node[0] for node in nodeinfo_list
            if node[1] in CONF.pxe_filter.supported_inspect_interfaces
        }
        all_ports = db.get_port_list()
        LOG.debug("Found %d nodes on inspection, handling %d ports",
                  len(nodes_on_inspection), len(all_ports))

        allow = [port.address for port in all_ports
                 if port.node_id in nodes_on_inspection]
        deny = [port.address for port in all_ports
                if port.node_id not in nodes_on_inspection]
        allow_unknown = (CONF.auto_discovery.enabled
                         or bool(nodes_on_inspection))

        dnsmasq.sync(allow, deny, allow_unknown)
        LOG.info('Finished periodic sync of the filter, took %.2f seconds',
                 time.time() - ts)
