# -*- coding: utf-8 -*-

# 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 mock
from testtools import matchers

from paunch import runner
from paunch.tests import base


class TestDockerRunner(base.TestCase):

    def setUp(self):
        super(TestDockerRunner, self).setUp()
        self.runner = runner.DockerRunner('tester')

    def mock_execute(self, popen, stdout, stderr, returncode):
        subproc = mock.Mock()
        subproc.returncode = returncode
        subproc.communicate.return_value = (stdout, stderr)
        popen.return_value = subproc

    def assert_execute(self, popen, cmd):
        popen.assert_called_with(cmd, stderr=-1, stdout=-1)

    @mock.patch('subprocess.Popen')
    def test_execute(self, popen):
        self.mock_execute(popen, 'The stdout', 'The stderr', 0)

        self.assertEqual(
            ('The stdout', 'The stderr', 0),
            self.runner.execute(['ls', '-l'])
        )
        self.assert_execute(popen, ['ls', '-l'])

    @mock.patch('subprocess.Popen')
    def test_current_config_ids(self, popen):
        self.mock_execute(popen, 'one\ntwo\nthree', '', 0)

        self.assertEqual(
            set(('one', 'two', 'three')),
            self.runner.current_config_ids()
        )
        self.assert_execute(
            popen, ['docker', 'ps', '-a', '--filter',
                    'label=managed_by=tester',
                    '--format', '{{.Label "config_id"}}']
        )

    @mock.patch('subprocess.Popen')
    def test_remove_containers(self, popen):
        self.mock_execute(popen, 'one\ntwo\nthree', '', 0)
        self.runner.remove_container = mock.Mock()

        self.runner.remove_containers('foo')

        self.assert_execute(
            popen, ['docker', 'ps', '-q', '-a',
                    '--filter', 'label=managed_by=tester',
                    '--filter', 'label=config_id=foo']
        )
        self.runner.remove_container.assert_has_calls([
            mock.call('one'), mock.call('two'), mock.call('three')
        ])

    @mock.patch('subprocess.Popen')
    def test_remove_container(self, popen):
        self.mock_execute(popen, '', '', 0)

        self.runner.remove_container('one')
        self.assert_execute(
            popen, ['docker', 'rm', '-f', 'one']
        )

    @mock.patch('subprocess.Popen')
    def test_rename_containers(self, popen):
        ps_result = '''one one
two-12345678 two
two two
three-12345678 three
four-12345678 four
'''

        self.mock_execute(popen, ps_result, '', 0)
        self.runner.rename_container = mock.Mock()

        self.runner.rename_containers()

        self.assert_execute(
            popen, ['docker', 'ps', '-a',
                    '--filter', 'label=managed_by=tester',
                    '--format', '{{.Names}} {{.Label "container_name"}}']
        )
        # only containers three-12345678 and four-12345678 four will be renamed
        self.runner.rename_container.assert_has_calls([
            mock.call('three-12345678', 'three'),
            mock.call('four-12345678', 'four')
        ], any_order=True)

    @mock.patch('subprocess.Popen')
    def test_inspect(self, popen):
        self.mock_execute(popen, '[{"foo": "bar"}]', '', 0)

        self.assertEqual(
            {"foo": "bar"},
            self.runner.inspect('one')
        )
        self.assert_execute(
            popen, ['docker', 'inspect', 'one']
        )

    @mock.patch('subprocess.Popen')
    def test_inspect_format(self, popen):
        self.mock_execute(popen, 'bar', '', 0)

        self.assertEqual(
            "bar",
            self.runner.inspect('one', format='{{foo}}')
        )
        self.assert_execute(
            popen, ['docker', 'inspect', '--format', '{{foo}}', 'one']
        )

    def test_unique_container_name(self):
        self.runner.inspect = mock.Mock()
        self.runner.inspect.return_value = None
        self.assertEqual('foo', self.runner.unique_container_name('foo'))

        self.runner.inspect.side_effect = ['exists', 'exists', None]
        name = self.runner.unique_container_name('foo')
        name_pattern = '^foo-[a-z0-9]{8}$'
        self.assertThat(name, matchers.MatchesRegex(name_pattern))

    @mock.patch('subprocess.Popen')
    def test_discover_container_name(self, popen):
        self.mock_execute(popen, 'one-12345678', '', 0)

        self.assertEqual(
            'one-12345678',
            self.runner.discover_container_name('one', 'foo')
        )

        self.assert_execute(
            popen, ['docker', 'ps', '-a',
                    '--filter', 'label=container_name=one',
                    '--filter', 'label=config_id=foo',
                    '--format', '{{.Names}}']
        )

    @mock.patch('subprocess.Popen')
    def test_discover_container_name_empty(self, popen):
        self.mock_execute(popen, '', '', 0)

        self.assertEqual(
            'one',
            self.runner.discover_container_name('one', 'foo')
        )

    @mock.patch('subprocess.Popen')
    def test_discover_container_name_error(self, popen):
        self.mock_execute(popen, '', 'ouch', 1)

        self.assertEqual(
            'one',
            self.runner.discover_container_name('one', 'foo')
        )

    @mock.patch('subprocess.Popen')
    def test_delete_missing_configs(self, popen):
        self.mock_execute(popen, 'one\ntwo\nthree\nfour', '', 0)
        self.runner.remove_containers = mock.Mock()

        self.runner.delete_missing_configs(['two', 'three'])
        self.assert_execute(
            popen, ['docker', 'ps', '-a', '--filter',
                    'label=managed_by=tester',
                    '--format', '{{.Label "config_id"}}']
        )

        # containers one and four will be deleted
        self.runner.remove_containers.assert_has_calls([
            mock.call('one'), mock.call('four')
        ], any_order=True)
