Skip to content

Commit

Permalink
Merge pull request #208 from lachmanfrantisek/check-timeouts
Browse files Browse the repository at this point in the history
Check timeouts
  • Loading branch information
TomasTomecek authored Nov 8, 2018
2 parents d440dee + 7364391 commit a36c261
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 7 deletions.
5 changes: 4 additions & 1 deletion colin/cli/colin.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ def cli():
@click.option('--target-type', type=click.STRING, default="image",
help="Type of selected target (one of image, dockerfile, "
"ostree). For ostree, please specify image name and path like this: image@path")
@click.option('--timeout', type=click.INT,
help="Timeout for each check in seconds. (default=600)")
@click.option('--insecure', is_flag=True, default=False,
help="Pull from an insecure registry (HTTP or invalid TLS).")
def check(target, ruleset, ruleset_file, debug, json, stat, skip, tag, verbose,
checks_paths, target_type, pull, insecure):
checks_paths, target_type, timeout, pull, insecure):
"""
Check the image/dockerfile (default).
"""
Expand All @@ -106,6 +108,7 @@ def check(target, ruleset, ruleset_file, debug, json, stat, skip, tag, verbose,
pull=pull,
checks_paths=checks_paths,
target_type=target_type,
timeout=timeout,
insecure=insecure,
skips=skip
)
Expand Down
17 changes: 13 additions & 4 deletions colin/core/check_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,33 @@
import logging
import traceback

from .constant import CHECK_TIMEOUT
from .result import CheckResults, FailedCheckResult
from ..utils.cmd_tools import exit_after

logger = logging.getLogger(__name__)


def go_through_checks(target, checks):
def go_through_checks(target, checks, timeout=None):
logger.debug("Going through checks.")
results = _result_generator(target=target,
checks=checks)
checks=checks,
timeout=timeout)
return CheckResults(results=results)


def _result_generator(target, checks):
def _result_generator(target, checks, timeout=None):
try:
for check in checks:
logger.debug("Checking {}".format(check.name))
try:
yield check.check(target)
timeout = timeout or check.timeout or CHECK_TIMEOUT
logger.debug("Check timeout: {}".format(timeout))
yield exit_after(timeout)(check.check)(target)
except TimeoutError as ex:
logger.warning(
"The check hit the timeout.")
yield FailedCheckResult(check, logs=[str(ex)])
except Exception as ex:
tb = traceback.format_exc()
logger.warning(
Expand Down
1 change: 1 addition & 0 deletions colin/core/checks/abstract_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self, message, description, reference_url, tags):
self.description = description
self.reference_url = reference_url
self.tags = tags
self.timeout = None

def check(self, target):
pass
Expand Down
4 changes: 3 additions & 1 deletion colin/core/colin.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ def run(
pull=None,
insecure=False,
skips=None,
timeout=None,
):
"""
Runs the sanity checks for the target.
:param timeout: timeout per-check (in seconds)
:param skips: name of checks to skip
:param target: str (image name, ostree or dockertar)
or Image (instance from conu)
Expand Down Expand Up @@ -72,7 +74,7 @@ def run(
checks_paths=checks_paths,
skips=skips,
)
result = go_through_checks(target=target, checks=checks_to_run)
result = go_through_checks(target=target, checks=checks_to_run, timeout=timeout)
return result


Expand Down
2 changes: 2 additions & 0 deletions colin/core/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@
}

COLIN_CHECKS_PATH = "CHECKS_PATH"

CHECK_TIMEOUT = 10 * 60 # s
37 changes: 36 additions & 1 deletion colin/utils/cmd_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import logging
import subprocess
import threading

try:
import thread
except ImportError:
import _thread as thread

logger = logging.getLogger(__name__)


def get_version_of_the_python_package(module):
Expand Down Expand Up @@ -85,3 +93,30 @@ def is_rpm_installed():
except FileNotFoundError:
rpm_installed = False
return rpm_installed


def exit_after(s):
"""
Use as decorator to exit process if
function takes longer than s seconds.
Direct call is available via exit_after(TIMEOUT_IN_S)(fce)(args).
Inspired by https://stackoverflow.com/a/31667005
"""

def outer(fn):
def inner(*args, **kwargs):
timer = threading.Timer(s, thread.interrupt_main)
timer.start()
try:
result = fn(*args, **kwargs)
except KeyboardInterrupt:
raise TimeoutError("Function '{}' hit the timeout ({}s).".format(fn.__name__, s))
finally:
timer.cancel()
return result

return inner

return outer
55 changes: 55 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from time import sleep

import pytest

from colin.core.exceptions import ColinException
from colin.utils.cmd_tools import exit_after


@exit_after(1)
def fast_fce():
pass


@exit_after(1)
def slow_fce():
sleep(2)


@exit_after(1)
def bad_fce():
raise ColinException("Error")


def test_timeout_fast_fce():
fast_fce()


def test_timeout_slow_fce():
with pytest.raises(TimeoutError):
slow_fce()


def test_timeout_bad_fce():
with pytest.raises(ColinException):
bad_fce()


def test_timeout_dirrect():
with pytest.raises(TimeoutError):
exit_after(1)(sleep)(2)

0 comments on commit a36c261

Please sign in to comment.