16
16
17
17
import logging
18
18
import subprocess
19
- from abc import ABC , abstractmethod
20
19
from typing import Any
21
20
22
- import ops
21
+ from charms . data_platform_libs . v0 . data_models import TypedCharmBase
23
22
from charms .grafana_agent .v0 .cos_agent import COSAgentProvider
24
23
from ops .charm import CharmEvents
25
24
from ops .framework import EventBase , EventSource
26
25
from ops .model import BlockedStatus
27
26
28
27
from benchmark .core .models import DPBenchmarkLifecycleState
29
28
from benchmark .core .pebble_workload_base import DPBenchmarkPebbleWorkloadBase
29
+ from benchmark .core .structured_config import BenchmarkCharmConfig
30
30
from benchmark .core .systemd_workload_base import DPBenchmarkSystemdWorkloadBase
31
31
from benchmark .core .workload_base import WorkloadBase
32
+ from benchmark .events .actions import ActionsHandler
32
33
from benchmark .events .db import DatabaseRelationHandler
33
34
from benchmark .events .peer import PeerRelationHandler
34
35
from benchmark .literals import (
35
36
COS_AGENT_RELATION ,
36
37
METRICS_PORT ,
37
38
PEER_RELATION ,
38
- DPBenchmarkLifecycleTransition ,
39
39
DPBenchmarkMissingOptionsError ,
40
40
)
41
41
from benchmark .managers .config import ConfigManager
@@ -70,34 +70,22 @@ def workload_build(workload_params_template: str) -> WorkloadBase:
70
70
return DPBenchmarkSystemdWorkloadBase (workload_params_template )
71
71
72
72
73
- class DPBenchmarkCharmBase (ops . CharmBase , ABC ):
73
+ class DPBenchmarkCharmBase (TypedCharmBase [ BenchmarkCharmConfig ] ):
74
74
"""The base benchmark class."""
75
75
76
- on = DPBenchmarkEvents () # pyright: ignore [reportGeneralTypeIssues ]
76
+ on = DPBenchmarkEvents () # pyright: ignore [reportAssignmentType ]
77
77
78
78
RESOURCE_DEB_NAME = "benchmark-deb"
79
79
workload_params_template = ""
80
80
81
+ config_type = BenchmarkCharmConfig
82
+
81
83
def __init__ (self , * args , db_relation_name : str , workload : WorkloadBase | None = None ):
82
84
super ().__init__ (* args )
83
85
self .framework .observe (self .on .install , self ._on_install )
84
86
self .framework .observe (self .on .config_changed , self ._on_config_changed )
85
87
self .framework .observe (self .on .update_status , self ._on_update_status )
86
88
87
- self .framework .observe (self .on .prepare_action , self .on_prepare_action )
88
- self .framework .observe (self .on .run_action , self .on_run_action )
89
- self .framework .observe (self .on .stop_action , self .on_stop_action )
90
- self .framework .observe (self .on .cleanup_action , self .on_clean_action )
91
-
92
- self .framework .observe (
93
- self .on .check_upload ,
94
- self ._on_check_upload ,
95
- )
96
- self .framework .observe (
97
- self .on .check_collect ,
98
- self ._on_check_collect ,
99
- )
100
-
101
89
self .database = DatabaseRelationHandler (self , db_relation_name )
102
90
self .peers = PeerRelationHandler (self , PEER_RELATION )
103
91
self .framework .observe (self .database .on .db_config_update , self ._on_config_changed )
@@ -119,8 +107,8 @@ def __init__(self, *args, db_relation_name: str, workload: WorkloadBase | None =
119
107
120
108
self .config_manager = ConfigManager (
121
109
workload = self .workload ,
122
- database = self .database .state ,
123
- peer = self .peers .peers (),
110
+ database_state = self .database .state ,
111
+ peers = self .peers .peers (),
124
112
config = self .config ,
125
113
labels = self .labels ,
126
114
)
@@ -129,11 +117,7 @@ def __init__(self, *args, db_relation_name: str, workload: WorkloadBase | None =
129
117
self .peers .this_unit (),
130
118
self .config_manager ,
131
119
)
132
-
133
- @abstractmethod
134
- def supported_workloads (self ) -> list [str ]:
135
- """List of supported workloads."""
136
- ...
120
+ self .actions = ActionsHandler (self )
137
121
138
122
###########################################################################
139
123
#
@@ -146,28 +130,6 @@ def _on_install(self, event: EventBase) -> None:
146
130
self .workload .install ()
147
131
self .peers .state .lifecycle = DPBenchmarkLifecycleState .UNSET
148
132
149
- def _on_check_collect (self , event : EventBase ) -> None :
150
- """Check if the upload is finished."""
151
- if self .config_manager .is_collecting ():
152
- # Nothing to do, upload is still in progress
153
- event .defer ()
154
- return
155
-
156
- if self .unit .is_leader ():
157
- self .peers .state .set (DPBenchmarkLifecycleState .UPLOADING )
158
- # Raise we are running an upload and we will check the status later
159
- self .on .check_upload .emit ()
160
- return
161
- self .peers .state .set (DPBenchmarkLifecycleState .FINISHED )
162
-
163
- def _on_check_upload (self , event : EventBase ) -> None :
164
- """Check if the upload is finished."""
165
- if self .config_manager .is_uploading ():
166
- # Nothing to do, upload is still in progress
167
- event .defer ()
168
- return
169
- self .peers .state .lifecycle = DPBenchmarkLifecycleState .FINISHED
170
-
171
133
def _on_update_status (self , event : EventBase | None = None ) -> None :
172
134
"""Set status for the operator and finishes the service.
173
135
@@ -176,34 +138,20 @@ def _on_update_status(self, event: EventBase | None = None) -> None:
176
138
benchmark service and the benchmark status.
177
139
"""
178
140
try :
179
- status = self .database .state .get ()
141
+ status = self .database .state .model ()
180
142
except DPBenchmarkMissingOptionsError as e :
181
143
self .unit .status = BlockedStatus (str (e ))
182
144
return
183
145
if not status :
184
146
self .unit .status = BlockedStatus ("No database relation available" )
185
147
return
186
148
187
- # We need to narrow the options of workload_name to the supported ones
188
- if self .config .get ("workload_name" ) not in self .supported_workloads ():
189
- self .unit .status = BlockedStatus (
190
- f"Unsupported workload: { self .config .get ('workload_name' )} "
191
- )
192
- return
193
-
194
149
# Now, let's check if we need to update our lifecycle position
195
- self ._update_state ()
150
+ self .update_state ()
196
151
self .unit .status = self .lifecycle .status
197
152
198
153
def _on_config_changed (self , event : EventBase ) -> None :
199
154
"""Config changed event."""
200
- # We need to narrow the options of workload_name to the supported ones
201
- if self .config .get ("workload_name" ) not in self .supported_workloads ():
202
- self .unit .status = BlockedStatus (
203
- f"Unsupported workload: { self .config .get ('workload_name' )} "
204
- )
205
- return
206
-
207
155
if not self .config_manager .is_prepared ():
208
156
# nothing to do: set the status and leave
209
157
self ._on_update_status ()
@@ -228,88 +176,6 @@ def scrape_config(self) -> list[dict[str, Any]]:
228
176
}
229
177
]
230
178
231
- ###########################################################################
232
- #
233
- # Action and Lifecycle Handlers
234
- #
235
- ###########################################################################
236
-
237
- def _preflight_checks (self ) -> bool :
238
- """Check if we have the necessary relations."""
239
- if len (self .peers .units ()) > 0 and not bool (self .peers .state .get ()):
240
- return False
241
- try :
242
- return bool (self .database .state .get ())
243
- except DPBenchmarkMissingOptionsError :
244
- return False
245
-
246
- def on_prepare_action (self , event : EventBase ) -> None :
247
- """Process the prepare action."""
248
- if not self ._preflight_checks ():
249
- event .fail ("Missing DB or S3 relations" )
250
- return
251
-
252
- if not (state := self .lifecycle .next (DPBenchmarkLifecycleTransition .PREPARE )):
253
- event .fail ("Failed to prepare the benchmark: already done" )
254
- return
255
-
256
- if state != DPBenchmarkLifecycleState .PREPARING :
257
- event .fail (
258
- "Another peer is already in prepare state. Wait or call clean action to reset."
259
- )
260
- return
261
-
262
- # We process the special case of PREPARE, as explained in lifecycle.make_transition()
263
- if not self .config_manager .prepare ():
264
- event .fail ("Failed to prepare the benchmark" )
265
- return
266
-
267
- self .lifecycle .make_transition (state )
268
- self .unit .status = self .lifecycle .status
269
- event .set_results ({"message" : "Benchmark is being prepared" })
270
-
271
- def on_run_action (self , event : EventBase ) -> None :
272
- """Process the run action."""
273
- if not self ._preflight_checks ():
274
- event .fail ("Missing DB or S3 relations" )
275
- return
276
-
277
- if not self ._process_action_transition (DPBenchmarkLifecycleTransition .RUN ):
278
- event .fail ("Failed to run the benchmark" )
279
- event .set_results ({"message" : "Benchmark has started" })
280
-
281
- def on_stop_action (self , event : EventBase ) -> None :
282
- """Process the stop action."""
283
- if not self ._preflight_checks ():
284
- event .fail ("Missing DB or S3 relations" )
285
- return
286
-
287
- if not self ._process_action_transition (DPBenchmarkLifecycleTransition .STOP ):
288
- event .fail ("Failed to stop the benchmark" )
289
- event .set_results ({"message" : "Benchmark has stopped" })
290
-
291
- def on_clean_action (self , event : EventBase ) -> None :
292
- """Process the clean action."""
293
- if not self ._preflight_checks ():
294
- event .fail ("Missing DB or S3 relations" )
295
- return
296
-
297
- if not self ._process_action_transition (DPBenchmarkLifecycleTransition .CLEAN ):
298
- event .fail ("Failed to clean the benchmark" )
299
- event .set_results ({"message" : "Benchmark is cleaning" })
300
-
301
- def _process_action_transition (self , transition : DPBenchmarkLifecycleTransition ) -> bool :
302
- """Process the action."""
303
- # First, check if we have an update in our lifecycle state
304
- self ._update_state ()
305
-
306
- if not (state := self .lifecycle .next (transition )):
307
- return False
308
-
309
- self .lifecycle .make_transition (state )
310
- self .unit .status = self .lifecycle .status
311
- return True
312
-
313
179
###########################################################################
314
180
#
315
181
# Helpers
@@ -318,9 +184,14 @@ def _process_action_transition(self, transition: DPBenchmarkLifecycleTransition)
318
184
319
185
def _unit_ip (self ) -> str :
320
186
"""Current unit ip."""
321
- return self .model .get_binding (PEER_RELATION ).network .bind_address
187
+ bind_address = None
188
+ if PEER_RELATION :
189
+ if binding := self .model .get_binding (PEER_RELATION ):
190
+ bind_address = binding .network .bind_address
191
+
192
+ return str (bind_address ) if bind_address else ""
322
193
323
- def _update_state (self ) -> None :
194
+ def update_state (self ) -> None :
324
195
"""Update the state of the charm."""
325
196
if (next_state := self .lifecycle .next (None )) and self .lifecycle .current () != next_state :
326
197
self .lifecycle .make_transition (next_state )
0 commit comments