|
3 | 3 | # Licensed under a 3-clause BSD style license (see LICENSE) |
4 | 4 | import json |
5 | 5 | import os |
6 | | -import time |
7 | | -from contextlib import ExitStack |
8 | 6 |
|
9 | 7 | import pytest |
10 | | -import requests |
11 | 8 |
|
12 | 9 | from datadog_checks.dev import get_here |
| 10 | +from datadog_checks.dev._env import get_state, save_state |
13 | 11 | from datadog_checks.dev.kind import kind_run |
14 | | -from datadog_checks.dev.kube_port_forward import port_forward |
15 | 12 | from datadog_checks.dev.subprocess import run_command |
16 | | -from datadog_checks.weaviate.check import DEFAULT_LIVENESS_ENDPOINT |
17 | 13 |
|
18 | 14 | from .common import BATCH_OBJECTS, USE_AUTH |
19 | 15 |
|
20 | 16 | HERE = get_here() |
21 | 17 | opj = os.path.join |
22 | 18 |
|
| 19 | +NAMESPACE = 'weaviate' |
| 20 | +# No Service targets the StatefulSet's metrics port, only the API port (see weaviate_install.yaml / |
| 21 | +# weaviate_auth.yaml), so the API endpoint uses Service DNS while metrics fall back to the pod IP. |
| 22 | +WEAVIATE_API_ENDPOINT = f'http://weaviate.{NAMESPACE}.svc.cluster.local:80' |
| 23 | +POD_IP_STATE = 'weaviate_pod_ip' |
| 24 | + |
23 | 25 |
|
24 | 26 | def setup_weaviate(): |
25 | | - run_command(['kubectl', 'create', 'ns', 'weaviate']) |
| 27 | + run_command(['kubectl', 'create', 'ns', 'weaviate'], check=True) |
26 | 28 |
|
27 | 29 | if USE_AUTH: |
28 | | - run_command(['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_auth.yaml'), '-n', 'weaviate']) |
| 30 | + run_command(['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_auth.yaml'), '-n', 'weaviate'], check=True) |
29 | 31 | else: |
30 | | - run_command(['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_install.yaml'), '-n', 'weaviate']) |
| 32 | + run_command( |
| 33 | + ['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_install.yaml'), '-n', 'weaviate'], check=True |
| 34 | + ) |
31 | 35 |
|
32 | 36 | # Tries to ensure that the Kubernetes resources are deployed and ready before we do anything else |
33 | | - run_command(['kubectl', 'rollout', 'status', 'statefulset/weaviate', '-n', 'weaviate']) |
34 | | - run_command(['kubectl', 'wait', 'pods', '--all', '-n', 'weaviate', '--for=condition=Ready', '--timeout=600s']) |
| 37 | + run_command(['kubectl', 'rollout', 'status', 'statefulset/weaviate', '-n', 'weaviate'], check=True) |
| 38 | + run_command( |
| 39 | + ['kubectl', 'wait', 'pods', '--all', '-n', 'weaviate', '--for=condition=Ready', '--timeout=600s'], |
| 40 | + check=True, |
| 41 | + ) |
| 42 | + |
| 43 | + # `setup_weaviate` only runs once, on the initial `ddev env start`. Later invocations of the |
| 44 | + # `dd_environment` fixture (e.g. during `ddev env stop`) run in a fresh process after the cluster |
| 45 | + # is torn down, so the pod IP is cached here via `save_state`/`get_state` rather than looked up live. |
| 46 | + save_state(POD_IP_STATE, get_weaviate_pod_ip()) |
| 47 | + |
| 48 | + make_weaviate_request() |
| 49 | + |
| 50 | + |
| 51 | +def get_weaviate_pod_ip() -> str: |
| 52 | + result = run_command( |
| 53 | + ['kubectl', 'get', 'pods', '--namespace', NAMESPACE, '--selector', 'app=weaviate', '--output', 'json'], |
| 54 | + capture='out', |
| 55 | + check=True, |
| 56 | + ) |
| 57 | + pods = json.loads(result.stdout)['items'] |
| 58 | + if len(pods) != 1 or not pods[0].get('status', {}).get('podIP'): |
| 59 | + raise RuntimeError(f'Expected one ready Weaviate pod, found {len(pods)}') |
| 60 | + return pods[0]['status']['podIP'] |
| 61 | + |
| 62 | + |
| 63 | +def make_weaviate_request(): |
| 64 | + # This helps seed some dummy data in to Weaviate to make some metrics available. Run from a |
| 65 | + # temporary pod since the host cannot reach the cluster directly. |
| 66 | + weaviate_batch_endpoint = f'{WEAVIATE_API_ENDPOINT}/v1/batch/objects' |
| 67 | + |
| 68 | + command = [ |
| 69 | + 'kubectl', |
| 70 | + 'run', |
| 71 | + 'weaviate-seed-data', |
| 72 | + '--namespace', |
| 73 | + NAMESPACE, |
| 74 | + '--image=curlimages/curl', |
| 75 | + '--restart=Never', |
| 76 | + '--attach', |
| 77 | + '--rm', |
| 78 | + '--quiet', |
| 79 | + '--', |
| 80 | + 'curl', |
| 81 | + '-sf', |
| 82 | + '-X', |
| 83 | + 'POST', |
| 84 | + weaviate_batch_endpoint, |
| 85 | + '-H', |
| 86 | + 'Content-Type: application/json', |
| 87 | + ] |
| 88 | + if USE_AUTH: |
| 89 | + command.extend(['-H', 'Authorization: Bearer test123']) |
| 90 | + command.extend(['-d', json.dumps(BATCH_OBJECTS)]) |
| 91 | + |
| 92 | + run_command(command, capture='both', check=True) |
35 | 93 |
|
36 | 94 |
|
37 | 95 | @pytest.fixture(scope='session') |
38 | 96 | def dd_environment(): |
39 | | - with kind_run(conditions=[setup_weaviate]) as kubeconfig, ExitStack() as stack: |
40 | | - weaviate_host, weaviate_port = stack.enter_context( |
41 | | - port_forward(kubeconfig, 'weaviate', 2112, 'statefulset', 'weaviate') |
42 | | - ) |
43 | | - weaviate_host, weaviate_api_port = stack.enter_context( |
44 | | - port_forward(kubeconfig, 'weaviate', 8080, 'statefulset', 'weaviate') |
45 | | - ) |
| 97 | + with kind_run(conditions=[setup_weaviate]) as kubeconfig: |
| 98 | + weaviate_metrics_port = 2112 |
46 | 99 |
|
47 | 100 | instance = { |
48 | | - 'openmetrics_endpoint': f'http://{weaviate_host}:{weaviate_port}/metrics', |
49 | | - 'weaviate_api_endpoint': f'http://{weaviate_host}:{weaviate_api_port}', |
| 101 | + 'openmetrics_endpoint': f'http://{get_state(POD_IP_STATE)}:{weaviate_metrics_port}/metrics', |
| 102 | + 'weaviate_api_endpoint': WEAVIATE_API_ENDPOINT, |
50 | 103 | } |
51 | 104 | if USE_AUTH: |
52 | 105 | instance['headers'] = {'Authorization': 'Bearer test123'} |
53 | 106 |
|
54 | | - make_weaviate_request(instance) |
55 | | - yield instance |
56 | | - |
57 | | - |
58 | | -def make_weaviate_request(instance): |
59 | | - # This helps seed some dummy data in to Weaviate to make some metrics available |
60 | | - weaviate_api_endpoint = instance.get('weaviate_api_endpoint') |
61 | | - weaviate_batch_endpoint = f'{weaviate_api_endpoint}/v1/batch/objects' |
62 | | - headers = {'content-type': 'application/json'} |
63 | | - |
64 | | - if instance.get('headers'): |
65 | | - headers.update(instance['headers']) |
66 | | - |
67 | | - if ready_check(weaviate_api_endpoint, 300): |
68 | | - requests.post(weaviate_batch_endpoint, headers=headers, data=json.dumps(BATCH_OBJECTS)) |
69 | | - |
70 | | - |
71 | | -def ready_check(endpoint, timeout=300): |
72 | | - # Sometimes the API endpoint isn't ready when the cluster is ready. This will try to ensure the |
73 | | - # API is ready for requests before we seed some dummy data. |
74 | | - stop_time = time.time() + timeout |
75 | | - endpoint = f'{endpoint}{DEFAULT_LIVENESS_ENDPOINT}' |
76 | | - while time.time() < stop_time: |
77 | | - try: |
78 | | - response = requests.get(endpoint, timeout=5) |
79 | | - if response.ok: |
80 | | - return True |
81 | | - except requests.RequestException as e: |
82 | | - print(f'Request failed: {e}') |
83 | | - |
84 | | - time.sleep(1) |
| 107 | + metadata = {'agent_type': 'kubernetes', 'kubernetes': {'kubeconfig': kubeconfig}} |
85 | 108 |
|
86 | | - return False |
| 109 | + yield instance, metadata |
0 commit comments