#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: __init__.py 9466 2015-11-02 09:16:33Z Eric $
#
# Copyright (c) 2015 Nuwa Information Co., Ltd, All Rights Reserved.
#
# Licensed under the Proprietary License,
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at our web site.
#
# See the License for the specific language governing permissions and
# limitations under the License.
#
# $Author: Eric $ (last)
# $Date: 2015-11-02 17:16:33 +0800 (Mon, 02 Nov 2015) $
# $Revision: 9466 $

import logging
import urllib2
import socket
import httplib
import ssl

from lxml.etree import fromstring

from Iuppiter.Util import preferredSort
from Iuno.cloud import localCache

class BaseBackend(object):
    """
    Abstract backend implementation for all backends.
    """

    # Read-only monit authentication.
    MONIT_USER = 'monit'
    MONIT_PASSWORD = '25025529'

    def __init__(self, context=None, config=None):
        """
        Constructor.

        @param context Settings context.
        @param config Service settings.
        """
        super(BaseBackend, self).__init__()
        self.name = self.__class__.__module__.split('.')[-1]
        self.logger = logging.getLogger(self.__class__.__module__)

        self.context = context
        self.config = config

        self.preferredDomains = ()

    def _readCache(self, key):
        """
        Simple wrapper to easier read data from cache for backend.

        @param key Data key.
        @return Data.
        """
        return localCache.read(file='%s.dat' % self.name, key=key)

    def _writeCache(self, key, value, expire=None):
        """
        Simple wrapper to easier write data to cache for backend.

        @param key Data key.
        @param value Data.
        @param expire Expire milliseconds.
        @return True if succeeded.
        """
        return localCache.write(file='%s.dat' % self.name,
                                key=key, value=value, expire=expire)

    def _checkMonit(self, host, services):
        """
        Check whether services available or not by monit.

        @param host Host DNS name.
        @param services List of services to check.
        @return True if all services available.
        """
        return True
        cacheKey = 'monit%s' % str(services)
        ok = self._readCache(cacheKey)
        if ok: # Not None and True.
            return ok

        # TODO: 1. It can be moved to Iuppiter, and refactor Iuppiter.Util to 
        #          Iuppiter.utils package to separate these networking utils.
        url = 'https://%s:2812/_status?format=xml' % host
        m = urllib2.HTTPPasswordMgrWithDefaultRealm()
        m.add_password(None, url, self.MONIT_USER, self.MONIT_PASSWORD)
        h = urllib2.HTTPBasicAuthHandler(m)
        opener = urllib2.build_opener(h)
        urllib2.install_opener(opener)

        try:
            # ssl verify change in python 2.7
            # "SL: CERTIFICATE_VERIFY_FAILED" Error solution:
            # http://stackoverflow.com/questions/
            # 27835619/ssl-certificate-verify-failed-error
            self.logger.info('Checking Monit %s: %s' % (services, host))
            context = ssl._create_unverified_context()
            d = fromstring(urllib2.urlopen(url, context=context).read())
            cond = ' or '.join(['text() = "%s"' % v for v in services])
            names = d.xpath('//service/name[%s]' % cond)
            for node in names:
                next = node.getnext()
                if next.tag != 'status' or next.text != '0':
                    self.logger.info('Monit service %s unavailable.' %
                                     node.text)
                    return False
        except Exception, ex:
            # Connection or other failure.
            self.logger.warn('Check moint service failed: %s' % str(ex))
            return False

        self.logger.info('Monit %s: %s OK' % (services, host))
        self._writeCache(cacheKey, True, expire=3000) # Expire after 3 seconds.

        return True

    def _checkPorts(self, host, ports, timeout=30):
        """
        Check whether ports available or not.

        @param host Host DNS name.
        @param ports List of ports to check.
        @return True if all ports available.
        """
        for port in ports:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            try:
                sock.connect((host, port))
            except Exception, e:
                self.logger.info('%s:%d unavailable.' % (host, port))
                return False
            finally:
                sock.close()

        return True

    def _checkHttp(self, host, port=80, path='/', secure=False):
        """
        Check whether HTTP server available or not.

        @param host Host DNS name.
        @param port HTTP port.
        @param path Check URL path.
        @param secure HTTPS or not.
        @return True if host available.
        """
        try:
            if secure:
                conn = httplib.HTTPSConnection(host, 
                                   context=ssl._create_unverified_context())
            else:
                conn = httplib.HTTPConnection(host)
            conn.request("HEAD", path)
            response = conn.getresponse()
            if response.status in (200, 302):
                return True
        except Exception, e:
            self.logger.info('%s unavailable (HTTP%s).' % 
                             (host, "S" if secure else ""))
            return False

        return False

    def _checkStatus(self, host, services):
        """
        Check whether services available or not.

        @param host Host DNS name.
        @param services List of services to check.
        @return True if all services available.
        """
        return self._checkMonit(host, services)

    def _adjustPreferred(self, preferred):
        """
        Adjust passed preferred argument to final workable preferred.

        @param preferred Raw preferred.
        @return Adjusted preferred.
        """
        if hasattr(self, 'DEFAULT_PREFERRED_DOMAINS'):
            preferredDomains = self.DEFAULT_PREFERRED_DOMAINS
        else:
            preferredDomains = self.preferredDomains

        if preferred:
            values = list(preferredDomains)
            if len(values) < len(preferred):
                values, preferred = preferred, values

            preferred = preferredSort(values, preferred, inPlace=False)
        else:
            preferred = list(preferredDomains)

        return preferred

    def getDomains(self, preferred=None):
        """
        Get all public available machine's DNS name.

        @param preferred Preferred machines.
        @return List of DNS name of machines.
        """
        preferred = self._adjustPreferred(preferred)
        self.logger.info('INSTANCES: %s' % str(preferred))
        return preferred

    def attachSettings(self, settingsLocals):
        """
        Attach settings for backend.

        @param settingsLocals locals() in settings.py.
        """
        pass # Do nothing.
