#!/usr/bin/env python
#
# Public Domain 2014-2018 MongoDB, Inc.
# Public Domain 2008-2014 WiredTiger, Inc.
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

import glob
import os
import shutil
import string
from suite_subprocess import suite_subprocess
import wiredtiger, wttest
from wtdataset import SimpleDataSet, ComplexDataSet, ComplexLSMDataSet
from helper import compare_files

# test_backup.py
#    Utilities: wt backup
# Test backup (both backup cursors and the wt backup command).
class test_backup(wttest.WiredTigerTestCase, suite_subprocess):
    dir='backup.dir'            # Backup directory name

    pfx = 'test_backup'
    objs = [
        ( 'file:' + pfx + '.1', SimpleDataSet, 0),
        ( 'file:' + pfx + '.2', SimpleDataSet, 0),
        ('table:' + pfx + '.3', SimpleDataSet, 0),
        ('table:' + pfx + '.4', SimpleDataSet, 0),
        ('table:' + pfx + '.5', ComplexDataSet, 0),
        ('table:' + pfx + '.6', ComplexDataSet, 0),
        ('table:' + pfx + '.7', ComplexLSMDataSet, 1),
        ('table:' + pfx + '.8', ComplexLSMDataSet, 1),
    ]

    # Populate a set of objects.
    def populate(self, skiplsm):
        for i in self.objs:
            if i[2]:
                if skiplsm:
                        continue
            i[1](self, i[0], 100).populate()

    # Compare the original and backed-up files using the wt dump command.
    def compare(self, uri):
        self.runWt(['dump', uri], outfilename='orig')
        self.runWt(['-h', self.dir, 'dump', uri], outfilename='backup')
        self.assertEqual(True, compare_files(self, 'orig', 'backup'))

    # Test simple backup cursor open/close.
    def test_cursor_simple(self):
        cursor = self.session.open_cursor('backup:', None, None)
        cursor.close()

    # Test you can't have more than one backup cursor open at a time.
    def test_cursor_single(self):
        cursor = self.session.open_cursor('backup:', None, None)
        msg = '/there is already a backup cursor open/'
        self.assertRaisesWithMessage(wiredtiger.WiredTigerError,
            lambda: self.session.open_cursor('backup:', None, None), msg)
        cursor.close()

    # Test backup of a database using the wt backup command.
    def test_backup_database(self):
        self.populate(0)
        os.mkdir(self.dir)
        self.runWt(['backup', self.dir])

        # Make sure all the files were copied.
        self.runWt(['list'], outfilename='outfile.orig')
        self.runWt(['-h', self.dir, 'list'], outfilename='outfile.backup')
        self.assertEqual(
            True, compare_files(self, 'outfile.orig', 'outfile.backup'))

        # And that the contents are the same.
        for i in self.objs:
            self.compare(i[0])

    # Check that a URI doesn't exist, both the meta-data and the file names.
    def confirmPathDoesNotExist(self, uri):
        conn = self.wiredtiger_open(self.dir)
        session = conn.open_session()
        self.assertRaises(wiredtiger.WiredTigerError,
            lambda: session.open_cursor(uri, None, None))
        conn.close()

        self.assertEqual(
            glob.glob(self.dir + '*' + uri.split(":")[1] + '*'), [],
            'confirmPathDoesNotExist: URI exists, file name matching \"' +
            uri.split(":")[1] + '\" found')

    # Backup a set of chosen tables/files using the wt backup command.
    def backup_table(self, l):
        # Remove any previous backup directories.
        shutil.rmtree(self.dir, ignore_errors=True)
        os.mkdir(self.dir)

        # Build a command line of objects to back up and run wt.
        o = 'backup'
        for i in range(0, len(self.objs)):
            if i in l:
                o += ' -t ' + self.objs[i][0]
        o += ' ' + self.dir
        self.runWt(o.split())

        # Confirm the objects we backed up exist, with correct contents.
        for i in range(0, len(self.objs)):
            if i in l:
                self.compare(self.objs[i][0])

        # Confirm the other objects don't exist.
        for i in range(0, len(self.objs)):
            if i not in l:
                self.confirmPathDoesNotExist(self.objs[i][0])

    # Test backup of database subsets.
    def test_backup_table(self):
        self.populate(0)
        self.backup_table([0,2,4,6])
        self.backup_table([1,3,5,7])
        self.backup_table([0,1,2])
        self.backup_table([3,4,5])
        self.backup_table([5,6,7])

    # Test cursor reset runs through the list twice.
    def test_cursor_reset(self):
        self.populate(0)
        cursor = self.session.open_cursor('backup:', None, None)
        i = 0
        while True:
            ret = cursor.next()
            if ret != 0:
                break
            i += 1
        self.assertEqual(ret, wiredtiger.WT_NOTFOUND)
        total = i * 2
        cursor.reset()
        while True:
            ret = cursor.next()
            if ret != 0:
                break
            i += 1
        self.assertEqual(ret, wiredtiger.WT_NOTFOUND)
        self.assertEqual(i, total)

    # Test that named checkpoints can't be deleted while backup cursors are
    # open, but that normal checkpoints continue to work.
    def test_checkpoint_delete(self):
        # You cannot name checkpoints including LSM tables, skip those.
        self.populate(1)

        # Confirm checkpoints are being deleted.
        self.session.checkpoint("name=one")
        self.session.checkpoint("name=two,drop=(one)")
        self.assertRaises(wiredtiger.WiredTigerError,
            lambda: self.session.open_cursor(
            self.objs[0][0], None, "checkpoint=one"))

        # Confirm opening a backup cursor causes checkpoint to fail if dropping
        # a named checkpoint, but does not stop a default checkpoint.
        cursor = self.session.open_cursor('backup:', None, None)
        self.session.checkpoint()
        msg = '/checkpoints cannot be deleted during a hot backup/'
        self.assertRaisesWithMessage(wiredtiger.WiredTigerError,
            lambda: self.session.checkpoint("name=three,drop=(two)"), msg)
        self.session.checkpoint()
        self.assertRaisesWithMessage(wiredtiger.WiredTigerError,
            lambda: self.session.checkpoint("name=three,drop=(two)"), msg)
        self.session.checkpoint()
        cursor.close()

if __name__ == '__main__':
    wttest.run()
