Skip to content

Commit 4571a68

Browse files
authored
Migrate models to Pydantic 2 (#1)
1 parent 28e2db3 commit 4571a68

File tree

10 files changed

+125
-127
lines changed

10 files changed

+125
-127
lines changed

python/Dockerfile

+25-33
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,28 @@
1-
FROM ubuntu:jammy as build-image
1+
FROM ubuntu:noble as python-builder
22

33
RUN apt-get update && \
4-
apt-get upgrade -y && \
5-
apt-get install --no-install-recommends python3.10-venv git -y && \
4+
apt-get install -y python3.12 python3.12-venv && \
65
rm -rf /var/lib/apt/lists/*
76

8-
# build into a venv we can copy across
9-
RUN python3 -m venv /opt/venv
10-
ENV PATH="/opt/venv/bin:$PATH"
7+
RUN python3.12 -m venv /venv && \
8+
/venv/bin/pip install -U pip setuptools
119

12-
COPY . /perftest
13-
RUN pip install -U pip setuptools
14-
RUN pip install --no-deps --requirement /perftest/requirements.txt
15-
RUN pip install -e /perftest
10+
COPY requirements.txt /app/requirements.txt
11+
RUN /venv/bin/pip install --no-deps --requirement /app/requirements.txt
1612

17-
#
18-
# Now the image we run with
19-
#
20-
FROM ubuntu:jammy as run-image
13+
# In order for template loading to work correctly, this has to be an editable mode install
14+
COPY . /app
15+
RUN /venv/bin/pip install --no-deps -e /app
2116

22-
RUN apt-get update && \
23-
apt-get upgrade -y && \
24-
apt-get install --no-install-recommends python3 tini ca-certificates -y && \
25-
rm -rf /var/lib/apt/lists/*
2617

27-
# Copy accross the venv
28-
COPY --from=build-image /opt/venv /opt/venv
29-
# Copy code to keep editable install working
30-
COPY . /perftest
31-
ENV PATH="/opt/venv/bin:$PATH"
18+
FROM ubuntu:noble
19+
20+
# Don't buffer stdout and stderr as it breaks realtime logging
21+
ENV PYTHONUNBUFFERED 1
22+
23+
# Make httpx use the system trust roots
24+
# By default, this means we use the roots baked into the image
25+
ENV SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
3226

3327
# Create the user that will be used to run the app
3428
ENV APP_UID 1001
@@ -44,19 +38,17 @@ RUN groupadd --gid $APP_GID $APP_GROUP && \
4438
--uid $APP_UID \
4539
$APP_USER
4640

47-
# Install tini, which we will use to marshal the processes
4841
RUN apt-get update && \
49-
apt-get install -y tini && \
42+
apt-get install -y \
43+
--no-install-recommends \
44+
--no-install-suggests \
45+
ca-certificates python3.12 tini \
46+
&& \
5047
rm -rf /var/lib/apt/lists/*
5148

52-
# Don't buffer stdout and stderr as it breaks realtime logging
53-
ENV PYTHONUNBUFFERED 1
54-
55-
# Make httpx use the system trust roots
56-
# By default, this means we use the CAs from the ca-certificates package
57-
ENV SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
49+
COPY --from=python-builder /venv /venv
50+
COPY --from=python-builder /app /app
5851

59-
# By default, run the operator using kopf
6052
USER $APP_UID
6153
ENTRYPOINT ["tini", "-g", "--"]
62-
CMD ["kopf", "run", "--module", "perftest.operator", "--all-namespaces"]
54+
CMD ["/venv/bin/kopf", "run", "--module", "perftest.operator", "--all-namespaces"]

python/perftest/models/v1alpha1/base.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import ipaddress
33
import typing as t
44

5-
from pydantic import Field, constr
5+
from pydantic import Field
66

77
from kube_custom_resource import CustomResource, schema
88

@@ -22,11 +22,11 @@ class ContainerResources(schema.BaseModel):
2222
"""
2323
Model for specifying container resources.
2424
"""
25-
requests: schema.Dict[str, t.Any] = Field(
25+
requests: schema.Dict[str, schema.Any] = Field(
2626
default_factory = dict,
2727
description = "The resource requests for the pod."
2828
)
29-
limits: schema.Dict[str, t.Any] = Field(
29+
limits: schema.Dict[str, schema.Any] = Field(
3030
default_factory = dict,
3131
description = "The resource limits for the pod."
3232
)
@@ -40,18 +40,18 @@ class BenchmarkSpec(schema.BaseModel):
4040
False,
4141
description = "Indicates whether to use host networking or not."
4242
)
43-
network_name: t.Optional[constr(min_length = 1)] = Field(
43+
network_name: schema.Optional[schema.constr(min_length = 1)] = Field(
4444
None,
4545
description = (
4646
"The name of a Multus network over which to run the benchmark. "
4747
"Only used when host networking is false."
4848
)
4949
)
50-
resources: t.Optional[ContainerResources] = Field(
50+
resources: schema.Optional[ContainerResources] = Field(
5151
None,
5252
description = "The resources to use for benchmark containers."
5353
)
54-
mtu: t.Optional[schema.conint(gt = 0)] = Field(
54+
mtu: schema.Optional[schema.conint(gt = 0)] = Field(
5555
None,
5656
description = (
5757
"The MTU to use for the benchmark. "
@@ -96,15 +96,15 @@ class ResourceRef(schema.BaseModel):
9696
"""
9797
Reference to a resource that is part of a benchmark.
9898
"""
99-
api_version: constr(min_length = 1) = Field(
99+
api_version: schema.constr(min_length = 1) = Field(
100100
...,
101101
description = "The API version of the resource."
102102
)
103-
kind: constr(min_length = 1) = Field(
103+
kind: schema.constr(min_length = 1) = Field(
104104
...,
105105
description = "The kind of the resource."
106106
)
107-
name: constr(min_length = 1) = Field(
107+
name: schema.constr(min_length = 1) = Field(
108108
...,
109109
description = "The name of the resource."
110110
)
@@ -118,7 +118,7 @@ class PodInfo(schema.BaseModel):
118118
...,
119119
description = "The IP of the pod."
120120
)
121-
node_name: constr(min_length = 1) = Field(
121+
node_name: schema.constr(min_length = 1) = Field(
122122
...,
123123
description = "The name of the node that the pod was scheduled on."
124124
)
@@ -147,19 +147,19 @@ class BenchmarkStatus(schema.BaseModel):
147147
BenchmarkPhase.UNKNOWN,
148148
description = "The phase of the benchmark."
149149
)
150-
priority_class_name: t.Optional[constr(min_length = 1)] = Field(
150+
priority_class_name: schema.Optional[schema.constr(min_length = 1)] = Field(
151151
None,
152152
description = "The name of the priority class for the benchmark."
153153
)
154154
managed_resources: t.List[ResourceRef] = Field(
155155
default_factory = list,
156156
description = "List of references to the managed resources for this benchmark."
157157
)
158-
started_at: t.Optional[datetime.datetime] = Field(
158+
started_at: schema.Optional[datetime.datetime] = Field(
159159
None,
160160
description = "The time at which the benchmark started."
161161
)
162-
finished_at: t.Optional[datetime.datetime] = Field(
162+
finished_at: schema.Optional[datetime.datetime] = Field(
163163
None,
164164
description = "The time at which the benchmark finished."
165165
)

python/perftest/models/v1alpha1/fio.py

+20-16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import itertools as it
21
import json
3-
import re
42
import typing as t
53

6-
from pydantic import Field, constr
4+
from pydantic import Field
75

86
from kube_custom_resource import schema
97

@@ -13,6 +11,7 @@
1311

1412
from . import base
1513

14+
1615
class FioRW(str, schema.Enum):
1716
"""
1817
Enumeration of supported Fio rw modes.
@@ -24,24 +23,27 @@ class FioRW(str, schema.Enum):
2423
RW_READWRITE = "rw,readwrite"
2524
RANDRW = "randrw"
2625

26+
2727
class FioDirect(int, schema.Enum):
2828
"""
2929
Enumeration of supported Fio direct Bools.
3030
"""
3131
TRUE = 1
3232
FALSE = 0
3333

34+
3435
class FioIOEngine(str, schema.Enum):
3536
"""
3637
Enumeration of supported Fio ioengines.
3738
"""
3839
LIBAIO = "libaio"
3940

41+
4042
class FioSpec(base.BenchmarkSpec):
4143
"""
4244
Defines the parameters for the Fio benchmark.
4345
"""
44-
image: constr(min_length = 1) = Field(
46+
image: schema.constr(min_length = 1) = Field(
4547
f"{settings.default_image_prefix}fio:{settings.default_image_tag}",
4648
description = "The image to use for the benchmark."
4749
)
@@ -53,7 +55,7 @@ class FioSpec(base.BenchmarkSpec):
5355
8765,
5456
description = "The port that the Fio sever listens on."
5557
)
56-
volume_claim_template: schema.Dict[str, t.Any] = Field(
58+
volume_claim_template: schema.Dict[str, schema.Any] = Field(
5759
default_factory = dict,
5860
description = "The template that describes the PVC to mount on workers."
5961
)
@@ -66,7 +68,7 @@ class FioSpec(base.BenchmarkSpec):
6668
FioRW.READ,
6769
description = "The value of the Fio rw config option."
6870
)
69-
bs: constr(regex = "\\d+(K|M|G|T|P)?") = Field(
71+
bs: schema.constr(pattern = "\\d+(K|M|G|T|P)?") = Field(
7072
"4M",
7173
description = "The value of the Fio bs config option."
7274
)
@@ -98,15 +100,15 @@ class FioSpec(base.BenchmarkSpec):
98100
FioIOEngine.LIBAIO,
99101
description = "The value of the Fio ioengine config option."
100102
)
101-
runtime: constr(regex = "\\d+(D|H|M|s|ms|us)?") = Field(
103+
runtime: schema.constr(pattern = "\\d+(D|H|M|s|ms|us)?") = Field(
102104
"30s",
103105
description = "The value of the Fio runtime config option."
104106
)
105107
num_jobs: schema.conint(gt = 0) = Field(
106108
1,
107109
description = "The value of the Fio numjobs config option."
108110
)
109-
size: constr(regex = "\\d+(K|M|G|T|P)?") = Field(
111+
size: schema.constr(pattern = "\\d+(K|M|G|T|P)?") = Field(
110112
"10G",
111113
description = "The value of the Fio size config option."
112114
)
@@ -152,44 +154,46 @@ class FioResult(schema.BaseModel):
152154
...,
153155
description = "The aggregate read latency standard deviation."
154156
)
155-
157+
158+
156159
class FioStatus(base.BenchmarkStatus):
157160
"""
158161
Represents the status of the Fio benchmark.
159162
"""
160-
result: t.Optional[FioResult] = Field(
163+
result: schema.Optional[FioResult] = Field(
161164
None,
162165
description = "The result of the benchmark."
163166
)
164-
read_bw_result: t.Optional[schema.IntOrString] = Field(
167+
read_bw_result: schema.Optional[schema.IntOrString] = Field(
165168
None,
166169
description = "The summary result for read bw, used for display."
167170
)
168-
write_bw_result: t.Optional[schema.IntOrString] = Field(
171+
write_bw_result: schema.Optional[schema.IntOrString] = Field(
169172
None,
170173
description = "The summary result for write bw, used for display."
171174
)
172-
read_iops_result: t.Optional[schema.confloat(ge = 0)] = Field(
175+
read_iops_result: schema.Optional[schema.confloat(ge = 0)] = Field(
173176
None,
174177
description = "The summary result for read IOPs, used for display."
175178
)
176-
write_iops_result: t.Optional[schema.confloat(ge = 0)] = Field(
179+
write_iops_result: schema.Optional[schema.confloat(ge = 0)] = Field(
177180
None,
178181
description = "The summary result for write IOPs, used for display."
179182
)
180-
master_pod: t.Optional[base.PodInfo] = Field(
183+
master_pod: schema.Optional[base.PodInfo] = Field(
181184
None,
182185
description = "Pod information for the Fio master pod."
183186
)
184187
worker_pods: schema.Dict[str, base.PodInfo] = Field(
185188
default_factory = dict,
186189
description = "Pod information for the worker pods, indexed by pod name."
187190
)
188-
client_log: t.Optional[constr(min_length = 1)] = Field(
191+
client_log: schema.Optional[schema.constr(min_length = 1)] = Field(
189192
None,
190193
description = "The raw pod log of the client pod."
191194
)
192195

196+
193197
class Fio(
194198
base.Benchmark,
195199
subresources = {"status": {}},

python/perftest/models/v1alpha1/iperf.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
import typing as t
44

5-
from pydantic import Field, constr
5+
from pydantic import Field
66

77
from kube_custom_resource import schema
88

@@ -17,7 +17,7 @@ class IPerfSpec(base.BenchmarkSpec):
1717
"""
1818
Defines the parameters for the iperf benchmark.
1919
"""
20-
image: constr(min_length = 1) = Field(
20+
image: schema.constr(min_length = 1) = Field(
2121
f"{settings.default_image_prefix}iperf:{settings.default_image_tag}",
2222
description = "The image to use for the benchmark."
2323
)
@@ -67,23 +67,23 @@ class IPerfStatus(base.BenchmarkStatus):
6767
"""
6868
Represents the status of the iperf benchmark.
6969
"""
70-
summary_result: t.Optional[schema.IntOrString] = Field(
70+
summary_result: schema.Optional[schema.IntOrString] = Field(
7171
None,
7272
description = "The summary result for the benchmark, used for display."
7373
)
74-
result: t.Optional[IPerfResult] = Field(
74+
result: schema.Optional[IPerfResult] = Field(
7575
None,
7676
description = "The complete result for the benchmark."
7777
)
78-
client_log: t.Optional[constr(min_length = 1)] = Field(
78+
client_log: schema.Optional[schema.constr(min_length = 1)] = Field(
7979
None,
8080
description = "The raw pod log of the client pod."
8181
)
82-
server_pod: t.Optional[base.PodInfo] = Field(
82+
server_pod: schema.Optional[base.PodInfo] = Field(
8383
None,
8484
description = "Pod information for the server pod."
8585
)
86-
client_pod: t.Optional[base.PodInfo] = Field(
86+
client_pod: schema.Optional[base.PodInfo] = Field(
8787
None,
8888
description = "Pod information for the client pod."
8989
)

python/perftest/models/v1alpha1/openfoam.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
import typing as t
33

4-
from pydantic import Field, constr
4+
from pydantic import Field
55

66
from kube_custom_resource import schema
77

@@ -49,7 +49,7 @@ class OpenFOAMSpec(base.BenchmarkSpec):
4949
"""
5050
Defines the parameters for the openFOAM benchmark.
5151
"""
52-
image: constr(min_length = 1) = Field(
52+
image: schema.constr(min_length = 1) = Field(
5353
f"{settings.default_image_prefix}openfoam:{settings.default_image_tag}",
5454
description = "The image to use for the benchmark."
5555
)
@@ -105,11 +105,11 @@ class OpenFOAMStatus(base.BenchmarkStatus):
105105
"""
106106
Represents the status of the iperf benchmark.
107107
"""
108-
result: t.Optional[OpenFOAMResult] = Field(
108+
result: schema.Optional[OpenFOAMResult] = Field(
109109
None,
110110
description = "The result of the benchmark."
111111
)
112-
master_pod: t.Optional[base.PodInfo] = Field(
112+
master_pod: schema.Optional[base.PodInfo] = Field(
113113
None,
114114
description = "Pod information for the MPI master pod."
115115
)

0 commit comments

Comments
 (0)