freezer/tests/scenario/backup_scenario.py
eldar nugaev f79c191fd6 Improve test coverage of storage.py
Change-Id: I125fca414f5926649edc360309a0cce59a16f22c
Implements: blueprint improve-storage-covering
2015-08-06 14:36:06 +01:00

448 lines
18 KiB
Python

"""
Copyright 2015 Hewlett-Packard
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.
This product includes cryptographic software written by Eric Young
(eay@cryptsoft.com). This product includes software written by Tim
Hudson (tjh@cryptsoft.com).
========================================================================
"""
import os
import re
import sys
import uuid
import time
import unittest
import tempfile
import shutil
import random
import hashlib
import string
import time
from copy import copy
lib_path = os.path.abspath(os.path.join('..', '..'))
sys.path.append(lib_path)
from freezer import arguments, main, swift
class BackupScenarioFS(unittest.TestCase):
def create_tmp_tree(self, path):
"""
freezer_test_XXXXXX
<tmp_files>
...........
dir_foo
<tmp_files>
...........
dir_bar
<tmp_files>
...........
dir_foobar
<tmp_files>
...........
dir_barfoo
<tmp_files>
...........
dir_foofoo
<tmp_files>
...........
dir_barbar
<tmp_files>
...........
"""
dir_path = copy(path)
tmp_files = ['foo', 'bar', 'foobar', 'barfoo', 'foofoo', 'barbar', 'hello.lock']
tmp_dirs = ['', 'dir_foo', 'dir_bar', 'dir_foobar', 'dir_barfoo', 'dir_foofoo', 'dir_barbar']
self.tmp_files = []
for fd in tmp_dirs:
if fd:
dir_path += os.path.sep + fd
os.mkdir(dir_path)
for fn in tmp_files:
file_path = dir_path + os.path.sep + fn
with open(file_path, 'w') as handle:
handle.write(fn + '\n' + dir_path + '\n')
handle.close()
self.tmp_files.append(file_path)
def hashfile(self, filepath):
"""
Get GIT style sha1 hash for a file
"""
filesize_bytes = os.path.getsize(filepath)
hash_obj = hashlib.sha1()
hash_obj.update(("blob %u\0" % filesize_bytes).encode('utf-8'))
with open(filepath, 'rb') as handle:
hash_obj.update(handle.read())
return hash_obj.hexdigest()
def snap_tmp_tree_sha1(self, file_list):
"""
Record in a dictionary all files' absulute paths and SHA1
hashes so they can be compared taken before the backup and
after the restore of a given level.
"""
hash_dict = {}
for file_name in file_list:
if os.path.isfile(file_name):
hash_dict[file_name] = self.hashfile(file_name)
return hash_dict
def damage_tmp_tree(self, tmp_files):
"""
Delete and modify random files from the tree file structure
"""
# Delete some files
tmp_files = copy(tmp_files)
for counter in range(0, 5):
fn = random.choice(tmp_files)
if fn not in self.tmp_deleted:
os.unlink(fn)
self.tmp_deleted.append(fn)
# Change the content of a couple files
for counter in range(0, 2):
fn = random.choice(tmp_files)
if fn not in self.tmp_deleted:
f = open(fn, 'w')
change_date = time.strftime('%Y-%m-%dT%H:%M:%S',
time.localtime()
)
f.write('text changed on {}\n'.format(
change_date
))
f.close()
self.tmp_modified.append(fn)
def create_big_file(self, file_path, size):
"""
Create test text file with random data and
configurable size
"""
buf = list(string.printable)
with open(file_path, 'w') as handle:
for i in range(size//len(buf)):
random.shuffle(buf)
handle.write('%s' % ''.join(buf))
handle.close()
def setUp(self):
self.tmp_files = []
self.tmp_deleted = []
self.tmp_modified = []
self.tmp_path = tempfile.mkdtemp(prefix='freezer_test_')
self.create_tmp_tree(self.tmp_path)
def tearDown(self):
# shutil.rmtree(self.tmp_path)
pass
def test_utils_methods(self):
"""
Test functions that manipulate the files
"""
dict_1 = self.snap_tmp_tree_sha1(self.tmp_files)
self.damage_tmp_tree(self.tmp_files)
dict_2 = self.snap_tmp_tree_sha1(self.tmp_files)
self.assertEqual(len(self.tmp_files), len(dict_1))
self.assertEqual(len(dict_1), len(self.tmp_deleted) + len(dict_2))
for key in self.tmp_files:
if key in self.tmp_deleted:
self.assertFalse(os.path.isfile(key))
self.assertFalse(key in dict_2)
elif key in self.tmp_modified:
self.assertTrue(os.path.isfile(key))
self.assertNotEqual(key + dict_1[key], key + dict_2[key])
else:
self.assertTrue(os.path.isfile(key))
self.assertEqual(key + dict_1[key], key + dict_2[key])
def test_no_lvm_level0(self):
"""
Maximum level filesystem backup
freezerc --action backup
--path-to-backup /var/log
--backup-name rsync-var-log-test-XX
--container var-log-test-XX
"""
max_retry = 5
# Set arguments
backup_args = {
#'proxy' : '',
'action' : 'backup',
'path_to_backup' : copy(self.tmp_path),
'backup_name' : str(uuid.uuid4()),
'container' : str(uuid.uuid4())
}
# Namespace backup_args object
name_list = []
retry = 0
while backup_args['container'] not in name_list and retry < max_retry:
ns_backup_args = main.freezer_main(backup_args)
ns_backup_args = swift.get_container_content(ns_backup_args)
name_list = [item['name'] for item in ns_backup_args.containers_list]
retry += 1
self.assertTrue(ns_backup_args.container in name_list)
# self.assertTrue(segments_name(ns_backup_args.container) in name_list)
fdict_before = self.snap_tmp_tree_sha1(self.tmp_files)
self.damage_tmp_tree(self.tmp_files)
# Restore
restore_args = {
#'proxy' : '',
'action' : 'restore',
'restore_abs_path' : copy(self.tmp_path),
'backup_name' : copy(backup_args['backup_name']),
'container' : copy(backup_args['container'])
}
main.freezer_main(restore_args)
fdict_after = self.snap_tmp_tree_sha1(self.tmp_files)
self.assertEqual(len(self.tmp_files), len(fdict_before))
self.assertEqual(len(self.tmp_files), len(fdict_after))
for key in self.tmp_files:
self.assertTrue(os.path.isfile(key))
self.assertEqual(key + fdict_before[key], key + fdict_after[key])
def test_lvm_level0(self):
"""
LVM snapshot filesystem backup
freezerc --action backup
--lvm-srcvol /dev/freezer-test1-volgroup/freezer-test1-vol
--lvm-dirmount /tmp/freezer-test-lvm-snapshot
--lvm-volgroup freezer-test1-volgroup
--lvm-snapsize 1M
--file-to-backup /mnt/freezer-test-lvm/lvm_test_XXXX/
--container UUID
--exclude "\*.lock"
--backup-name UUID
"""
max_retry = 5
# Set arguments
lvm_path = '/mnt/freezer-test-lvm'
self.tmp_path = tempfile.mkdtemp(prefix='lvm_test_', dir=lvm_path)
self.create_tmp_tree(self.tmp_path)
backup_args = {
#'proxy' : '',
'action' : 'backup',
'lvm_srcvol' : '/dev/freezer-test1-volgroup/freezer-test1-vol',
'lvm_dirmount' : '/tmp/freezer-test-lvm-snapshot',
'lvm_volgroup' : 'freezer-test1-volgroup',
'lvm_snapsize' : '1M',
'exclude' : '*.lock',
'path_to_backup' : copy(self.tmp_path),
'backup_name' : str(uuid.uuid4()),
'container' : str(uuid.uuid4())
}
# Call the actual BACKUP
# Namespace backup_args object
name_list = []
retry = 0
while backup_args['container'] not in name_list and retry < max_retry:
ns_backup_args = main.freezer_main(backup_args)
ns_backup_args = swift.get_container_content(ns_backup_args)
name_list = [item['name'] for item in ns_backup_args.containers_list]
retry += 1
self.assertTrue(ns_backup_args.container in name_list)
# self.assertTrue(segments_name(ns_backup_args.container) in name_list)
# Create a file => SAH1 hash dictionary that will recored file
# hashes before any files being modified or deleted
fdict_before = self.snap_tmp_tree_sha1(self.tmp_files)
# Delete and modify random files in the test directory
# structure
self.damage_tmp_tree(self.tmp_files)
# RESTORE section
# Create RESTORE action dictionary to be passed to
# arguments.backup_arguments() they will emulate the
# command line arguments
restore_args = {
#'proxy' : '',
'action' : 'restore',
'restore_abs_path' : copy(self.tmp_path),
'backup_name' : copy(backup_args['backup_name']),
'container' : copy(backup_args['container'])
}
# Call RESTORE on Freezer code base
main.freezer_main(restore_args)
fdict_after = self.snap_tmp_tree_sha1(self.tmp_files)
self.assertEqual(len(self.tmp_files), len(fdict_before))
# Check if cout of all original files match recovered files
# plus the number of deleted .LOCK files which were not restored
self.assertEqual(len(self.tmp_files), len(fdict_after) +
len([x for x in self.tmp_deleted if x.endswith('.lock')]))
for key in self.tmp_files:
if key.endswith('.lock') and key in self.tmp_deleted:
self.assertFalse(os.path.isfile(key))
elif key.endswith('.lock') and key in self.tmp_modified:
self.assertNotEqual(key + fdict_before[key], key + fdict_after[key])
else:
self.assertTrue(os.path.isfile(key))
self.assertEqual(key + fdict_before[key], key + fdict_after[key])
def test_bandwith_limit(self):
"""
Freezer upload/download speed limit test. We set a fixed 512KB/s speed and
try to backup (upload) 1MB file na restore (download) the backup. Each of
those action on avarage should not take more than 2s or less than 3s
2s < EXEC_TIME < 3s. Without throttle it is normaly about 0.4s.
freezerc --action backup
--path-to-backup /tmp/freezer_test_XXXX
--backup-name UUID
--container UUID
--upload-limit 524288
freezerc --action restore
--path-to-backup /tmp/freezer_test_XXXX
--backup-name UUID
--container UUID
--download-limit 524288
"""
# print '\nWorking in:', self.tmp_path
# Set 512KB/s connection limit
speed_limit_bytes = 512 * 1024
time_low = 2
abs_file_name = self.tmp_path + os.path.sep + 'limitfoo'
# Create 1MB test text file with random data
self.create_big_file(abs_file_name, 2 * speed_limit_bytes)
# Freezer CLI for backup argument dictionary
backup_args = {
'action' : 'backup',
'path_to_backup' : copy(self.tmp_path),
'backup_name' : str(uuid.uuid4()),
'container' : str(uuid.uuid4()),
'upload_limit' : speed_limit_bytes
}
start_time = time.time()
# Call Freezer CLI backup
main.freezer_main(backup_args)
end_time = time.time()
# Calculate backup time in sec
upload_time = end_time - start_time
# print "\nUpload time: %g seconds" % upload_time
# Test that time is longer than the theoretical 2 sec
self.assertTrue(time_low < upload_time)
# Delete test file
os.unlink(abs_file_name)
# Build dictionary for Freezer CLI restore
restore_args = {
'action' : 'restore',
'restore_abs_path' : copy(self.tmp_path),
'backup_name' : copy(backup_args['backup_name']),
'container' : copy(backup_args['container']),
'download_limit' : speed_limit_bytes
}
start_time = time.time()
# Call the actual Freezer CLI restore
main.freezer_main(restore_args)
end_time = time.time()
self.assertTrue(os.path.isfile(abs_file_name))
# Calculate restore time in sec
download_time = end_time - start_time
# print "Download time: %g seconds" % download_time
# sys.stdout.flush()
# Test that time is longer than the theoretical 2 sec
self.assertTrue(time_low < download_time)
def test_lvm_incremental_level5(self):
"""
Incremental LVM snapshots filesystem backup
freezerc --action backup
--lvm-srcvol /dev/freezer-test1-volgroup/freezer-test1-vol
--lvm-dirmount /tmp/freezer-test-lvm-snapshot
--lvm-volgroup freezer-test1-volgroup
--lvm-snapsize 1M
--file-to-backup /mnt/freezer-test-lvm/lvm_test_XXXX/
--container UUID
--exclude "\*.lock"
--backup-name UUID
--max-level 5
"""
# Set arguments
lvm_path = '/mnt/freezer-test-lvm'
self.tmp_path = tempfile.mkdtemp(prefix='lvm_test_', dir=lvm_path)
self.create_tmp_tree(self.tmp_path)
max_level = 5
backup_args = {
'action' : 'backup',
'lvm_srcvol' : '/dev/freezer-test1-volgroup/freezer-test1-vol',
'lvm_dirmount' : '/tmp/freezer-test-lvm-snapshot',
'lvm_volgroup' : 'freezer-test1-volgroup',
'lvm_snapsize' : '1M',
'path_to_backup' : copy(self.tmp_path),
'backup_name' : str(uuid.uuid4()),
'container' : str(uuid.uuid4()),
'max_level' : max_level
}
fdict_before = []
# print ''
for i in range(0, max_level):
# print "TEST FILE CONTENT BEFORE BACKUP %s:" % i
# print open(self.tmp_path + os.path.sep + 'foo', 'r').read()
fdict_before.append(
self.snap_tmp_tree_sha1(self.tmp_files)
)
ns_backup_args = main.freezer_main(backup_args)
self.damage_tmp_tree(self.tmp_files)
# time.sleep(2)
# Filter only the container names from all other data
ns_backup_args = swift.get_container_content(
ns_backup_args.client_manager,
ns_backup_args.container)
name_list = [item['name'] for item in ns_backup_args]
for counter in range(0, max_level):
found_objects = [obj for obj in name_list if obj.endswith('_%s' % counter)]
objects_str = ' '.join(found_objects)
# print objects_str
self.assertEqual('%s(%s)' % (objects_str,
len(found_objects)), objects_str + '(2)')
found_objects = sorted(found_objects)
self.assertEqual(found_objects[1], found_objects[0][-len(found_objects[1]):])
# From max_level-1 downto 0
for i in range(max_level - 1, -1, -1):
restore_level = i
restore_epoch = re.findall('_(\d{10}?)_%s' % restore_level , ' '.join(name_list))[0]
restore_epoch = int(restore_epoch) + 0
restore_date = time.strftime('%Y-%m-%dT%H:%M:%S',
time.localtime(int(restore_epoch))
)
# print 'Restore level:', restore_level, restore_epoch, restore_date
# Remove all files in the whole testing directory
shutil.rmtree(self.tmp_path)
self.assertFalse(os.path.isdir(self.tmp_path))
os.makedirs(self.tmp_path)
# Build
restore_args = {
'action' : 'restore',
'restore_abs_path' : copy(self.tmp_path),
'backup_name' : copy(backup_args['backup_name']),
'container' : copy(backup_args['container']),
'restore_from_date' : restore_date
}
# Call RESTORE on Freezer code base
main.freezer_main(restore_args)
fdict_after = self.snap_tmp_tree_sha1(self.tmp_files)
self.assertEqual(len(fdict_before[restore_level]), len(fdict_after))
for key in fdict_before[restore_level]:
self.assertTrue(os.path.isfile(key))
self.assertEqual(key + fdict_before[restore_level][key], key + fdict_after[key])
# print 'Just checked %s files' % len(fdict_after)
if __name__ == '__main__':
unittest.main()