Add freezer exec action to execute script
Implements: blueprint pre-post-exec Change-Id: Id182facb84703a976e566429e005afa373d08965
This commit is contained in:
@@ -21,6 +21,7 @@ Contributors
|
|||||||
- Zahari Zahariev
|
- Zahari Zahariev
|
||||||
- Eldar Nugaev
|
- Eldar Nugaev
|
||||||
- Saad Zaher Saad
|
- Saad Zaher Saad
|
||||||
|
- Samuel Bartel
|
||||||
|
|
||||||
Credits
|
Credits
|
||||||
=======
|
=======
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ DEFAULT_PARAMS = {
|
|||||||
'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': '',
|
'vssadmin': True, 'shadow': '', 'shadow_path': '',
|
||||||
'windows_volume': ''
|
'windows_volume': '', 'command': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -128,11 +128,13 @@ def backup_arguments(args_dict={}):
|
|||||||
parents=[conf_parser])
|
parents=[conf_parser])
|
||||||
|
|
||||||
arg_parser.add_argument(
|
arg_parser.add_argument(
|
||||||
'--action', choices=['backup', 'restore', 'info', 'admin'],
|
'--action', choices=['backup', 'restore', 'info', 'admin',
|
||||||
|
'exec'],
|
||||||
help=(
|
help=(
|
||||||
"Set the action to be taken. backup and restore are"
|
"Set the action to be taken. backup and restore are"
|
||||||
" self explanatory, info is used to retrieve info from the"
|
" 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."),
|
" and other admin actions. Default backup."),
|
||||||
dest='action', default='backup')
|
dest='action', default='backup')
|
||||||
arg_parser.add_argument(
|
arg_parser.add_argument(
|
||||||
@@ -396,6 +398,10 @@ def backup_arguments(args_dict={}):
|
|||||||
help='''Create a backup using a snapshot on windows
|
help='''Create a backup using a snapshot on windows
|
||||||
using vssadmin. Options are: True and False, default is True''',
|
using vssadmin. Options are: True and False, default is True''',
|
||||||
dest='vssadmin', default=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)
|
arg_parser.set_defaults(**defaults)
|
||||||
backup_args = arg_parser.parse_args()
|
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 utils
|
||||||
from freezer import backup
|
from freezer import backup
|
||||||
from freezer import restore
|
from freezer import restore
|
||||||
|
from freezer import exec_cmd
|
||||||
import logging
|
import logging
|
||||||
from freezer.restore import RestoreOs
|
from freezer.restore import RestoreOs
|
||||||
|
|
||||||
@@ -149,6 +149,20 @@ class AdminJob(Job):
|
|||||||
swift.remove_obj_older_than(self.conf)
|
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):
|
def create_job(conf):
|
||||||
if conf.action == 'backup':
|
if conf.action == 'backup':
|
||||||
return BackupJob(conf)
|
return BackupJob(conf)
|
||||||
@@ -158,4 +172,6 @@ def create_job(conf):
|
|||||||
return InfoJob(conf)
|
return InfoJob(conf)
|
||||||
if conf.action == 'admin':
|
if conf.action == 'admin':
|
||||||
return AdminJob(conf)
|
return AdminJob(conf)
|
||||||
|
if conf.action == 'exec':
|
||||||
|
return ExecJob(conf)
|
||||||
raise Exception('Action "{0}" not supported'.format(conf.action))
|
raise Exception('Action "{0}" not supported'.format(conf.action))
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ def freezer_main(args={}):
|
|||||||
except Exception as priority_error:
|
except Exception as priority_error:
|
||||||
logging.warning('[*] Priority: {0}'.format(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
|
# command line e.g. functional testing
|
||||||
if args:
|
if args:
|
||||||
backup_args.__dict__.update(args)
|
backup_args.__dict__.update(args)
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ class FakeSwiftClient1:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Connection:
|
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
|
pass
|
||||||
|
|
||||||
def put_object(self, opt1=True, opt2=True, opt3=True, opt4=True, opt5=True, headers=True, content_length=True, content_type=True):
|
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()
|
nova_client = MagicMock()
|
||||||
|
|
||||||
self.client_manager.get_nova = Mock(return_value=nova_client)
|
self.client_manager.get_nova = Mock(return_value=nova_client)
|
||||||
|
self.command = None
|
||||||
|
|
||||||
|
|
||||||
class FakeMySQLdb:
|
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 commons import *
|
||||||
from freezer import (swift, restore, backup)
|
from freezer import (
|
||||||
|
swift, restore, backup, exec_cmd)
|
||||||
from freezer.job import Job, InfoJob, AdminJob, BackupJob, RestoreJob, create_job
|
from freezer.job import (
|
||||||
|
Job, InfoJob, AdminJob, BackupJob, RestoreJob, ExecJob, create_job)
|
||||||
import logging
|
import logging
|
||||||
|
from mock import patch, Mock
|
||||||
import pytest
|
import pytest
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
class TestJob:
|
class TestJob:
|
||||||
|
|
||||||
|
|
||||||
def do_monkeypatch(self, monkeypatch):
|
def do_monkeypatch(self, monkeypatch):
|
||||||
fakelogging = FakeLogging()
|
fakelogging = FakeLogging()
|
||||||
self.fakeswift = fakeswift = FakeSwift()
|
self.fakeswift = fakeswift = FakeSwift()
|
||||||
@@ -143,6 +146,51 @@ class TestAdminJob(TestJob):
|
|||||||
assert job.execute() is None
|
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():
|
def test_create_job():
|
||||||
backup_opt = BackupOpt1()
|
backup_opt = BackupOpt1()
|
||||||
backup_opt.action = None
|
backup_opt.action = None
|
||||||
@@ -164,3 +212,6 @@ def test_create_job():
|
|||||||
job = create_job(backup_opt)
|
job = create_job(backup_opt)
|
||||||
assert isinstance(job, AdminJob)
|
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.main import freezer_main
|
||||||
from freezer import job
|
from freezer import job
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user