Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Retry errors during storage access #74

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.1] - 2023-09-13

### Added

- Add retry when accessing the storage

## [1.4.0] - 2023-09-09

### Added
Expand Down
87 changes: 53 additions & 34 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "sekoia-automation-sdk"

version = "1.4.0"
version = "1.4.1"
description = "SDK to create Sekoia.io playbook modules"
license = "MIT"
readme = "README.md"
Expand Down Expand Up @@ -33,8 +33,8 @@ python = ">=3.10,<3.12"
requests = "^2.25"
sentry-sdk = "*"
tenacity = "*"
boto3 = "^1.26"
s3path = "^0.4"
boto3 = "^1.28"
s3path = "^0.5"
orjson = "^3.8"
pydantic = "^1.10"
typer = { extras = ["all"], version = "^0.7"}
Expand Down
11 changes: 9 additions & 2 deletions sekoia_automation/module.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import sys
import time
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, cast
Expand All @@ -17,7 +18,10 @@
SendEventError,
)
from sekoia_automation.storage import get_data_path
from sekoia_automation.utils import get_annotation_for, get_as_model
from sekoia_automation.utils import (
get_annotation_for,
get_as_model,
)


class Module:
Expand Down Expand Up @@ -304,6 +308,8 @@ class ModuleItem(ABC):
description: str | None = None
results_model: type[BaseModel] | None = None

_http_error_base_sleep = 0.5

def __init__(self, module: Module | None = None, data_path: Path | None = None):
self.module: Module = module or Module()

Expand Down Expand Up @@ -391,7 +397,7 @@ def _send_request(self, data: dict, verb: str = "POST", attempt=1) -> Response:
return response
except HTTPError as exception:
self._log_request_error(exception)
if attempt == 3:
if attempt == 5:
status_code = (
exception.response.status_code
if isinstance(exception.response, Response)
Expand All @@ -408,6 +414,7 @@ def _send_request(self, data: dict, verb: str = "POST", attempt=1) -> Response:
"Impossible to send event to Sekoia.io API",
status_code=exception.response.status_code,
)
time.sleep(attempt * self._http_error_base_sleep)
return self._send_request(data, verb, attempt + 1)

def _log_request_error(self, exception: HTTPError):
Expand Down
39 changes: 37 additions & 2 deletions sekoia_automation/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@
import orjson
from botocore.config import Config
from s3path import S3Path, register_configuration_parameter
from tenacity import retry, stop_after_attempt, wait_exponential

from sekoia_automation import constants
from sekoia_automation.config import VOLUME_PATH, load_config
from sekoia_automation.utils import capture_retry_error

FilePath = Path | str


@lru_cache
@retry(
reraise=True,
wait=wait_exponential(max=6),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
)
def get_s3_data_path() -> Path:
ca_path, cert_path, key_path = _get_tls_client_credentials()
config = Config()
Expand Down Expand Up @@ -133,6 +141,12 @@ def __init__(self, filepath: FilePath, data_path: Path | None = None):

self._data: dict = {}

@retry(
reraise=True,
wait=wait_exponential(max=6),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
)
def load(self):
if not self._data and self._filepath.is_file():
with self._filepath.open("r") as fd:
Expand All @@ -142,16 +156,31 @@ def load(self):
# The content is not valid json
self._data = {}

@retry(
reraise=True,
wait=wait_exponential(max=6),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
)
def dump(self):
with self._filepath.open("w") as out:
out.write(orjson.dumps(self._data).decode("utf-8"))

def __enter__(self):
self.load()

return self._data

def __exit__(self, _, __, ___):
with self._filepath.open("w") as out:
out.write(orjson.dumps(self._data).decode("utf-8"))
self.dump()


@retry(
reraise=True,
wait=wait_exponential(max=6),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
)
def temp_directory(data_path: Path | None = None) -> str:
"""Create a temporary directory inside data storage.

Expand All @@ -166,6 +195,12 @@ def temp_directory(data_path: Path | None = None) -> str:
return name


@retry(
reraise=True,
wait=wait_exponential(max=6),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
)
def write(
filename: str,
content: dict | str,
Expand Down
7 changes: 7 additions & 0 deletions sekoia_automation/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ def __init__(self, module: Module | None = None, data_path: Path | None = None):
self._liveness_server = None
self._exporter = None

@retry(
reraise=True,
wait=wait_exponential(max=10),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
)
def _get_secrets_from_server(self) -> dict[str, Any]:
"""Calls the API to fetch this trigger's secrets

Expand Down Expand Up @@ -278,6 +284,7 @@ def log(self, message: str, level: str = "info", *args, **kwargs) -> None:
self._critical_log_sent = True

@retry(
reraise=True,
wait=wait_exponential(max=10),
stop=stop_after_attempt(10),
retry_error_callback=capture_retry_error,
Expand Down
1 change: 1 addition & 0 deletions tests/test_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def test_send_event_4xx_error(mocked_trigger_logs):

def test_send_event_too_many_failures(mocked_trigger_logs):
trigger = DummyTrigger()
trigger._http_error_base_sleep = 0

mocked_trigger_logs.post(
"http://sekoia-playbooks/callback",
Expand Down