|
16 | 16 | # under the License.
|
17 | 17 | #
|
18 | 18 | # this module wraps utilities functions
|
19 |
| -import nuvolaris.kube as kube |
20 | 19 | import logging
|
21 |
| -import time, random, math, os |
22 |
| -import nuvolaris.config as cfg |
| 20 | +import math |
| 21 | +import random |
| 22 | +import time |
23 | 23 | import uuid
|
24 |
| -import nuvolaris.apihost_util as apihost_util |
25 | 24 | from base64 import b64decode, b64encode
|
| 25 | +from typing import List, Union |
| 26 | +from urllib.parse import urlparse |
| 27 | + |
| 28 | +import urllib3 |
| 29 | +from urllib3.exceptions import NewConnectionError, MaxRetryError, ProtocolError |
| 30 | + |
| 31 | +import nuvolaris.apihost_util as apihost_util |
| 32 | +import nuvolaris.config as cfg |
| 33 | +import nuvolaris.kube as kube |
| 34 | + |
26 | 35 |
|
27 | 36 | # Implements truncated exponential backoff from
|
28 | 37 | # https://cloud.google.com/storage/docs/retry-strategy#exponential-backoff
|
@@ -190,6 +199,74 @@ def wait_for_pod_ready(pod_name_jsonpath, timeout="600s", namespace="nuvolaris")
|
190 | 199 | except Exception as e:
|
191 | 200 | logging.error(e)
|
192 | 201 |
|
| 202 | + |
| 203 | +def status_matches(code: int, allowed: List[Union[int, str]]) -> bool: |
| 204 | + """Check if the status code matches any allowed pattern.""" |
| 205 | + for pattern in allowed: |
| 206 | + if isinstance(pattern, int) and code == pattern: |
| 207 | + return True |
| 208 | + if isinstance(pattern, str) and len(pattern) == 3 and pattern.endswith("XX"): |
| 209 | + if int(pattern[0]) == code // 100: |
| 210 | + return True |
| 211 | + return False |
| 212 | + |
| 213 | +def status_matches(code: int, allowed: List[Union[int, str]]) -> bool: |
| 214 | + """Check if the status code matches any allowed pattern.""" |
| 215 | + for pattern in allowed: |
| 216 | + if isinstance(pattern, int) and code == pattern: |
| 217 | + return True |
| 218 | + if isinstance(pattern, str) and len(pattern) == 3 and pattern.endswith("XX"): |
| 219 | + if int(pattern[0]) == code // 100: |
| 220 | + return True |
| 221 | + return False |
| 222 | + |
| 223 | +def wait_for_http(url: str, timeout: int = 60, up_statuses: List[Union[int, str]] = [200]): |
| 224 | + """Wait until an HTTP endpoint becomes available with an accepted status code. |
| 225 | +
|
| 226 | + Args: |
| 227 | + url (str): Full URL to check (e.g. http://milvus:9091/healthz) |
| 228 | + timeout (int): Total seconds to wait before giving up. |
| 229 | + up_statuses (List[Union[int, str]]): Status codes or patterns considered as 'UP'. |
| 230 | +
|
| 231 | + Raises: |
| 232 | + TimeoutError: If the endpoint doesn't respond with a valid status within the timeout. |
| 233 | + """ |
| 234 | + parsed = urlparse(url) |
| 235 | + scheme = parsed.scheme |
| 236 | + host = parsed.hostname |
| 237 | + port = parsed.port or (443 if scheme == "https" else 80) |
| 238 | + path = parsed.path or "/" |
| 239 | + |
| 240 | + if scheme == "https": |
| 241 | + conn = urllib3.connectionpool.HTTPSConnectionPool(host, port=port, |
| 242 | + timeout=urllib3.util.Timeout(connect=5.0, read=5.0), |
| 243 | + retries=False) |
| 244 | + else: |
| 245 | + conn = urllib3.connectionpool.HTTPConnectionPool(host, port=port, |
| 246 | + timeout=urllib3.util.Timeout(connect=5.0, read=5.0), |
| 247 | + retries=False) |
| 248 | + |
| 249 | + deadline = time.time() + timeout |
| 250 | + |
| 251 | + while time.time() < deadline: |
| 252 | + try: |
| 253 | + response = conn.request("GET", path) |
| 254 | + if status_matches(response.status, up_statuses): |
| 255 | + logging.info(f"Service is up: {url} (status {response.status})") |
| 256 | + return |
| 257 | + else: |
| 258 | + logging.warning(f"Service responded with {response.status}, not in {up_statuses}. Waiting...") |
| 259 | + except (NewConnectionError, MaxRetryError): |
| 260 | + logging.warning(f"Cannot connect to {url}, retrying...") |
| 261 | + except ProtocolError as e: |
| 262 | + if "Connection reset by peer" in str(e): |
| 263 | + logging.warning("Connection reset by peer. Sleeping 2 seconds...") |
| 264 | + time.sleep(2) |
| 265 | + continue |
| 266 | + else: |
| 267 | + logging.error(f"Protocol error: {e}") |
| 268 | + time.sleep(1) |
| 269 | + |
193 | 270 | # return mongodb configuration parameter with default valued if not configured
|
194 | 271 | def get_mongodb_config_data():
|
195 | 272 | data = {
|
|
0 commit comments