예제 #1
0
def ensure_hypothesis_tolerance_is_valid(tolerance: Tolerance):
    """
    Validate the tolerance of the hypothesis probe and raises
    :exc:`InvalidActivity` if it isn't valid.
    """
    if not isinstance(tolerance, (bool, int, list, str, dict)):
        raise InvalidActivity(
            "hypothesis probe tolerance must either be an integer, "
            "a string, a boolean or a pair of values for boundaries. "
            "It can also be a dictionary which is a probe activity "
            "definition that takes an argument called `value` with "
            "the value of the probe itself to be validated")

    if isinstance(tolerance, dict):
        tolerance_type = tolerance.get("type")

        if tolerance_type == "probe":
            ensure_activity_is_valid(tolerance)
        elif tolerance_type == "regex":
            check_regex_pattern(tolerance)
        elif tolerance_type == "jsonpath":
            check_json_path(tolerance)
        elif tolerance_type == "range":
            check_range(tolerance)
        else:
            raise InvalidActivity(
                "hypothesis probe tolerance type '{}' is unsupported".format(
                    tolerance_type))
예제 #2
0
def ensure_hypothesis_is_valid(experiment: Experiment):
    """
    Validates that the steady state hypothesis entry has the expected schema
    or raises :exc:`InvalidExperiment` or :exc:`InvalidProbe`.
    """
    hypo = experiment.get("steady-state-hypothesis")
    if hypo is None:
        return

    if not hypo.get("title"):
        raise InvalidExperiment("hypothesis requires a title")

    probes = hypo.get("probes")
    if probes:
        for probe in probes:
            ensure_activity_is_valid(probe)

            if "tolerance" not in probe:
                raise InvalidActivity(
                    "hypothesis probe must have a tolerance entry")

            if not isinstance(probe["tolerance"], (
                    bool, int, list, str, dict)):
                raise InvalidActivity(
                    "hypothesis probe tolerance must either be an integer, "
                    "a string, a boolean or a pair of values for boundaries. "
                    "It can also be a dictionary which is a probe activity "
                    "definition that takes an argument called `value` with "
                    "the value of the probe itself to be validated")

            if isinstance(probe, dict):
                ensure_activity_is_valid(probe)
예제 #3
0
def ensure_hypothesis_is_valid(experiment: Experiment):
    """
    Validates that the steady state hypothesis entry has the expected schema
    or raises :exc:`InvalidExperiment` or :exc:`InvalidActivity`.
    """
    hypo = experiment.get("steady-state-hypothesis")
    if hypo is None:
        return

    if not hypo.get("title"):
        raise InvalidExperiment("hypothesis requires a title")

    probes = hypo.get("probes")
    if probes:
        for probe in probes:
            ensure_activity_is_valid(probe)

            if "tolerance" not in probe:
                raise InvalidActivity("hypothesis probe must have a tolerance entry")

            ensure_hypothesis_tolerance_is_valid(probe["tolerance"])
예제 #4
0
def ensure_experiment_is_valid(experiment: Experiment):
    """
    A chaos experiment consists of a method made of activities to carry
    sequentially.

    There are two kinds of activities:

    * probe: detecting the state of a resource in your system or external to it
      There are two kinds of probes: `steady` and `close`
    * action: an operation to apply against your system

    Usually, an experiment is made of a set of `steady` probes that ensure the
    system is sound to carry further the experiment. Then, an action before
    another set of of  ̀close` probes to sense the state of the system
    post-action.

    This function raises :exc:`InvalidExperiment`, :exc:`InvalidProbe` or
    :exc:`InvalidAction` depending on where it fails.
    """
    logger.info("Validating the experiment's syntax")

    if not experiment:
        raise InvalidExperiment("an empty experiment is not an experiment")

    if not experiment.get("title"):
        raise InvalidExperiment("experiment requires a title")

    if not experiment.get("description"):
        raise InvalidExperiment("experiment requires a description")

    tags = experiment.get("tags")
    if tags:
        if list(filter(lambda t: t == '' or not isinstance(t, str), tags)):
            raise InvalidExperiment(
                "experiment tags must be a non-empty string")

    validate_extensions(experiment)

    config = load_configuration(experiment.get("configuration", {}))
    load_secrets(experiment.get("secrets", {}), config)

    ensure_hypothesis_is_valid(experiment)

    method = experiment.get("method")
    if not method:
        raise InvalidExperiment("an experiment requires a method with "
                                "at least one activity")

    for activity in method:
        ensure_activity_is_valid(activity)

        # let's see if a ref is indeed found in the experiment
        ref = activity.get("ref")
        if ref and not lookup_activity(ref):
            raise InvalidActivity("referenced activity '{r}' could not be "
                                  "found in the experiment".format(r=ref))

    rollbacks = experiment.get("rollbacks", [])
    for activity in rollbacks:
        ensure_activity_is_valid(activity)

    warn_about_deprecated_features(experiment)

    validate_controls(experiment)

    logger.info("Experiment looks valid")
예제 #5
0
def test_http_probe_must_have_a_url():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.MissingHTTPUrlProbe)
    assert "a HTTP activity must have a URL" in str(exc.value)
예제 #6
0
def test_process_probe_path_must_exist():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.ProcessPathDoesNotExistProbe)
    assert "path 'somewhere/not/here' cannot be found, in activity" in str(
        exc.value)
예제 #7
0
def test_process_probe_have_a_path():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.MissingProcessPathProbe)
    assert "a process activity must have a path" in str(exc.value)
예제 #8
0
def test_empty_probe_is_invalid():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.EmptyProbe)
    assert "empty activity is no activity" in str(exc.value)
예제 #9
0
def test_python_probe_func_must_have_enough_args():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.MissingFuncArgProbe)
    assert "required argument 'path' is missing" in str(exc.value)
예제 #10
0
def test_python_probe_must_be_importable():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.NotImportableModuleProbe)
    assert "could not find Python module 'fake.module'" in str(exc.value)
예제 #11
0
def test_python_probe_must_have_a_function_name():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.MissingFunctionProbe)
    assert "a Python activity must have a function name" in str(exc.value)
예제 #12
0
def test_python_probe_must_have_a_module_path():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.MissingModuleProbe)
    assert "a Python activity must have a module path" in str(exc.value)
예제 #13
0
def test_probe_provider_must_have_a_known_type():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.UnknownProviderTypeProbe)
    assert "unknown provider type 'pizza'" in str(exc.value)
예제 #14
0
def test_probe_must_have_a_known_type():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.UnknownTypeProbe)
    assert "'whatever' is not a supported activity type" in str(exc.value)
예제 #15
0
def test_probe_must_have_a_type():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.MissingTypeProbe)
    assert "an activity must have a type" in str(exc.value)
def test_pauses_must_be_numbers_or_substitution_pattern():
    try:
        ensure_activity_is_valid({
            "name": "can-use-numbers",
            "type": "probe",
            "provider": {
                "type": "python",
                "module": "os.path",
                "func": "exists",
                "arguments": {
                    "path": os.getcwd()
                },
            },
            "pauses": {
                "before": 1,
                "after": 0.7
            },
        })
    except InvalidActivity:
        pytest.fail("pauses should support numbers")

    try:
        ensure_activity_is_valid({
            "name": "can-use-numbers",
            "type": "probe",
            "provider": {
                "type": "python",
                "module": "os.path",
                "func": "exists",
                "arguments": {
                    "path": os.getcwd()
                },
            },
            "pauses": {
                "before": "${pause_before}",
                "after": "${pause_after}",
            },
        })
    except InvalidActivity:
        pytest.fail("pauses should support substitution patterns")

    with pytest.raises(InvalidActivity):
        ensure_activity_is_valid({
            "name": "can-use-numbers",
            "type": "probe",
            "provider": {
                "type": "python",
                "module": "os.path",
                "func": "exists",
                "arguments": {
                    "path": os.getcwd()
                },
            },
            "pauses": {
                "before": "hello",
                "after": 0.9,
            },
        })

    with pytest.raises(InvalidActivity):
        ensure_activity_is_valid({
            "name": "can-use-numbers",
            "type": "probe",
            "provider": {
                "type": "python",
                "module": "os.path",
                "func": "exists",
                "arguments": {
                    "path": os.getcwd()
                },
            },
            "pauses": {
                "before": 0.8,
                "after": "world",
            },
        })
예제 #17
0
def test_empty_action_is_invalid():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(actions.EmptyAction)
    assert "empty activity is no activity" in str(exc.value)
예제 #18
0
def test_python_probe_func_cannot_have_too_many_args():
    with pytest.raises(InvalidActivity) as exc:
        ensure_activity_is_valid(probes.TooManyFuncArgsProbe)
    assert ("argument 'should_not_be_here' is not part of the "
            "function signature" in str(exc.value))