Skip to content

Commit 8e02c86

Browse files
Merge pull request #35 from DataKitchen/release/2.8.0
Release/2.8.0
2 parents a9e3635 + 49981e5 commit 8e02c86

File tree

22 files changed

+523
-92
lines changed

22 files changed

+523
-92
lines changed

cli/entry_points/init.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
from common.auth.keys.lib import hash_value
1010
from common.auth.keys.service_key import generate_key
1111
from common.entities import DB, Action, AuthProvider, Company, Organization, Project, User
12+
from common.kafka import (
13+
TOPIC_IDENTIFIED_EVENTS,
14+
TOPIC_UNIDENTIFIED_EVENTS,
15+
TOPIC_SCHEDULED_EVENTS,
16+
TOPIC_DEAD_LETTER_OFFICE,
17+
)
18+
from common.kafka.admin import create_topics
1219
from common.model import create_all_tables
1320

1421
USER_FIELDS = ["name", "email", "username", "password"]
@@ -89,10 +96,19 @@ def args(parser: ArgumentParser) -> None:
8996
action="store_true",
9097
help="Outputs the generated IDs in JSON format when successful",
9198
)
99+
parser.add_argument(
100+
"-k",
101+
"--topics",
102+
action="store_true",
103+
help="Create the Kafka Topics",
104+
)
92105

93106
def subcmd_entry_point(self) -> None:
94107
try:
108+
if not any([self.kwargs.get(arg) for arg in ("data", "demo", "tables", "topics")]):
109+
raise OperationAborted("Either --data or --demo or --tables or --topics has to be set.")
95110
self.initialize_database()
111+
self.create_kafka_topics()
96112
except OperationAborted as e:
97113
LOG.info("Operation #y<ABORTED>: %s", e)
98114
sys.exit(1)
@@ -102,10 +118,14 @@ def subcmd_entry_point(self) -> None:
102118
else:
103119
LOG.info("Operation #g<SUCCEEDED>")
104120

105-
def initialize_database(self) -> None:
106-
if not (self.kwargs.get("tables") or self.kwargs.get("data") or self.kwargs.get("demo")):
107-
raise OperationAborted("Either --data or --demo or --tables has to be set.")
121+
def create_kafka_topics(self) -> None:
122+
if self.kwargs.get("topics"):
123+
LOG.info("#c<Creating Kafka topics...>")
124+
create_topics(
125+
[TOPIC_IDENTIFIED_EVENTS, TOPIC_UNIDENTIFIED_EVENTS, TOPIC_SCHEDULED_EVENTS, TOPIC_DEAD_LETTER_OFFICE]
126+
)
108127

128+
def initialize_database(self) -> None:
109129
if self.kwargs.get("tables"):
110130
if self.kwargs.get("force"):
111131
raise OperationAborted("The --force option can not be used when creating the tables.")

cli/tests/entry_points/test_init.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ def create_tables_mock():
1515
yield create_tables_mock
1616

1717

18+
@pytest.fixture
19+
def create_topics_mock():
20+
with patch("cli.entry_points.init.create_topics") as mock:
21+
yield mock
22+
23+
1824
@pytest.fixture
1925
def init_entry_point(test_db, create_tables_mock):
2026
yield Initialize(default=True)
@@ -124,6 +130,16 @@ def test_init_not_empty(arg, init_entry_point, create_tables_mock, mock_user_inp
124130
assert ServiceAccountKey.select().count() == demo_data_count
125131

126132

133+
@pytest.mark.integration
134+
def test_init_topics(init_entry_point, create_topics_mock):
135+
with (
136+
patch.dict(init_entry_point.kwargs, {"topics": True}),
137+
):
138+
init_entry_point.subcmd_entry_point()
139+
140+
create_topics_mock.assert_called_once()
141+
142+
127143
@pytest.mark.integration
128144
def test_init_error(init_entry_point, mock_user_input):
129145
DB.create_tables(ALL_MODELS)

common/kafka/admin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import logging
2+
3+
from confluent_kafka.admin import AdminClient, NewTopic
4+
from confluent_kafka import KafkaException, KafkaError
5+
6+
from common.kafka.topic import Topic
7+
from conf import settings
8+
9+
LOG = logging.getLogger(__name__)
10+
11+
12+
def create_topics(topics: list[Topic], num_partitions: int = 1, replication_factor: int = 1) -> None:
13+
client = AdminClient(settings.KAFKA_CONNECTION_PARAMS)
14+
new_topics = [
15+
NewTopic(topic.name, num_partitions=num_partitions, replication_factor=replication_factor) for topic in topics
16+
]
17+
failed_topics = []
18+
for topic_name, future in client.create_topics(new_topics).items():
19+
try:
20+
future.result()
21+
except KafkaException as e:
22+
if e.args[0] != KafkaError.TOPIC_ALREADY_EXISTS:
23+
failed_topics.append(topic_name)
24+
except Exception:
25+
LOG.exception("Error creating %s Kafka topic", topic_name)
26+
failed_topics.append(topic_name)
27+
28+
if failed_topics:
29+
raise Exception(f"Creating the topics {", ".join(failed_topics)} failed.")

common/kubernetes/readiness_probe.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
import time
1515
from argparse import ArgumentParser, Namespace
1616
from contextlib import contextmanager
17+
from pathlib import Path
1718
from typing import NoReturn
1819
from collections.abc import Generator
1920
from collections.abc import Callable
2021

2122
LOG = logging.getLogger(__name__)
2223

23-
FILE_NAME = "observability_readyz"
24+
FILE_NAME = "{service}_readyz"
2425
"""Name of the file that will be created into the temp dir to mark the service as ready."""
2526

2627
POLLING_SLEEP_SECONDS = 0.1
@@ -34,19 +35,26 @@ class NotReadyException(Exception):
3435
"""Raised when the service shouldn't be marked as ready."""
3536

3637

37-
def _get_mark_file_path() -> str:
38-
return os.path.join(tempfile.gettempdir(), FILE_NAME)
38+
def _get_mark_file_path() -> Path:
39+
file_name = FILE_NAME.format(service=os.getenv("SUPERVISOR_PROCESS_NAME", "observability"))
40+
return Path(tempfile.gettempdir()) / file_name
3941

4042

4143
def set_ready() -> None:
4244
"""Mark the service as ready by adding a PID file to the temp dir."""
43-
with open(_get_mark_file_path(), "w") as mark_file:
44-
mark_file.write(str(os.getpid()))
45+
mark_file_path = _get_mark_file_path()
46+
mark_file_path.parent.mkdir(parents=True, exist_ok=True)
47+
mark_file_path.write_text(str(os.getpid()))
4548

4649

4750
def is_ready() -> bool:
4851
"""Checks the existence of the readiness mark file."""
49-
return os.path.exists(_get_mark_file_path())
52+
try:
53+
os.kill(int(_get_mark_file_path().read_text().strip()), 0)
54+
except (FileNotFoundError, ProcessLookupError, ValueError):
55+
return False
56+
else:
57+
return True
5058

5159

5260
@contextmanager
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from unittest.mock import patch, Mock
2+
3+
import pytest
4+
from confluent_kafka.cimpl import NewTopic, KafkaException, KafkaError
5+
6+
from common.kafka.admin import create_topics
7+
from common.kafka.topic import Topic
8+
9+
10+
@pytest.fixture
11+
def client_class_mock():
12+
with patch("common.kafka.admin.AdminClient") as mock:
13+
yield mock
14+
15+
16+
@pytest.fixture
17+
def client_mock(client_class_mock):
18+
mock = Mock()
19+
client_class_mock.return_value = mock
20+
yield mock
21+
22+
23+
@pytest.fixture
24+
def create_topics_mock(client_mock):
25+
yield client_mock.create_topics
26+
27+
28+
@pytest.fixture
29+
def result_mock(create_topics_mock):
30+
future_mock = Mock()
31+
result_mock = future_mock.result
32+
create_topics_mock.return_value = {"TEST_TOPIC": future_mock}
33+
yield result_mock
34+
35+
36+
@pytest.fixture
37+
def topic():
38+
return Topic(name="TEST_TOPIC")
39+
40+
41+
@pytest.mark.unit
42+
@pytest.mark.parametrize("result_side_effect", (lambda: None, KafkaException(KafkaError.TOPIC_ALREADY_EXISTS)))
43+
def test_create_topics(result_side_effect, client_class_mock, create_topics_mock, result_mock, topic):
44+
result_mock.side_effect = result_side_effect
45+
46+
create_topics([topic], num_partitions=5, replication_factor=5)
47+
48+
client_class_mock.assert_called_once()
49+
create_topics_mock.assert_called_once_with([NewTopic("TEST_TOPIC", num_partitions=5, replication_factor=5)])
50+
result_mock.assert_called_once()
51+
52+
53+
@pytest.mark.unit
54+
def test_create_topics_fail(client_class_mock, create_topics_mock, result_mock, topic):
55+
result_mock.side_effect = KafkaException(KafkaError.MEMBER_ID_REQUIRED)
56+
57+
with pytest.raises(Exception):
58+
create_topics([topic], num_partitions=5, replication_factor=5)
59+
60+
client_class_mock.assert_called_once()
61+
create_topics_mock.assert_called_once_with([NewTopic("TEST_TOPIC", num_partitions=5, replication_factor=5)])
62+
result_mock.assert_called_once()

common/tests/unit/kubernetes/test_readiness_probe.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import os
12
from itertools import count
3+
from pathlib import Path
24
from unittest.mock import patch
35

46
import pytest
@@ -18,8 +20,16 @@ def set_ready_mock():
1820

1921
@pytest.mark.unit
2022
@patch.object(readiness_probe.tempfile, "gettempdir", return_value="/xpto")
21-
def test_get_filename(temp_dir_mock):
22-
assert readiness_probe._get_mark_file_path() == "/xpto/observability_readyz"
23+
@pytest.mark.parametrize(
24+
"env, expected",
25+
(
26+
({}, Path("/xpto/observability_readyz")),
27+
({"SUPERVISOR_PROCESS_NAME": "a_service"}, Path("/xpto/a_service_readyz")),
28+
),
29+
)
30+
def test_get_filename(temp_dir_mock, env, expected):
31+
with patch.dict(os.environ, env):
32+
assert readiness_probe._get_mark_file_path() == expected
2333
temp_dir_mock.assert_called_once()
2434

2535

conf/defaults.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
RULE_REFRESH_SECONDS: int = 30
22
"""Number of seconds to cache rules in rules engine"""
33

4-
54
MIGRATIONS_SRC_PATH = "/dk/lib/migrations"
65
"""Yoyo migrations source folder."""
76

deploy/charts/observability-app/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: dataops-observability-app
33
type: application
44
appVersion: "2.x.x"
5-
version: "2.3.1"
5+
version: "2.4.0"
66

77
description: DataOps Observability
88
home: https://datakitchen.io

deploy/charts/observability-app/templates/observability-ui.yaml

Lines changed: 6 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,6 @@ spec:
3030
serviceAccountName: {{ include "observability.serviceAccountName" . }}
3131
securityContext:
3232
{{- toYaml .Values.observability_ui.podSecurityContext | nindent 8 }}
33-
{{- if or .Values.observability_ui.environmentJson .Values.observability_ui.manifestJson }}
34-
volumes:
35-
- name: {{ include "observability.observability_ui.name" . }}-configmap-volume
36-
configMap:
37-
name: {{ include "observability.observability_ui.name" . }}-configmap
38-
items:
39-
{{- if .Values.observability_ui.environmentJson }}
40-
- key: "environment.json"
41-
path: "environment.json"
42-
{{- end }}
43-
{{- if .Values.observability_ui.manifestJson }}
44-
- key: "module-federation.manifest.json"
45-
path: "module-federation.manifest.json"
46-
{{- end }}
47-
{{- end }}
4833
containers:
4934
- name: {{ include "observability.observability_ui.name" . }}
5035
securityContext:
@@ -58,31 +43,21 @@ spec:
5843
resources:
5944
{{- toYaml .Values.observability_ui.resources | nindent 12 }}
6045
env:
61-
{{- if .Values.observability_api.hostname }}
46+
- name: OBSERVABILITY_API_BASE_URL
47+
value: {{ .Values.observability_ui.api_base_url | quote }}
48+
- name: OBSERVABILITY_AUTH_METHOD
49+
value: {{ .Values.observability_ui.auth_method | quote }}
50+
{{- if .Values.observability_ui.hostname }}
6251
- name: OBSERVABILITY_API_HOSTNAME
6352
value: {{ tpl .Values.observability_api.hostname . | quote }}
6453
{{- end }}
6554
{{- if not (quote .Values.observability_ui.csp_extra | empty) }}
6655
- name: OBSERVABILITY_CSP_EXTRA
6756
value: {{ tpl .Values.observability_ui.csp_extra . | quote }}
6857
{{- end }}
69-
{{- if or .Values.observability_ui.environmentJson .Values.observability_ui.manifestJson .Values.extraVolumeMounts }}
58+
{{- with .Values.extraVolumeMounts }}
7059
volumeMounts:
71-
{{- if .Values.observability_ui.environmentJson }}
72-
- mountPath: /observability_ui/shell/environments/environment.json
73-
name: {{ include "observability.observability_ui.name" . }}-configmap-volume
74-
readOnly: true
75-
subPath: environment.json
76-
{{- end }}
77-
{{- if .Values.observability_ui.manifestJson }}
78-
- mountPath: /observability_ui/shell/assets/module-federation.manifest.json
79-
name: {{ include "observability.observability_ui.name" . }}-configmap-volume
80-
readOnly: true
81-
subPath: module-federation.manifest.json
82-
{{- end }}
83-
{{- with .Values.extraVolumeMounts }}
8460
{{ toYaml . | nindent 12 }}
85-
{{- end }}
8661
{{- end }}
8762
{{- with .Values.extraVolumes }}
8863
volumes:
@@ -122,21 +97,4 @@ spec:
12297
{{- end }}
12398
selector:
12499
{{- include "observability.observability_ui.selectorLabels" . | nindent 4 }}
125-
{{- if or .Values.observability_ui.environmentJson .Values.observability_ui.manifestJson }}
126-
---
127-
apiVersion: v1
128-
kind: ConfigMap
129-
metadata:
130-
name: {{ include "observability.observability_ui.name" . }}-configmap
131-
labels:
132-
{{- include "observability.labels" . | nindent 4 }}
133-
{{- include "observability.observability_ui.selectorLabels" . | nindent 4 }}
134-
data:
135-
{{- if .Values.observability_ui.environmentJson }}
136-
environment.json: {{ .Values.observability_ui.environmentJson | toPrettyJson | quote }}
137-
{{- end }}
138-
{{- if .Values.observability_ui.manifestJson }}
139-
module-federation.manifest.json: {{ .Values.observability_ui.manifestJson | toPrettyJson | quote }}
140-
{{- end }}
141-
{{- end }}
142100
{{- end }}

deploy/charts/observability-app/values.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ observability_ui:
2424
type: NodePort
2525
port: 8082
2626
nodePort: 8082
27+
api_base_url: "/api"
28+
auth_method: "sso"
2729
podSecurityContext:
2830
sysctls:
2931
- name: net.core.somaxconn
@@ -32,7 +34,6 @@ observability_ui:
3234
nodeSelector: { }
3335
tolerations: [ ]
3436
affinity: { }
35-
environmentJson: {"apiBaseUrl": "/api"}
3637

3738
observability_api:
3839
enable: true

0 commit comments

Comments
 (0)