Reduce SQL Server downtime and Snapshot option on windows

CLI argument for snapshots on windows (vssadmin)
Reduce downtime with SQL Server if a snapshot is available

Change-Id: Iaae4d028cd19e1732925ec5bd86665b839528cd3
This commit is contained in:
Memo Garcia 2015-06-09 10:42:06 +01:00
parent 4a863ca7f3
commit 257ea34d23
9 changed files with 151 additions and 127 deletions

View File

@ -97,13 +97,13 @@ Windows
Install OpenSSL binaries from http://www.openssl.org/related/binaries.html and add Install OpenSSL binaries from http://www.openssl.org/related/binaries.html and add
it to Path: it to Path:
Swift client and Keystone client: Swift client and Keystone client::
> pip install python-swiftclient > pip install python-swiftclient
> pip install python-keystoneclient > pip install python-keystoneclient
> pip install freezer > pip install freezer
The basic Swift account configuration is needed to use freezer. Make sure python-swiftclient is installed. The basic Swift account configuration is needed to use freezer. Make sure python-swiftclient is installed::
set OS_REGION_NAME=region-a.geo-1 set OS_REGION_NAME=region-a.geo-1
set OS_TENANT_ID=<account tenant> set OS_TENANT_ID=<account tenant>
@ -135,7 +135,7 @@ The most simple backup execution is a direct file system backup::
* On windows (need admin rights)* * On windows (need admin rights)*
> freezerc --action backup --mode fs --backup-name testwindows > freezerc --action backup --mode fs --backup-name testwindows
--path-to-backup "C:\path\to\backup" --volume C:\ --container freezer_windows --path-to-backup "C:\path\to\backup" --container freezer_windows
--log-file C:\path\to\log\freezer.log --log-file C:\path\to\log\freezer.log
By default --mode fs is set. The command would generate a compressed tar By default --mode fs is set. The command would generate a compressed tar
@ -646,4 +646,6 @@ Available options::
Set the SQL Server configuration file where freezer Set the SQL Server configuration file where freezer
retrieve the sql server instance. Following is an retrieve the sql server instance. Following is an
example of config file: instance = <db-instance> example of config file: instance = <db-instance>
--volume VOLUME Create a snapshot of the selected volume --vssadmin VSSADMIN Create a backup using a snapshot on windows
using vssadmin. Options are: True and False,
default is True

View File

@ -116,7 +116,10 @@ def backup_arguments(args_dict={}):
'upload_limit': -1, 'always_level': False, 'version': False, 'upload_limit': -1, 'always_level': False, 'version': False,
'dry_run': False, 'lvm_snapsize': False, 'dry_run': False, 'lvm_snapsize': False,
'restore_abs_path': False, 'log_file': None, 'restore_abs_path': False, 'log_file': None,
'upload': True, 'mode': 'fs', 'action': 'backup'} 'upload': True, 'mode': 'fs', 'action': 'backup',
'vssadmin': True, 'shadow': '', 'shadow_path': '',
'windows_volume': ''
}
# Generate a new argparse istance and inherit options from config parse # Generate a new argparse istance and inherit options from config parse
arg_parser = argparse.ArgumentParser( arg_parser = argparse.ArgumentParser(
@ -376,9 +379,10 @@ def backup_arguments(args_dict={}):
instance = <db-instance>''', instance = <db-instance>''',
dest='sql_server_conf', default=False) dest='sql_server_conf', default=False)
arg_parser.add_argument( arg_parser.add_argument(
'--volume', action='store', '--vssadmin', action='store',
help='Create a snapshot of the selected volume', help='''Create a backup using a snapshot on windows
dest='volume', default=False) using vssadmin. Options are: True and False, default is True''',
dest='vssadmin', default=True)
arg_parser.set_defaults(**defaults) arg_parser.set_defaults(**defaults)
backup_args = arg_parser.parse_args() backup_args = arg_parser.parse_args()
@ -386,7 +390,7 @@ def backup_arguments(args_dict={}):
# windows bin # windows bin
path_to_binaries = os.path.dirname(os.path.abspath(__file__)) path_to_binaries = os.path.dirname(os.path.abspath(__file__))
# Intercept command line arguments if you are not using the CLI # Intercept command line arguments if you are not using the CLIvss
if args_dict: if args_dict:
backup_args.__dict__.update(args_dict) backup_args.__dict__.update(args_dict)
@ -463,6 +467,13 @@ def backup_arguments(args_dict={}):
backup_args.__dict__['shadow'] = '' backup_args.__dict__['shadow'] = ''
backup_args.__dict__['shadow_path'] = '' backup_args.__dict__['shadow_path'] = ''
backup_args.__dict__['file_name'] = '' backup_args.__dict__['file_name'] = ''
if is_windows():
if backup_args.path_to_backup:
backup_args.__dict__['windows_volume'] = \
backup_args.path_to_backup[:3]
if backup_args.vssadmin == 'False' or backup_args.vssadmin == 'false':
backup_args.vssadmin = False
backup_args.__dict__['meta_data'] = {} backup_args.__dict__['meta_data'] = {}
backup_args.__dict__['meta_data_file'] = '' backup_args.__dict__['meta_data_file'] = ''

View File

@ -33,8 +33,8 @@ from freezer.swift import add_object, manifest_upload
from freezer.utils import gen_manifest_meta, add_host_name_ts_level from freezer.utils import gen_manifest_meta, add_host_name_ts_level
from freezer.vss import vss_create_shadow_copy from freezer.vss import vss_create_shadow_copy
from freezer.vss import vss_delete_shadow_copy from freezer.vss import vss_delete_shadow_copy
from freezer.vss import start_sql_server from freezer.winutils import start_sql_server
from freezer.vss import stop_sql_server from freezer.winutils import stop_sql_server
from freezer.winutils import use_shadow from freezer.winutils import use_shadow
from freezer.winutils import is_windows from freezer.winutils import is_windows
from freezer import swift from freezer import swift
@ -63,6 +63,9 @@ def backup_mode_sql_server(backup_opt_dict, time_stamp, manifest_meta_dict):
stop_sql_server(backup_opt_dict) stop_sql_server(backup_opt_dict)
backup_mode_fs(backup_opt_dict, time_stamp, manifest_meta_dict) backup_mode_fs(backup_opt_dict, time_stamp, manifest_meta_dict)
finally: finally:
if not backup_opt_dict.vssadmin:
# if vssadmin is false, wait until the backup is complete
# to start sql server again
start_sql_server(backup_opt_dict) start_sql_server(backup_opt_dict)
@ -237,10 +240,14 @@ def backup_mode_fs(backup_opt_dict, time_stamp, manifest_meta_dict):
try: try:
if is_windows(): if is_windows():
# Create a shadow copy. if backup_opt_dict.vssadmin:
# Create a shadow copy. # Create a shadow copy.
backup_opt_dict.shadow_path, backup_opt_dict.shadow = \ backup_opt_dict.shadow_path, backup_opt_dict.shadow = \
vss_create_shadow_copy(backup_opt_dict.volume) vss_create_shadow_copy(backup_opt_dict.windows_volume)
# execute this after the snapshot creation
if backup_opt_dict.mode == 'sqlserver':
start_sql_server(backup_opt_dict)
else: else:
# If lvm_auto_snap is true, the volume group and volume name will # If lvm_auto_snap is true, the volume group and volume name will
@ -335,7 +342,7 @@ def backup_mode_fs(backup_opt_dict, time_stamp, manifest_meta_dict):
if is_windows(): if is_windows():
# Delete the shadow copy after the backup # Delete the shadow copy after the backup
vss_delete_shadow_copy(backup_opt_dict.shadow, vss_delete_shadow_copy(backup_opt_dict.shadow,
backup_opt_dict.volume) backup_opt_dict.windows_volume)
else: else:
# Unmount and remove lvm snapshot volume # Unmount and remove lvm snapshot volume
lvm_snap_remove(backup_opt_dict) lvm_snap_remove(backup_opt_dict)

View File

@ -382,7 +382,7 @@ def add_object(
package_name, time_stamp, package_name, time_stamp,
backup_opt_dict.max_segment_size, file_chunk_index) backup_opt_dict.max_segment_size, file_chunk_index)
add_chunk(backup_opt_dict.client_manager, add_chunk(backup_opt_dict.client_manager,
backup_opt_dict.container_segment, backup_opt_dict.container_segments,
package_name, file_chunk) package_name, file_chunk)

View File

@ -19,11 +19,11 @@ import logging
import os import os
def vss_create_shadow_copy(volume): def vss_create_shadow_copy(windows_volume):
""" """
Create a new shadow copy for the specified volume Create a new shadow copy for the specified volume
Windows registry path for vss: Windows registry path for vssadmin:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VSS\Settings HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VSS\Settings
MaxShadowCopies MaxShadowCopies
@ -64,10 +64,10 @@ def vss_create_shadow_copy(volume):
(out, err) = create_subprocess(['powershell.exe', (out, err) = create_subprocess(['powershell.exe',
'-executionpolicy', 'unrestricted', '-executionpolicy', 'unrestricted',
'-command', script, '-command', script,
'-volume', volume]) '-volume', windows_volume])
if err != '': if err != '':
raise Exception('[*] Error creating a new shadow copy on {0}' raise Exception('[*] Error creating a new shadow copy on {0}'
', error {1}' .format(volume, err)) ', error {1}' .format(windows_volume, err))
for line in out.split('\n'): for line in out.split('\n'):
if 'symbolic' in line: if 'symbolic' in line:
@ -82,7 +82,7 @@ def vss_create_shadow_copy(volume):
return shadow_path, shadow_id return shadow_path, shadow_id
def vss_delete_shadow_copy(shadow_id, volume): def vss_delete_shadow_copy(shadow_id, windows_volume):
""" """
Delete a shadow copy from the volume with the given shadow_id Delete a shadow copy from the volume with the given shadow_id
:param shadow_id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX :param shadow_id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
@ -98,38 +98,12 @@ def vss_delete_shadow_copy(shadow_id, volume):
', error {1}' .format(shadow_id, err)) ', error {1}' .format(shadow_id, err))
try: try:
os.rmdir(os.path.join(volume, 'shadowcopy')) os.rmdir(os.path.join(windows_volume, 'shadowcopy'))
except Exception: except Exception:
logging.error('Failed to delete shadow copy symlink {0}'. logging.error('Failed to delete shadow copy symlink {0}'.
format(os.path.join(volume, 'shadowcopy'))) format(os.path.join(windows_volume, 'shadowcopy')))
logging.info('[*] Deleting shadow copy {0}'. logging.info('[*] Deleting shadow copy {0}'.
format(shadow_id)) format(shadow_id))
return True return True
def stop_sql_server(backup_opt_dict):
""" Stop a SQL Server instance to perform the backup of the db files """
logging.info('[*] Stopping SQL Server for backup')
with DisableFileSystemRedirection():
cmd = 'net stop "SQL Server ({0})"'\
.format(backup_opt_dict.sql_server_instance)
(out, err) = create_subprocess(cmd)
if err != '':
raise Exception('[*] Error while stopping SQL Server,'
', error {0}'.format(err))
def start_sql_server(backup_opt_dict):
""" Start the SQL Server instance after the backup is completed """
with DisableFileSystemRedirection():
cmd = 'net start "SQL Server ({0})"'\
.format(backup_opt_dict.sql_server_instance)
(out, err) = create_subprocess(cmd)
if err != '':
raise Exception('[*] Error while starting SQL Server'
', error {0}'.format(err))
logging.info('[*] SQL Server back to normal')

View File

@ -17,8 +17,12 @@
# Hudson (tjh@cryptsoft.com). # Hudson (tjh@cryptsoft.com).
# ======================================================================== # ========================================================================
import sys
import ctypes import ctypes
import logging
import sys
from freezer.utils import create_subprocess
def is_windows(): def is_windows():
@ -51,9 +55,10 @@ class DisableFileSystemRedirection:
self._revert(self.old_value) self._revert(self.old_value)
def use_shadow(to_backup, volume): def use_shadow(to_backup, windows_volume):
""" add the shadow path to the backup directory """ """ add the shadow path to the backup directory """
return to_backup.replace(volume, '{0}shadowcopy\\'.format(volume)) return to_backup.replace(windows_volume, '{0}freezer_shadowcopy\\'
.format(windows_volume))
def clean_tar_command(tar_cmd): def clean_tar_command(tar_cmd):
@ -69,3 +74,29 @@ def clean_tar_command(tar_cmd):
def add_gzip_to_command(tar_cmd): def add_gzip_to_command(tar_cmd):
gzip_cmd = 'gzip -7' gzip_cmd = 'gzip -7'
return '{0} | {1}'.format(tar_cmd, gzip_cmd) return '{0} | {1}'.format(tar_cmd, gzip_cmd)
def stop_sql_server(backup_opt_dict):
""" Stop a SQL Server instance to perform the backup of the db files """
logging.info('[*] Stopping SQL Server for backup')
with DisableFileSystemRedirection():
cmd = 'net stop "SQL Server ({0})"'\
.format(backup_opt_dict.sql_server_instance)
(out, err) = create_subprocess(cmd)
if err != '':
raise Exception('[*] Error while stopping SQL Server,'
', error {0}'.format(err))
def start_sql_server(backup_opt_dict):
""" Start the SQL Server instance after the backup is completed """
with DisableFileSystemRedirection():
cmd = 'net start "SQL Server ({0})"'\
.format(backup_opt_dict.sql_server_instance)
(out, err) = create_subprocess(cmd)
if err != '':
raise Exception('[*] Error while starting SQL Server'
', error {0}'.format(err))
logging.info('[*] SQL Server back to normal')

View File

@ -27,76 +27,6 @@ import pytest
class TestVss: class TestVss:
def test_start_sql_server(self, monkeypatch):
fake_disable_redirection = FakeDisableFileSystemRedirection()
backup_opt = BackupOpt1()
fakelogging = FakeLogging()
fakesubprocess = FakeSubProcess()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__enter__',
fake_disable_redirection.__enter__)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__exit__',
fake_disable_redirection.__exit__)
monkeypatch.setattr(logging, 'info', fakelogging.info)
assert vss.start_sql_server(backup_opt) is not False
fakesubprocess = FakeSubProcess3()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
pytest.raises(Exception, vss.start_sql_server(backup_opt))
def test_stop_sql_server(self, monkeypatch):
fake_disable_redirection = FakeDisableFileSystemRedirection()
backup_opt = BackupOpt1()
fakelogging = FakeLogging()
fakesubprocess = FakeSubProcess()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__enter__',
fake_disable_redirection.__enter__)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__exit__',
fake_disable_redirection.__exit__)
monkeypatch.setattr(logging, 'info', fakelogging.info)
assert vss.start_sql_server(backup_opt) is not False
fakesubprocess = FakeSubProcess3()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
pytest.raises(Exception, vss.stop_sql_server(backup_opt))
def test_vss_create_shadow_copy(self, monkeypatch): def test_vss_create_shadow_copy(self, monkeypatch):
fake_disable_redirection = FakeDisableFileSystemRedirection() fake_disable_redirection = FakeDisableFileSystemRedirection()
fakelogging = FakeLogging() fakelogging = FakeLogging()

View File

@ -17,7 +17,9 @@ from freezer.winutils import use_shadow
from freezer.winutils import clean_tar_command from freezer.winutils import clean_tar_command
from freezer.winutils import add_gzip_to_command from freezer.winutils import add_gzip_to_command
from freezer.winutils import DisableFileSystemRedirection from freezer.winutils import DisableFileSystemRedirection
from freezer import winutils
from commons import * from commons import *
import logging
import pytest import pytest
@ -33,7 +35,7 @@ class TestWinutils:
test_volume = 'C:' test_volume = 'C:'
test_volume2 = 'C:\\' test_volume2 = 'C:\\'
path = 'C:\\Users\\Test' path = 'C:\\Users\\Test'
expected = 'C:\\shadowcopy\\Users\\Test' expected = 'C:\\freezer_shadowcopy\\Users\\Test'
assert use_shadow(path, test_volume2) == expected assert use_shadow(path, test_volume2) == expected
# test if the volume format is incorrect # test if the volume format is incorrect
@ -68,3 +70,70 @@ class TestWinutils:
pytest.raises(Exception, fake_disable_redirection.__enter__) pytest.raises(Exception, fake_disable_redirection.__enter__)
pytest.raises(Exception, fake_disable_redirection.__exit__) pytest.raises(Exception, fake_disable_redirection.__exit__)
def test_start_sql_server(self, monkeypatch):
fake_disable_redirection = FakeDisableFileSystemRedirection()
backup_opt = BackupOpt1()
fakelogging = FakeLogging()
fakesubprocess = FakeSubProcess()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__enter__',
fake_disable_redirection.__enter__)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__exit__',
fake_disable_redirection.__exit__)
monkeypatch.setattr(logging, 'info', fakelogging.info)
assert winutils.start_sql_server(backup_opt) is not False
fakesubprocess = FakeSubProcess3()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
pytest.raises(Exception, winutils.start_sql_server(backup_opt))
def test_stop_sql_server(self, monkeypatch):
fake_disable_redirection = FakeDisableFileSystemRedirection()
backup_opt = BackupOpt1()
fakelogging = FakeLogging()
fakesubprocess = FakeSubProcess()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__enter__',
fake_disable_redirection.__enter__)
monkeypatch.setattr(
winutils.DisableFileSystemRedirection, '__exit__',
fake_disable_redirection.__exit__)
monkeypatch.setattr(logging, 'info', fakelogging.info)
assert winutils.start_sql_server(backup_opt) is not False
fakesubprocess = FakeSubProcess3()
fakesubprocesspopen = fakesubprocess.Popen()
monkeypatch.setattr(
subprocess.Popen, 'communicate',
fakesubprocesspopen.communicate)
monkeypatch.setattr(
subprocess, 'Popen', fakesubprocesspopen)
pytest.raises(Exception, winutils.stop_sql_server(backup_opt))