# Copyright (c) 2010-2011 OpenStack, LLC.
#
# 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 os
from datetime import datetime
from tempfile import mkdtemp
from shutil import rmtree
from functools import partial
from collections import defaultdict
import random
import string

from test_slogging.unit import temptree
from slogging import log_uploader

import logging
logging.basicConfig(level=logging.DEBUG)
LOGGER = logging.getLogger()

COMPRESSED_DATA = '\x1f\x8b\x08\x08\x87\xa5zM\x02\xffdata\x00KI,I\x04\x00c' \
        '\xf3\xf3\xad\x04\x00\x00\x00'

access_regex = '''
    ^
    (?P<year>[0-9]{4})
    (?P<month>[0-1][0-9])
    (?P<day>[0-3][0-9])
    (?P<hour>[0-2][0-9])
    .*$
    '''


def mock_appconfig(*args, **kwargs):
    pass


class MockInternalProxy():

    def __init__(self, *args, **kwargs):
        pass

    def create_container(self, *args, **kwargs):
        return True

    def upload_file(self, *args, **kwargs):
        return True


_orig_LogUploader = log_uploader.LogUploader


class MockLogUploader(_orig_LogUploader):

    def __init__(self, conf, logger=LOGGER):
        conf['swift_account'] = conf.get('swift_account', '')
        conf['container_name'] = conf.get('container_name', '')
        conf['new_log_cutoff'] = conf.get('new_log_cutoff', '0')
        conf['source_filename_format'] = conf.get(
            'source_filename_format', conf.get('filename_format'))
        log_uploader.LogUploader.__init__(self, conf, 'plugin')
        self.logger = logger
        self.uploaded_files = []

    def upload_one_log(self, filename, year, month, day, hour):
        d = {'year': year, 'month': month, 'day': day, 'hour': hour}
        self.uploaded_files.append((filename, d))
        _orig_LogUploader.upload_one_log(self, filename, year, month,
                                         day, hour)


class ErrorLogUploader(MockLogUploader):

    def upload_one_log(self, filename, year, month, day, hour):
        raise OSError('foo bar')


class TestLogUploader(unittest.TestCase):

    def setUp(self):
        # mock internal proxy
        self._orig_InternalProxy = log_uploader.InternalProxy
        self._orig_appconfig = log_uploader.appconfig
        log_uploader.InternalProxy = MockInternalProxy
        log_uploader.appconfig = mock_appconfig

    def tearDown(self):
        log_uploader.appconfig = self._orig_appconfig
        log_uploader.InternalProxy = self._orig_InternalProxy

    def test_bad_upload(self):
        files = [datetime.now().strftime('%Y%m%d%H')]
        with temptree(files, contents=[COMPRESSED_DATA] * len(files)) as t:
            # invalid pattern
            conf = {'log_dir': t,
                    'source_filename_pattern': '%Y%m%d%h'}  # should be %H
            uploader = MockLogUploader(conf)
            self.assertRaises(SystemExit, uploader.upload_all_logs)

            conf = {'log_dir': t, 'source_filename_pattern': access_regex}
            uploader = ErrorLogUploader(conf)
            # this tests if the exception is handled
            uploader.upload_all_logs()

    def test_bad_pattern_in_config(self):
        files = [datetime.now().strftime('%Y%m%d%H')]
        with temptree(files, contents=[COMPRESSED_DATA] * len(files)) as t:
            # invalid pattern
            conf = {'log_dir': t,
                    'source_filename_pattern': '%Y%m%d%h'}  # should be %H
            uploader = MockLogUploader(conf)
            self.assertRaises(SystemExit, uploader.upload_all_logs)

            conf = {'log_dir': t, 'source_filename_pattern': access_regex}
            uploader = MockLogUploader(conf)
            uploader.upload_all_logs()
            self.assertEquals(len(uploader.uploaded_files), 1)

    def test_pattern_upload_all_logs(self):

        # test empty dir
        with temptree([]) as t:
            conf = {'log_dir': t}
            uploader = MockLogUploader(conf)
            self.assertRaises(SystemExit, uploader.run_once)

        def get_random_length_str(max_len=10, chars=string.ascii_letters):
            return ''.join(random.choice(chars) for x in
                           range(random.randint(1, max_len)))

        template = 'prefix_%(random)s_%(digits)s.blah.' \
                '%(datestr)s%(hour)0.2d00-%(next_hour)0.2d00-%(number)s.gz'
        pattern = '''prefix_.*_[0-9]+\.blah\.
                     (?P<year>[0-9]{4})
                     (?P<month>[0-1][0-9])
                     (?P<day>[0-3][0-9])
                     (?P<hour>[0-2][0-9])00-[0-9]{2}00
                     -[0-9]?[0-9]\.gz'''
        files_that_should_match = []
        # add some files that match
        for i in range(24):
            fname = template % {
                'random': get_random_length_str(),
                'digits': get_random_length_str(16, string.digits),
                'datestr': datetime.now().strftime('%Y%m%d'),
                'hour': i,
                'next_hour': i + 1,
                'number': random.randint(0, 20),
            }
            files_that_should_match.append(fname)

        # add some files that don't match
        files = list(files_that_should_match)
        for i in range(24):
            fname = template % {
                'random': get_random_length_str(),
                'digits': get_random_length_str(16, string.digits),
                'datestr': datetime.now().strftime('%Y%m'),
                'hour': i,
                'next_hour': i + 1,
                'number': random.randint(0, 20),
            }
            files.append(fname)

        for fname in files:
            print fname

        with temptree(files, contents=[COMPRESSED_DATA] * len(files)) as t:
            self.assertEquals(len(os.listdir(t)), 48)
            conf = {'source_filename_pattern': pattern, 'log_dir': t}
            uploader = MockLogUploader(conf)
            uploader.run_once()
            self.assertEquals(len(os.listdir(t)), 24)
            self.assertEquals(len(uploader.uploaded_files), 24)
            files_that_were_uploaded = set(x[0] for x in
                                           uploader.uploaded_files)
            for f in files_that_should_match:
                self.assert_(os.path.join(t, f) in files_that_were_uploaded)

    def test_log_cutoff(self):
        files = [datetime.now().strftime('%Y%m%d%H')]
        with temptree(files) as t:
            conf = {'log_dir': t, 'new_log_cutoff': '7200',
                    'source_filename_pattern': access_regex}
            uploader = MockLogUploader(conf)
            uploader.run_once()
            self.assertEquals(len(uploader.uploaded_files), 0)
            conf = {'log_dir': t, 'new_log_cutoff': '0',
                    'source_filename_pattern': access_regex}
            uploader = MockLogUploader(conf)
            uploader.run_once()
            self.assertEquals(len(uploader.uploaded_files), 1)

    def test_create_container_fail(self):
        files = [datetime.now().strftime('%Y%m%d%H')]
        conf = {'source_filename_pattern': access_regex}
        with temptree(files) as t:
            conf['log_dir'] = t
            uploader = MockLogUploader(conf)
            uploader.run_once()
            self.assertEquals(len(uploader.uploaded_files), 1)

        with temptree(files) as t:
            conf['log_dir'] = t
            uploader = MockLogUploader(conf)
            # mock create_container to fail
            uploader.internal_proxy.create_container = lambda *args: False
            uploader.run_once()
            self.assertEquals(len(uploader.uploaded_files), 0)

    def test_unlink_log(self):
        files = [datetime.now().strftime('%Y%m%d%H')]
        with temptree(files, contents=[COMPRESSED_DATA]) as t:
            conf = {'log_dir': t, 'unlink_log': 'false',
                    'source_filename_pattern': access_regex}
            uploader = MockLogUploader(conf)
            uploader.run_once()
            self.assertEquals(len(uploader.uploaded_files), 1)
            # file still there
            self.assertEquals(len(os.listdir(t)), 1)

            conf = {'log_dir': t, 'unlink_log': 'true',
                    'source_filename_pattern': access_regex}
            uploader = MockLogUploader(conf)
            uploader.run_once()
            self.assertEquals(len(uploader.uploaded_files), 1)
            # file gone
            self.assertEquals(len(os.listdir(t)), 0)

    def test_upload_file_failed(self):
        files = ['plugin-%s' % datetime.now().strftime('%Y%m%d%H')]
        with temptree(files, contents=[COMPRESSED_DATA]) as t:
            conf = {'log_dir': t, 'unlink_log': 'true',
                    'source_filename_pattern': access_regex}
            uploader = MockLogUploader(conf)

            # mock upload_file to fail, and clean up mock
            def mock_upload_file(self, *args, **kwargs):
                uploader.uploaded_files.pop()
                return False
            uploader.internal_proxy.upload_file = mock_upload_file
            self.assertRaises(SystemExit, uploader.run_once)
            # file still there
            self.assertEquals(len(os.listdir(t)), 1)


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