Allow to specify timeout for commands in shotgun

Added new global setting DEFAULT_TIMEOUT for setting
default timeout (10s) for executing command.

It's also possible to specify timeout for all commands in
snapshot by using 'timeout' option in snapshot config file.

Additionally it's also possible to set command-specific timeout
by using 'timeout' option in command config.

Change-Id: If896485ea3b20ddde7bfc955a0a12b1faef98ea8
Closes-Bug: #1441954
This commit is contained in:
Sebastian Kalinowski 2015-09-22 13:43:49 +02:00
parent 4ffafc861c
commit 4c50b7025b
6 changed files with 82 additions and 27 deletions

View File

@ -62,3 +62,8 @@ class Config(object):
for object_ in properties.get("objects", []): for object_ in properties.get("objects", []):
object_["host"] = host object_["host"] = host
yield object_ yield object_
@property
def timeout(self):
"""Timeout for executing commands."""
return self.data.get("timeout", settings.DEFAULT_TIMEOUT)

View File

@ -45,6 +45,7 @@ class CommandOut(object):
class Driver(object): class Driver(object):
@classmethod @classmethod
def getDriver(cls, data, conf): def getDriver(cls, data, conf):
driver_type = data["type"] driver_type = data["type"]
@ -65,6 +66,7 @@ class Driver(object):
self.ssh_key = self.data.get("host", {}).get("ssh-key") self.ssh_key = self.data.get("host", {}).get("ssh-key")
self.local = utils.is_local(self.host) self.local = utils.is_local(self.host)
self.conf = conf self.conf = conf
self.timeout = self.data.get("timeout", self.conf.timeout)
def snapshot(self): def snapshot(self):
raise NotImplementedError raise NotImplementedError
@ -79,7 +81,7 @@ class Driver(object):
host_string=self.host, # destination host host_string=self.host, # destination host
key_filename=self.ssh_key, # a path to ssh key key_filename=self.ssh_key, # a path to ssh key
timeout=2, # a network connection timeout timeout=2, # a network connection timeout
command_timeout=10, # a command execution timeout command_timeout=self.timeout, # command execution timeout
warn_only=True, # don't exit on error warn_only=True, # don't exit on error
abort_on_prompts=True, # non-interactive mode abort_on_prompts=True, # non-interactive mode
): ):
@ -302,6 +304,7 @@ class XmlRpc(Driver):
class Command(Driver): class Command(Driver):
def __init__(self, data, conf): def __init__(self, data, conf):
super(Command, self).__init__(data, conf) super(Command, self).__init__(data, conf)
self.cmdname = self.data["command"] self.cmdname = self.data["command"]

View File

@ -17,3 +17,4 @@ LASTDUMP = "/tmp/snapshot_last"
TIMESTAMP = True TIMESTAMP = True
COMPRESSION_LEVEL = 3 COMPRESSION_LEVEL = 3
LOG_FILE = "/var/log/shotgun.log" LOG_FILE = "/var/log/shotgun.log"
DEFAULT_TIMEOUT = 10

View File

@ -12,11 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
try: from unittest2.case import TestCase
from unittest.case import TestCase
except ImportError:
# Runing unit-tests in production environment
from unittest2.case import TestCase
class BaseTestCase(TestCase): class BaseTestCase(TestCase):

View File

@ -12,10 +12,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from mock import patch
import re
import time import time
import mock
from shotgun.config import Config from shotgun.config import Config
from shotgun.test import base from shotgun.test import base
@ -24,7 +24,7 @@ class TestConfig(base.BaseTestCase):
def test_timestamp(self): def test_timestamp(self):
t = time.localtime() t = time.localtime()
with patch('shotgun.config.time') as MockedTime: with mock.patch('shotgun.config.time') as MockedTime:
MockedTime.localtime.return_value = t MockedTime.localtime.return_value = t
MockedTime.strftime.side_effect = time.strftime MockedTime.strftime.side_effect = time.strftime
conf = Config({}) conf = Config({})
@ -39,10 +39,20 @@ class TestConfig(base.BaseTestCase):
"target": "/tmp/sample", "target": "/tmp/sample",
"timestamp": True "timestamp": True
}) })
assert bool( self.assertRegex(
re.search( conf.target,
ur"\/tmp\/sample\-[\d]{4}\-[\d]{2}\-[\d]{2}_" ur"\/tmp\/sample\-[\d]{4}\-[\d]{2}\-[\d]{2}_"
"([\d]{2}\-){2}[\d]{2}", "([\d]{2}\-){2}[\d]{2}",
conf.target
)
) )
@mock.patch('shotgun.config.settings')
def test_timeout(self, m_settings):
conf = Config({})
self.assertIs(conf.timeout, m_settings.DEFAULT_TIMEOUT)
def test_pass_default_timeout(self):
timeout = 1345
conf = Config({
'timeout': timeout,
})
self.assertEqual(conf.timeout, timeout)

View File

@ -14,14 +14,13 @@
import fnmatch import fnmatch
import os import os
import random
import sys import sys
import fabric import fabric
import mock import mock
import shotgun.config import shotgun
import shotgun.driver
import shotgun.settings
from shotgun.test import base from shotgun.test import base
@ -63,17 +62,39 @@ class TestDriver(base.BaseTestCase):
command = "COMMAND" command = "COMMAND"
conf = mock.Mock()
driver = shotgun.driver.Driver( driver = shotgun.driver.Driver(
{"host": {"address": "remote_host"}}, None) {"host": {"address": "remote_host"}}, conf)
result = driver.command(command) result = driver.command(command)
shotgun.driver.fabric.api.run.assert_called_with( mfabrun.assert_called_with(
command, stdout=mock.ANY) command, stdout=mock.ANY)
shotgun.driver.fabric.api.settings.assert_called_with( mfabset.assert_called_with(
host_string="remote_host", timeout=2, command_timeout=10, host_string="remote_host",
warn_only=True, key_filename=None, abort_on_prompts=True) timeout=2,
command_timeout=driver.timeout,
warn_only=True,
key_filename=None,
abort_on_prompts=True)
self.assertEqual(result, out) self.assertEqual(result, out)
@mock.patch('shotgun.driver.fabric.api.run')
@mock.patch('shotgun.driver.fabric.api.settings')
def test_fabric_use_timout_from_driver(self, mfabset, _):
timeout = random.randint(1, 100)
conf = mock.Mock()
driver = shotgun.driver.Driver(
{"host": {"address": "remote_host"}}, conf)
driver.timeout = timeout
driver.command("COMMAND")
mfabset.assert_called_with(
host_string=mock.ANY,
timeout=mock.ANY,
command_timeout=timeout,
warn_only=mock.ANY,
key_filename=mock.ANY,
abort_on_prompts=mock.ANY)
@mock.patch('shotgun.driver.utils.execute') @mock.patch('shotgun.driver.utils.execute')
def test_driver_local_command(self, mexecute): def test_driver_local_command(self, mexecute):
mexecute.return_value = ("RETURN_CODE", "STDOUT", "STDERR") mexecute.return_value = ("RETURN_CODE", "STDOUT", "STDERR")
@ -84,7 +105,8 @@ class TestDriver(base.BaseTestCase):
out.return_code = "RETURN_CODE" out.return_code = "RETURN_CODE"
command = "COMMAND" command = "COMMAND"
driver = shotgun.driver.Driver({}, None) conf = mock.Mock()
driver = shotgun.driver.Driver({}, conf)
result = driver.command(command) result = driver.command(command)
shotgun.driver.utils.execute.assert_called_with(command) shotgun.driver.utils.execute.assert_called_with(command)
self.assertEqual(result, out) self.assertEqual(result, out)
@ -101,8 +123,9 @@ class TestDriver(base.BaseTestCase):
command = "COMMAND" command = "COMMAND"
conf = mock.Mock()
driver = shotgun.driver.Driver( driver = shotgun.driver.Driver(
{"host": {"address": "remote_host"}}, None) {"host": {"address": "remote_host"}}, conf)
result = driver.command(command) result = driver.command(command)
mstringio.assert_has_calls([ mstringio.assert_has_calls([
@ -118,13 +141,14 @@ class TestDriver(base.BaseTestCase):
mexecute.return_value = ("RETURN_CODE", "STDOUT", "STDERR") mexecute.return_value = ("RETURN_CODE", "STDOUT", "STDERR")
remote_path = "/remote_dir/remote_file" remote_path = "/remote_dir/remote_file"
target_path = "/target_dir" target_path = "/target_dir"
conf = mock.Mock()
driver = shotgun.driver.Driver({ driver = shotgun.driver.Driver({
"host": { "host": {
"address": "remote_host", "address": "remote_host",
"ssh-key": "path_to_key", "ssh-key": "path_to_key",
} }
}, None) }, conf)
driver.get(remote_path, target_path) driver.get(remote_path, target_path)
mexecute.assert_called_with('mkdir -p "{0}"'.format(target_path)) mexecute.assert_called_with('mkdir -p "{0}"'.format(target_path))
mfabget.assert_called_with(remote_path, target_path) mfabget.assert_called_with(remote_path, target_path)
@ -133,12 +157,28 @@ class TestDriver(base.BaseTestCase):
timeout=2, warn_only=True, abort_on_prompts=True) timeout=2, warn_only=True, abort_on_prompts=True)
mexecute.reset_mock() mexecute.reset_mock()
driver = shotgun.driver.Driver({}, None) driver = shotgun.driver.Driver({}, conf)
driver.get(remote_path, target_path) driver.get(remote_path, target_path)
self.assertEqual(mexecute.mock_calls, [ self.assertEqual(mexecute.mock_calls, [
mock.call('mkdir -p "{0}"'.format(target_path)), mock.call('mkdir -p "{0}"'.format(target_path)),
mock.call('cp -r "{0}" "{1}"'.format(remote_path, target_path))]) mock.call('cp -r "{0}" "{1}"'.format(remote_path, target_path))])
def test_use_timeout_from_global_conf(self):
data = {}
conf = mock.Mock(spec=shotgun.config.Config, target="some_target")
cmd_driver = shotgun.driver.Driver(data, conf)
self.assertEqual(cmd_driver.timeout, conf.timeout)
def test_use_command_specific_timeout(self):
timeout = 1234
data = {
"timeout": timeout
}
conf = mock.Mock(spec=shotgun.config.Config, target="some_target")
cmd_driver = shotgun.driver.Driver(data, conf)
self.assertEqual(cmd_driver.timeout, timeout)
self.assertNotEqual(cmd_driver.timeout, conf.timeout)
class TestFile(base.BaseTestCase): class TestFile(base.BaseTestCase):