sovereign 0.23.0__py3-none-any.whl → 0.24.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sovereign might be problematic. Click here for more details.

sovereign/__init__.py CHANGED
@@ -92,6 +92,8 @@ cipher_suite = CipherContainer(
92
92
  template_context = TemplateContext(
93
93
  refresh_rate=config.template_context.refresh_rate,
94
94
  refresh_cron=config.template_context.refresh_cron,
95
+ refresh_num_retries=config.template_context.refresh_num_retries,
96
+ refresh_retry_interval_secs=config.template_context.refresh_retry_interval_secs,
95
97
  configured_context=config.template_context.context,
96
98
  poller=poller,
97
99
  encryption_suite=cipher_suite,
@@ -61,6 +61,8 @@ POLLER = SourcePoller(
61
61
  TEMPLATE_CONTEXT = TemplateContext(
62
62
  refresh_rate=CONFIG.template_context.refresh_rate,
63
63
  refresh_cron=CONFIG.template_context.refresh_cron,
64
+ refresh_num_retries=CONFIG.template_context.refresh_num_retries,
65
+ refresh_retry_interval_secs=CONFIG.template_context.refresh_retry_interval_secs,
64
66
  configured_context=CONFIG.template_context.context,
65
67
  poller=POLLER,
66
68
  encryption_suite=CIPHER_SUITE,
sovereign/context.py CHANGED
@@ -1,13 +1,30 @@
1
+ import asyncio
1
2
  import traceback
2
- from typing import Dict, Any, Generator, Iterable, NoReturn, Optional
3
3
  from copy import deepcopy
4
+ from typing import (
5
+ Any,
6
+ Awaitable,
7
+ Dict,
8
+ Generator,
9
+ Iterable,
10
+ NamedTuple,
11
+ NoReturn,
12
+ Optional,
13
+ )
14
+
4
15
  from fastapi import HTTPException
16
+ from structlog.stdlib import BoundLogger
17
+
5
18
  from sovereign.config_loader import Loadable
6
19
  from sovereign.schemas import DiscoveryRequest, XdsTemplate
7
20
  from sovereign.sources import SourcePoller
8
- from sovereign.utils.crypto import CipherSuite, CipherContainer
21
+ from sovereign.utils.crypto import CipherContainer, CipherSuite
9
22
  from sovereign.utils.timer import poll_forever, poll_forever_cron
10
- from structlog.stdlib import BoundLogger
23
+
24
+
25
+ class LoadContextResponse(NamedTuple):
26
+ context_name: str
27
+ context: Dict[str, Any]
11
28
 
12
29
 
13
30
  class TemplateContext:
@@ -15,9 +32,11 @@ class TemplateContext:
15
32
  self,
16
33
  refresh_rate: Optional[int],
17
34
  refresh_cron: Optional[str],
35
+ refresh_num_retries: int,
36
+ refresh_retry_interval_secs: int,
18
37
  configured_context: Dict[str, Loadable],
19
38
  poller: SourcePoller,
20
- encryption_suite: CipherContainer,
39
+ encryption_suite: Optional[CipherContainer],
21
40
  disabled_suite: CipherSuite,
22
41
  logger: BoundLogger,
23
42
  stats: Any,
@@ -25,13 +44,15 @@ class TemplateContext:
25
44
  self.poller = poller
26
45
  self.refresh_rate = refresh_rate
27
46
  self.refresh_cron = refresh_cron
47
+ self.refresh_num_retries = refresh_num_retries
48
+ self.refresh_retry_interval_secs = refresh_retry_interval_secs
28
49
  self.configured_context = configured_context
29
50
  self.crypto = encryption_suite
30
51
  self.disabled_suite = disabled_suite
31
52
  self.logger = logger
32
53
  self.stats = stats
33
54
  # initial load
34
- self.context = self.load_context_variables()
55
+ self.context = asyncio.run(self.load_context_variables())
35
56
 
36
57
  async def start_refresh_context(self) -> NoReturn:
37
58
  if self.refresh_cron is not None:
@@ -42,30 +63,66 @@ class TemplateContext:
42
63
  raise RuntimeError("Failed to start refresh_context, this should never happen")
43
64
 
44
65
  async def refresh_context(self) -> None:
45
- self.context = self.load_context_variables()
66
+ self.context = await self.load_context_variables()
46
67
 
47
- def load_context_variables(self) -> Dict[str, Any]:
48
- ret = dict()
49
- for k, v in self.configured_context.items():
68
+ async def _load_context(
69
+ self,
70
+ context_name: str,
71
+ context_config: Loadable | str,
72
+ refresh_num_retries: int,
73
+ refresh_retry_interval_secs: int,
74
+ ) -> LoadContextResponse:
75
+ retries_left = refresh_num_retries
76
+ context_response = {}
77
+
78
+ while True:
50
79
  try:
51
- if isinstance(v, Loadable):
52
- ret[k] = v.load()
53
- elif isinstance(v, str):
54
- ret[k] = Loadable.from_legacy_fmt(v).load()
80
+ if isinstance(context_config, Loadable):
81
+ context_response = context_config.load()
82
+ elif isinstance(context_config, str):
83
+ context_response = Loadable.from_legacy_fmt(context_config).load()
55
84
  self.stats.increment(
56
85
  "context.refresh.success",
57
- tags=[f"context:{k}"],
86
+ tags=[f"context:{context_name}"],
58
87
  )
59
- except Exception as e: # pylint: disable=broad-exception-caught
60
- tb = [line for line in traceback.format_exc().split("\n")]
61
- self.logger.error(str(e), traceback=tb)
62
- self.stats.increment(
63
- "context.refresh.error",
64
- tags=[f"context:{k}"],
88
+ return LoadContextResponse(context_name, context_response)
89
+ # pylint: disable=broad-except
90
+ except Exception as e:
91
+ retries_left -= 1
92
+ if retries_left < 0:
93
+ tb = [line for line in traceback.format_exc().split("\n")]
94
+ self.logger.error(str(e), traceback=tb)
95
+ self.stats.increment(
96
+ "context.refresh.error",
97
+ tags=[f"context:{context_name}"],
98
+ )
99
+ return LoadContextResponse(context_name, context_response)
100
+ else:
101
+ await asyncio.sleep(refresh_retry_interval_secs)
102
+
103
+ async def load_context_variables(self) -> Dict[str, Any]:
104
+ context_response: Dict[str, Any] = dict()
105
+
106
+ context_coroutines: list[Awaitable[LoadContextResponse]] = []
107
+ for context_name, context_config in self.configured_context.items():
108
+ context_coroutines.append(
109
+ self._load_context(
110
+ context_name,
111
+ context_config,
112
+ self.refresh_num_retries,
113
+ self.refresh_retry_interval_secs,
65
114
  )
66
- if "crypto" not in ret:
67
- ret["crypto"] = self.crypto
68
- return ret
115
+ )
116
+
117
+ context_results: list[LoadContextResponse] = await asyncio.gather(
118
+ *context_coroutines
119
+ )
120
+ for context_result in context_results:
121
+ context_response[context_result.context_name] = context_result.context
122
+
123
+ if "crypto" not in context_response and self.crypto:
124
+ context_response["crypto"] = self.crypto
125
+ return context_response
69
126
 
70
127
  def build_new_context_from_instances(self, node_value: str) -> Dict[str, Any]:
71
128
  matches = self.poller.match_node(node_value=node_value)
sovereign/schemas.py CHANGED
@@ -542,6 +542,8 @@ class ContextConfiguration(BaseSettings):
542
542
  refresh: bool = False
543
543
  refresh_rate: Optional[int] = None
544
544
  refresh_cron: Optional[str] = None
545
+ refresh_num_retries: int = 3
546
+ refresh_retry_interval_secs: int = 10
545
547
 
546
548
  @staticmethod
547
549
  def context_from_legacy(context: Dict[str, str]) -> Dict[str, Loadable]:
@@ -585,6 +587,10 @@ class ContextConfiguration(BaseSettings):
585
587
  "refresh": {"env": "SOVEREIGN_REFRESH_CONTEXT"},
586
588
  "refresh_rate": {"env": "SOVEREIGN_CONTEXT_REFRESH_RATE"},
587
589
  "refresh_cron": {"env": "SOVEREIGN_CONTEXT_REFRESH_CRON"},
590
+ "refresh_num_retries": {"env": "SOVEREIGN_CONTEXT_REFRESH_NUM_RETRIES"},
591
+ "refresh_retry_interval_secs": {
592
+ "env": "SOVEREIGN_CONTEXT_REFRESH_RETRY_INTERVAL_SECS"
593
+ },
588
594
  }
589
595
 
590
596
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sovereign
3
- Version: 0.23.0
3
+ Version: 0.24.1
4
4
  Summary: Envoy Proxy control-plane written in Python
5
5
  Home-page: https://pypi.org/project/sovereign/
6
6
  License: Apache-2.0
@@ -29,23 +29,23 @@ Provides-Extra: statsd
29
29
  Provides-Extra: ujson
30
30
  Requires-Dist: Jinja2 (>=3.1.2,<4.0.0)
31
31
  Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
32
- Requires-Dist: aiofiles (>=23.1.0,<24.0.0)
33
- Requires-Dist: boto3 (>=1.26.136,<2.0.0) ; extra == "boto"
32
+ Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
33
+ Requires-Dist: boto3 (>=1.28.62,<2.0.0) ; extra == "boto"
34
34
  Requires-Dist: cachelib (>=0.10.2,<0.11.0)
35
- Requires-Dist: cashews[redis] (>=6.1.0,<7.0.0) ; extra == "caching"
36
- Requires-Dist: croniter (>=1.3.14,<2.0.0)
37
- Requires-Dist: cryptography (>=41.0.2,<42.0.0)
38
- Requires-Dist: datadog (>=0.45.0,<0.46.0) ; extra == "statsd"
39
- Requires-Dist: fastapi (>=0.95.2,<0.96.0)
35
+ Requires-Dist: cashews[redis] (>=6.3.0,<7.0.0) ; extra == "caching"
36
+ Requires-Dist: croniter (>=1.4.1,<2.0.0)
37
+ Requires-Dist: cryptography (>=41.0.4,<42.0.0)
38
+ Requires-Dist: datadog (>=0.47.0,<0.48.0) ; extra == "statsd"
39
+ Requires-Dist: fastapi (>=0.99.1,<0.100.0)
40
40
  Requires-Dist: glom (>=23.3.0,<24.0.0)
41
- Requires-Dist: gunicorn (>=20.1.0,<21.0.0)
42
- Requires-Dist: httptools (>=0.5.0,<0.6.0) ; extra == "httptools"
43
- Requires-Dist: orjson (>=3.8.12,<4.0.0) ; extra == "orjson"
44
- Requires-Dist: requests (>=2.30.0,<3.0.0)
41
+ Requires-Dist: gunicorn (>=21.2.0,<22.0.0)
42
+ Requires-Dist: httptools (>=0.6.0,<0.7.0) ; extra == "httptools"
43
+ Requires-Dist: orjson (>=3.9.7,<4.0.0) ; extra == "orjson"
44
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
45
45
  Requires-Dist: sentry-sdk (>=1.23.1,<2.0.0) ; extra == "sentry"
46
46
  Requires-Dist: structlog (>=23.1.0,<24.0.0)
47
- Requires-Dist: ujson (>=5.7.0,<6.0.0) ; extra == "ujson"
48
- Requires-Dist: uvicorn (>=0.22.0,<0.23.0)
47
+ Requires-Dist: ujson (>=5.8.0,<6.0.0) ; extra == "ujson"
48
+ Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
49
49
  Requires-Dist: uvloop (>=0.17.0,<0.18.0)
50
50
  Project-URL: Documentation, https://vsyrakis.bitbucket.io/sovereign/docs/
51
51
  Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
@@ -1,9 +1,9 @@
1
- sovereign/__init__.py,sha256=VglKS_mlkPm4DipSmN9Mwbz9yHMq3f_mas7_GdUbkmc,3282
1
+ sovereign/__init__.py,sha256=IJscxrxp9cWH85Ee1x_5joMR0b-31l1-sXi6E6wgxOI,3436
2
2
  sovereign/app.py,sha256=uozeEQJjefBUV6dZfIooTBJgDhEE4bd2ozRxWVdVMK4,4085
3
3
  sovereign/config_loader.py,sha256=ZLKlyuvJ8Wqhsg6xZI-zp_5ABslB3Tc9zEdpds7-d7Q,6349
4
- sovereign/configuration.py,sha256=sB0iV9srKKn12RPRF3lQEmfZI-46maUaV-I6pyDan8g,2511
4
+ sovereign/configuration.py,sha256=rpZWNgn5ySNO1bdHlUix02yzo3wOefJaWkif5_4HjyE,2665
5
5
  sovereign/constants.py,sha256=qdWD1lTvkaW5JGF7TmZhfksQHlRAJFVqbG7v6JQA9k8,46
6
- sovereign/context.py,sha256=OXXMo7GkQW3ZF3d7oH7ACdbdnVyokZiKGMUp7EP7J5Y,4571
6
+ sovereign/context.py,sha256=zpkYSfGLhFNKQEGLGjcDnYaR-GF5TorlG646glttGBw,6427
7
7
  sovereign/discovery.py,sha256=TPecAoUDHx6SbS5hE2K73uEgDro0Uzz3qsvOQMpFtGI,5899
8
8
  sovereign/error_info.py,sha256=r2KXBYq9Fo7AI2pmIpATWFm0pykr2MqfrKH0WWW5Sfk,1488
9
9
  sovereign/logging/access_logger.py,sha256=JMMzQvi7doFJGA__YYqyasdfAT9W31Ycu_oZ2ovAMis,2565
@@ -16,7 +16,7 @@ sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
16
16
  sovereign/modifiers/lib.py,sha256=DbXsxrrjnFE4Y7rbwpeiM5tS5w5NBwSdYH58AtDTP0I,2884
17
17
  sovereign/modifiers/test.py,sha256=7_c2hWXn_sYJ6997N1_uSWtClOikcOzu1yRCY56-l-4,361
18
18
  sovereign/response_class.py,sha256=beMAFV-4L6DwyWzJzy71GkEW4gb7fzH1jd8-Tul13cU,427
19
- sovereign/schemas.py,sha256=H8K7cevb9shyiz7R1QoMIa367c6WJ0NLRH0ajOfT47M,27484
19
+ sovereign/schemas.py,sha256=ylUlHeaLzpsNJnOQCfdcVKt5kaNh0oo8nkDwRuchIWk,27775
20
20
  sovereign/server.py,sha256=z8Uz1UYIZix0S40Srk774WIMDN2jl2SozO8irib0wc4,1402
21
21
  sovereign/sources/__init__.py,sha256=g9hEpFk8j5i1ApHQpbc9giTyJW41Ppgsqv5P9zGxOJk,78
22
22
  sovereign/sources/file.py,sha256=A4UWoRU39v2Ex5Mtdl_uw53iMkslYylF4CiiwW7LOpk,689
@@ -48,8 +48,8 @@ sovereign/views/crypto.py,sha256=nxp6bkPU9GZw_zOk0fsJdz_XRQPXxPI6cXQDL9-cigU,204
48
48
  sovereign/views/discovery.py,sha256=DzFfG8fdFHKAZzmWZi9YzFP2PYLCf3tPlEAY3udNyNg,5980
49
49
  sovereign/views/healthchecks.py,sha256=_WkMunlrFpqGTLgtNtRr7gCsDCv5kiuYxCyTi-dMEKM,1357
50
50
  sovereign/views/interface.py,sha256=Y2fbR26cSF8eKQHfTbnv5WKEdgqaGNwys0lEGUTjXqw,7041
51
- sovereign-0.23.0.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
52
- sovereign-0.23.0.dist-info/METADATA,sha256=WSi5WtASCKtha-xDeZGxPadXdUu3fbktV228E9G1qHw,6290
53
- sovereign-0.23.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
54
- sovereign-0.23.0.dist-info/entry_points.txt,sha256=kOn848ucVbNvtsGABDuwzOHmNiOb0Ey8dV85Z3dLv3Y,222
55
- sovereign-0.23.0.dist-info/RECORD,,
51
+ sovereign-0.24.1.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
52
+ sovereign-0.24.1.dist-info/METADATA,sha256=e_YoN1lhq0XIZdyFsxN8t4AaQfa5S3FXjguJDFPzvcQ,6288
53
+ sovereign-0.24.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
54
+ sovereign-0.24.1.dist-info/entry_points.txt,sha256=kOn848ucVbNvtsGABDuwzOHmNiOb0Ey8dV85Z3dLv3Y,222
55
+ sovereign-0.24.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.1
2
+ Generator: poetry-core 1.7.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any