# 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 unittest
import logging
import subprocess
from tempfile import mkdtemp
import time
import socket

import pymongo

from tests.common import load_check
from nose.plugins.skip import SkipTest

PORT1 = 37017
PORT2 = 37018
MAX_WAIT = 150


class TestMongo(unittest.TestCase):

    def wait4mongo(self, process, port):
        # Somehow process.communicate() hangs
        out = process.stdout
        loop = 0
        while True:
            l = out.readline()
            if l.find("[initandlisten] waiting for connections on port") > -1:
                break
            else:
                time.sleep(0.1)
                loop += 1
                if loop >= MAX_WAIT:
                    break

    def setUp(self):
        self.agent_config = {
            'version': '0.1',
            'api_key': 'toto'
        }

        # Initialize the check from checks_d
        self.check = load_check('mongo', {'init_config': {}, 'instances': {}}, self.agent_config)

        # Start 2 instances of Mongo in a replica set
        dir1 = mkdtemp()
        dir2 = mkdtemp()
        try:
            self.p1 = subprocess.Popen(["mongod",
                                        "--dbpath",
                                        dir1,
                                        "--port",
                                        str(PORT1),
                                        "--replSet",
                                        "testset/%s:%d" % (socket.gethostname(),
                                                           PORT2),
                                        "--rest"],
                                       executable="mongod",
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # Sleep until mongo comes online
            self.wait4mongo(self.p1, PORT1)
            if self.p1:
                # Set up replication
                c1 = pymongo.Connection('localhost:%s' % PORT1, slave_okay=True)
                self.p2 = subprocess.Popen(["mongod",
                                            "--dbpath",
                                            dir2,
                                            "--port",
                                            str(PORT2),
                                            "--replSet",
                                            "testset/%s:%d" % (socket.gethostname(),
                                                               PORT1),
                                            "--rest"],
                                           executable="mongod",
                                           stdout=subprocess.PIPE,
                                           stderr=subprocess.PIPE)
                self.wait4mongo(self.p2, PORT2)
                # Waiting before all members are online
                time.sleep(15)
                c1.admin.command("replSetInitiate")
                # Sleep for 15s until replication is stable
                time.sleep(30)
                assert pymongo.Connection('localhost:%s' % PORT2)
        except Exception:
            logging.getLogger().exception("Cannot instantiate mongod properly")

    def tearDown(self):
        try:
            if "p1" in dir(self):
                self.p1.terminate()
            if "p2" in dir(self):
                self.p2.terminate()
        except Exception:
            logging.getLogger().exception("Cannot terminate mongod instances")

    def testMongoCheck(self):
        raise SkipTest('Requires MongoDB')
        self.config = {
            'instances': [{
                'server': "mongodb://localhost:%s/test" % PORT1
            },
                {
                'server': "mongodb://localhost:%s/test" % PORT2
            }]
        }

        # Test mongodb with checks_d
        self.check = load_check('mongo', self.config, self.agent_config)

        # Run the check against our running server
        self.check.check(self.config['instances'][0])
        # Sleep for 1 second so the rate interval >=1
        time.sleep(1)
        # Run the check again so we get the rates
        self.check.check(self.config['instances'][0])

        # Metric assertions
        metrics = self.check.get_metrics()
        assert metrics
        self.assertIsInstance(metrics, list)
        self.assertTrue(len(metrics) > 0)

        metric_val_checks = {
            'mongodb.connections.current': lambda x: x >= 1,
            'mongodb.connections.available': lambda x: x >= 1,
            'mongodb.uptime': lambda x: x >= 0,
            'mongodb.mem.resident': lambda x: x > 0,
            'mongodb.mem.virtual': lambda x: x > 0
        }

        for m in metrics:
            metric_name = m[0]
            if metric_name in metric_val_checks:
                self.assertTrue(metric_val_checks[metric_name](m[2]))

        # Run the check against our running server
        self.check.check(self.config['instances'][1])
        # Sleep for 1 second so the rate interval >=1
        time.sleep(1)
        # Run the check again so we get the rates
        self.check.check(self.config['instances'][1])

        # Metric assertions
        metrics = self.check.get_metrics()
        assert metrics
        self.assertIsInstance(metrics, list)
        self.assertTrue(len(metrics) > 0)

        for m in metrics:
            metric_name = m[0]
            if metric_name in metric_val_checks:
                self.assertTrue(metric_val_checks[metric_name](m[2]))

    def testMongoOldConfig(self):
        raise SkipTest('Requires MongoDB')
        self.agent_config1 = {
            'mongodb_server': "mongodb://localhost:%s/test" % PORT1,
            'version': '0.1',
            'api_key': 'toto'
        }
        conf1 = self.check.parse_agent_config(self.agent_config1)
        self.agent_config2 = {
            'mongodb_server': "mongodb://localhost:%s/test" % PORT2,
            'version': '0.1',
            'api_key': 'toto'
        }
        conf2 = self.check.parse_agent_config(self.agent_config2)

        # Test the first mongodb instance
        self.check = load_check('mongo', conf1, self.agent_config1)

        # Run the check against our running server
        self.check.check(conf1['instances'][0])
        # Sleep for 1 second so the rate interval >=1
        time.sleep(1)
        # Run the check again so we get the rates
        self.check.check(conf1['instances'][0])

        # Metric assertions
        metrics = self.check.get_metrics()
        assert metrics
        self.assertIsInstance(metrics, list)
        self.assertTrue(len(metrics) > 0)

        metric_val_checks = {
            'mongodb.connections.current': lambda x: x >= 1,
            'mongodb.connections.available': lambda x: x >= 1,
            'mongodb.uptime': lambda x: x >= 0,
            'mongodb.mem.resident': lambda x: x > 0,
            'mongodb.mem.virtual': lambda x: x > 0
        }

        for m in metrics:
            metric_name = m[0]
            if metric_name in metric_val_checks:
                self.assertTrue(metric_val_checks[metric_name](m[2]))

        # Test the second mongodb instance
        self.check = load_check('mongo', conf2, self.agent_config2)

        # Run the check against our running server
        self.check.check(conf2['instances'][0])
        # Sleep for 1 second so the rate interval >=1
        time.sleep(1)
        # Run the check again so we get the rates
        self.check.check(conf2['instances'][0])

        # Metric assertions
        metrics = self.check.get_metrics()
        assert metrics
        self.assertIsInstance(metrics, list)
        self.assertTrue(len(metrics) > 0)

        for m in metrics:
            metric_name = m[0]
            if metric_name in metric_val_checks:
                self.assertTrue(metric_val_checks[metric_name](m[2]))

if __name__ == '__main__':
    unittest.main()
