Add freezer exec action to execute script
Implements: blueprint pre-post-exec Change-Id: Id182facb84703a976e566429e005afa373d08965
This commit is contained in:
parent
643d823cf2
commit
c3bdcf2548
@ -21,6 +21,7 @@ Contributors
|
||||
- Zahari Zahariev
|
||||
- Eldar Nugaev
|
||||
- Saad Zaher Saad
|
||||
- Samuel Bartel
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
@ -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
60
freezer/exec_cmd.py
Normal 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
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
61
tests/test_exec_cmd.py
Normal 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()
|
@ -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)
|
||||
|
@ -27,7 +27,6 @@ from commons import BackupOpt1
|
||||
|
||||
from freezer.main import freezer_main
|
||||
from freezer import job
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user