from __future__ import absolute_import
import logging
import mock
import os
import tempfile
import yaml

import six

from deployer.deployment import Deployment
from deployer.config import ConfigStack
from deployer.utils import ErrorExit

from .base import Base


class ConfigTest(Base):

    def setUp(self):
        self.output = self.capture_logging(
            "deployer.config", level=logging.DEBUG)

    def test_config_basic(self):
        config = ConfigStack(['configs/ostack-testing-sample.cfg'])
        config.load()
        self.assertEqual(
            list(config.keys()),
            [u'openstack-precise-ec2',
             u'openstack-precise-ec2-trunk',
             u'openstack-ubuntu-testing'])
        self.assertRaises(ErrorExit, config.get, 'zeeland')
        result = config.get("openstack-precise-ec2")
        self.assertTrue(isinstance(result, Deployment))
        self.assertEqual(config.version, 3)

    def test_config(self):
        config = ConfigStack([
            os.path.join(self.test_data_dir, "stack-default.cfg"),
            os.path.join(self.test_data_dir, "stack-inherits.cfg")])
        config.load()
        self.assertEqual(
            list(config.keys()),
            [u'my-files-frontend-dev', u'wordpress'])
        deployment = config.get("wordpress")
        self.assertTrue(deployment)
        self.assertEqual(config.version, 3)

    def test_config_v4(self):
        config = ConfigStack([
            os.path.join(self.test_data_dir, 'v4', 'simple.yaml')])
        config.load()
        self.assertEqual(
            list(config.keys()),
            [os.path.join(self.test_data_dir, 'v4', 'simple.yaml')])
        with mock.patch('deployer.config.ConfigStack._resolve_inherited') \
                as mock_resolve:
            deployment = config.get(list(config.keys())[0])
            self.assertTrue(deployment)
            self.assertFalse(mock_resolve.called)
        self.assertEqual(config.version, 4)

    def test_config_include_file(self):
        config = ConfigStack([
            os.path.join(self.test_data_dir, "stack-includes.cfg")])
        config.load()
        # ensure picked up stacks from both files
        self.assertEqual(
            list(config.keys()),
            [u'my-files-frontend-dev', u'wordpress'])

        # ensure inheritance was adhered to during cross-file load
        wordpress = [s.name for s in config.get('wordpress').get_services()]
        my_app = [s.name for s in
                  config.get('my-files-frontend-dev').get_services()]
        self.assertTrue(set(wordpress).issubset(set(my_app)))

    def test_inherits_config_overridden(self):
        config = ConfigStack([
            os.path.join(self.test_data_dir, "stack-default.cfg"),
            os.path.join(self.test_data_dir, "stack-inherits.cfg")])
        config.load()
        deployment = config.get('my-files-frontend-dev')
        db = deployment.get_service('db')
        # base deployment (wordpress)'s db tuning level should have been
        # over-ridden
        self.assertEquals(db.config.get('tuning-level'), 'fastest')

    def test_multi_inheritance_multi_files(self):
        config = ConfigStack([
            os.path.join(self.test_data_dir, "openstack", "openstack.cfg"),
            os.path.join(self.test_data_dir, "openstack", "ubuntu_base.cfg"),
            os.path.join(
                self.test_data_dir, "openstack", "openstack_base.cfg"),
        ])
        self._test_multiple_inheritance(config)

    def test_multi_inheritance_multi_included_files(self):
        # openstack.cfg
        #  includes -> ubuntu_base.cfg includes
        #               includes -> openstack_base.cfg
        test_conf = yaml.load(open(
            os.path.join(self.test_data_dir, "openstack", "openstack.cfg")))
        includes = [
            os.path.join(self.test_data_dir, "openstack", "ubuntu_base.cfg"),
            os.path.join(self.test_data_dir, "openstack", "openstack_base.cfg")
        ]
        for key in ['include-config', 'include-configs']:
            test_conf[key] = includes
            with tempfile.NamedTemporaryFile() as tmp_cfg:
                tmp_cfg.write(yaml.dump(test_conf).encode())
                tmp_cfg.flush()
                config = ConfigStack([tmp_cfg.name])
                self._test_multiple_inheritance(config)
            del test_conf[key]

    def test_multi_inheritance_included_multi_configs(self):
        # openstack.cfg
        #  includes -> [ubuntu_base.cfg, openstack_base.cfg]
        config = ConfigStack([
            os.path.join(self.test_data_dir, "openstack", "openstack.cfg"),
        ])
        self._test_multiple_inheritance(config)

    def _test_multiple_inheritance(self, config):
        config.load()

        deployment = config.get('precise-grizzly')
        services = [s.name for s in list(deployment.get_services())]
        self.assertEquals(['mysql', 'nova-cloud-controller'], services)

        nova = deployment.get_service('nova-cloud-controller')
        self.assertEquals(nova.config['openstack-origin'],
                          'cloud:precise-grizzly')

        deployment = config.get('precise-grizzly-quantum')
        services = [s.name for s in list(deployment.get_services())]
        self.assertEquals(services,
                          ['mysql', 'nova-cloud-controller',
                           'quantum-gateway'])
        nova = deployment.get_service('nova-cloud-controller')
        self.assertEquals(nova.config['network-manager'], 'Quantum')
        self.assertEquals(nova.config['openstack-origin'],
                          'cloud:precise-grizzly')
        ex_rels = [('quantum-gateway', 'nova-cloud-controller'),
                   ('quantum-gateway', 'mysql'),
                   ('nova-cloud-controller', 'mysql')]
        self.assertEquals(
            sorted(ex_rels),
            sorted(list(deployment.get_relations())))

    def test_config_series_override(self):
        config = ConfigStack(['configs/wiki.yaml'], 'trusty')
        config.load()
        result = config.get("wiki")
        self.assertTrue(isinstance(result, Deployment))
        self.assertEquals(result.series, 'trusty')


class NetworkConfigFetchingTests(Base):
    """Configuration files can be specified via URL that is then fetched."""

    def setUp(self):
        self.output = self.capture_logging(
            "deployer.config", level=logging.DEBUG)

    def test_urls_are_fetched(self):
        # If a config file is specified as a URL, that URL is fetched and
        # placed at a temporary location where it is read and treated as a
        # regular config file.
        CONFIG_URL = 'http://site.invalid/config-1'
        config = ConfigStack([])
        config.config_files = [CONFIG_URL]

        class FauxResponse(six.BytesIO):
            def getcode(self):
                return 200

        def faux_urlopen(url):
            self.assertEqual(url, CONFIG_URL)
            with open('configs/ostack-testing-sample.cfg', 'rb') as f:
                return FauxResponse(f.read())

        config.urlopen = faux_urlopen
        config.load()
        self.assertEqual(
            list(config.keys()),
            [u'openstack-precise-ec2',
             u'openstack-precise-ec2-trunk',
             u'openstack-ubuntu-testing'])
        self.assertRaises(ErrorExit, config.get, 'zeeland')
        result = config.get("openstack-precise-ec2")
        self.assertTrue(isinstance(result, Deployment))

    def test_unfetchable_urls_generate_an_error(self):
        # If a config file is specified as a URL, that URL is fetched and
        # placed at a temporary location where it is read and treated as a
        # regular config file.
        CONFIG_URL = 'http://site.invalid/config-1'
        config = ConfigStack([])
        config.config_files = [CONFIG_URL]

        class FauxResponse(six.BytesIO):
            def getcode(self):
                return 400

        def faux_urlopen(url):
            self.assertEqual(url, CONFIG_URL)
            with open('configs/ostack-testing-sample.cfg', 'rb') as f:
                return FauxResponse(f.read())

        config.urlopen = faux_urlopen
        self.assertRaises(ErrorExit, config.load)
