Add freezer exec action to execute script

Implements: blueprint pre-post-exec

Change-Id: Id182facb84703a976e566429e005afa373d08965
This commit is contained in:
sbartel 2015-06-16 15:47:08 +02:00
parent 643d823cf2
commit c3bdcf2548
9 changed files with 206 additions and 11 deletions

View File

@ -21,6 +21,7 @@ Contributors
- Zahari Zahariev
- Eldar Nugaev
- Saad Zaher Saad
- Samuel Bartel
Credits
=======

View File

@ -64,7 +64,7 @@ DEFAULT_PARAMS = {
'restore_abs_path': False, 'log_file': None,
'upload': True, 'mode': 'fs', 'action': 'backup',
'vssadmin': True, 'shadow': '', 'shadow_path': '',
'windows_volume': ''
'windows_volume': '', 'command': None
}
@ -128,11 +128,13 @@ def backup_arguments(args_dict={}):
parents=[conf_parser])
arg_parser.add_argument(
'--action', choices=['backup', 'restore', 'info', 'admin'],
'--action', choices=['backup', 'restore', 'info', 'admin',
'exec'],
help=(
"Set the action to be taken. backup and restore are"
" self explanatory, info is used to retrieve info from the"
" storage media, while admin is used to delete old backups"
" storage media, exec is used to execute a script,"
" while admin is used to delete old backups"
" and other admin actions. Default backup."),
dest='action', default='backup')
arg_parser.add_argument(
@ -396,6 +398,10 @@ def backup_arguments(args_dict={}):
help='''Create a backup using a snapshot on windows
using vssadmin. Options are: True and False, default is True''',
dest='vssadmin', default=True)
arg_parser.add_argument(
'--command', action='store',
help='Command executed by exec action',
dest='command', default=None)
arg_parser.set_defaults(**defaults)
backup_args = arg_parser.parse_args()

60
freezer/exec_cmd.py Normal file
View File

@ -0,0 +1,60 @@
"""
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).
========================================================================
Freezer script execution related functions
"""
import subprocess
def execute(cmd):
"""
Split a command specified as function arguments into separate sub commands
executed separately
"""
cmds = cmd.split('|')
nb_process = len(cmds)
index = 1
process = None
for sub_cmd in cmds:
is_last_process = (index == nb_process)
process = popen_call(sub_cmd.split(' '), process, is_last_process)
index += 1
def popen_call(sub_cmd, input, is_last_process):
"""
Execute a command specified as function arguments using the given input
"""
if not input:
process = subprocess.Popen(sub_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=False)
else:
process = subprocess.Popen(sub_cmd,
stdout=subprocess.PIPE, stdin=input.stdout,
stderr=subprocess.PIPE, shell=False)
input.stdout.close()
if (is_last_process):
process.communicate()[0]
rc = process.returncode
if rc != 0:
raise Exception('Error: while executing script '
'%s return code was %d instead of 0'
% (' '.join(sub_cmd), rc))
return process

View File

@ -23,7 +23,7 @@ from freezer import swift
from freezer import utils
from freezer import backup
from freezer import restore
from freezer import exec_cmd
import logging
from freezer.restore import RestoreOs
@ -149,6 +149,20 @@ class AdminJob(Job):
swift.remove_obj_older_than(self.conf)
class ExecJob(Job):
@Job.executemethod
def execute(self):
logging.info('[*] exec job....')
if self.conf.command:
logging.info('[*] Executing exec job....')
exec_cmd.execute(self.conf.command)
else:
logging.warning(
'[*] No command info options were set. Exiting.')
return False
return True
def create_job(conf):
if conf.action == 'backup':
return BackupJob(conf)
@ -158,4 +172,6 @@ def create_job(conf):
return InfoJob(conf)
if conf.action == 'admin':
return AdminJob(conf)
if conf.action == 'exec':
return ExecJob(conf)
raise Exception('Action "{0}" not supported'.format(conf.action))

View File

@ -94,7 +94,7 @@ def freezer_main(args={}):
except Exception as priority_error:
logging.warning('[*] Priority: {0}'.format(priority_error))
# Alternative aruments provision useful to run Freezer without
# Alternative arguments provision useful to run Freezer without
# command line e.g. functional testing
if args:
backup_args.__dict__.update(args)

View File

@ -666,7 +666,7 @@ class FakeSwiftClient1:
pass
class Connection:
def __init__(self, key=True, os_options=True, os_auth_ver=True, user=True, authurl=True, tenant_name=True, retries=True, insecure=True):
def __init__(self, key=True, os_options=True, auth_version=True, user=True, authurl=True, tenant_name=True, retries=True, insecure=True):
pass
def put_object(self, opt1=True, opt2=True, opt3=True, opt4=True, opt5=True, headers=True, content_length=True, content_type=True):
@ -813,6 +813,7 @@ class BackupOpt1:
nova_client = MagicMock()
self.client_manager.get_nova = Mock(return_value=nova_client)
self.command = None
class FakeMySQLdb:

61
tests/test_exec_cmd.py Normal file
View File

@ -0,0 +1,61 @@
"""Freezer pre_post_exec.py related tests
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).
========================================================================
"""
from freezer import exec_cmd
from mock import patch, Mock
import subprocess
from __builtin__ import True
def test_exec_cmd(monkeypatch):
cmd="echo test > test.txt"
popen=patch('freezer.exec_cmd.subprocess.Popen')
mock_popen=popen.start()
mock_popen.return_value = Mock()
mock_popen.return_value.communicate = Mock()
mock_popen.return_value.communicate.return_value = ['some stderr']
mock_popen.return_value.returncode = 0
exec_cmd.execute(cmd)
assert (mock_popen.call_count == 1)
mock_popen.assert_called_with(['echo', 'test', '>', 'test.txt'],
shell=False,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
popen.stop()
def test__exec_cmd_with_pipe(monkeypatch):
cmd="echo test|wc -l"
popen=patch('freezer.exec_cmd.subprocess.Popen')
mock_popen=popen.start()
mock_popen.return_value = Mock()
mock_popen.return_value.communicate = Mock()
mock_popen.return_value.communicate.return_value = ['some stderr']
mock_popen.return_value.returncode = 0
exec_cmd.execute(cmd)
assert (mock_popen.call_count == 2)
popen.stop()

View File

@ -22,16 +22,19 @@ Hudson (tjh@cryptsoft.com).
"""
from commons import *
from freezer import (swift, restore, backup)
from freezer.job import Job, InfoJob, AdminJob, BackupJob, RestoreJob, create_job
from freezer import (
swift, restore, backup, exec_cmd)
from freezer.job import (
Job, InfoJob, AdminJob, BackupJob, RestoreJob, ExecJob, create_job)
import logging
from mock import patch, Mock
import pytest
import unittest
class TestJob:
def do_monkeypatch(self, monkeypatch):
fakelogging = FakeLogging()
self.fakeswift = fakeswift = FakeSwift()
@ -143,6 +146,51 @@ class TestAdminJob(TestJob):
assert job.execute() is None
class TestExecJob(TestJob):
def setUp(self):
#init mock_popen
self.popen=patch('freezer.exec_cmd.subprocess.Popen')
self.mock_popen=self.popen.start()
self.mock_popen.return_value = Mock()
self.mock_popen.return_value.communicate = Mock()
self.mock_popen.return_value.communicate.return_value = ['some stderr']
def tearDown(self):
self.popen.stop()
def test_execute_nothing_to_do(self, monkeypatch):
self.do_monkeypatch(monkeypatch)
backup_opt = BackupOpt1()
job = ExecJob(backup_opt)
assert job.execute() is False
def test_execute_script(self, monkeypatch):
self.setUp()
self.do_monkeypatch(monkeypatch)
self.mock_popen.return_value.returncode = 0
backup_opt = BackupOpt1()
backup_opt.command='echo test'
job = ExecJob(backup_opt)
assert job.execute() is True
self.tearDown()
def test_execute_raise(self, monkeypatch):
self.setUp()
self.do_monkeypatch(monkeypatch)
popen=patch('freezer.exec_cmd.subprocess.Popen')
self.mock_popen.return_value.returncode = 1
backup_opt = BackupOpt1()
backup_opt.command='echo test'
job = ExecJob(backup_opt)
pytest.raises(Exception, job.execute)
self.tearDown()
def test_create_job():
backup_opt = BackupOpt1()
backup_opt.action = None
@ -164,3 +212,6 @@ def test_create_job():
job = create_job(backup_opt)
assert isinstance(job, AdminJob)
backup_opt.action = 'exec'
job = create_job(backup_opt)
assert isinstance(job, ExecJob)

View File

@ -27,7 +27,6 @@ from commons import BackupOpt1
from freezer.main import freezer_main
from freezer import job
import pytest
import sys