1
+ /*
2
+ * Copyright Strimzi authors.
3
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4
+ */
5
+ package io .strimzi .operator .cluster .operator .assembly ;
6
+
7
+ import com .fasterxml .jackson .databind .JsonNode ;
8
+ import io .fabric8 .kubernetes .api .model .ConfigMap ;
9
+ import io .strimzi .api .kafka .model .common .Condition ;
10
+ import io .strimzi .api .kafka .model .rebalance .KafkaRebalanceState ;
11
+ import io .strimzi .api .kafka .model .rebalance .KafkaRebalanceStatus ;
12
+ import io .strimzi .operator .cluster .model .CruiseControl ;
13
+ import io .strimzi .operator .cluster .model .cruisecontrol .ExecutorStateProcessor ;
14
+ import io .strimzi .operator .cluster .operator .VertxUtil ;
15
+ import io .strimzi .operator .cluster .operator .resource .cruisecontrol .CruiseControlApi ;
16
+ import io .strimzi .operator .common .Reconciliation ;
17
+ import io .strimzi .operator .common .model .StatusUtils ;
18
+ import io .vertx .core .Future ;
19
+
20
+ import java .time .Instant ;
21
+ import java .util .HashMap ;
22
+ import java .util .List ;
23
+ import java .util .Map ;
24
+
25
+ /**
26
+ * Utility class for updating data in KafkaRebalance ConfigMap
27
+ */
28
+ public class KafkaRebalanceConfigMapUtils {
29
+
30
+ /* test */ static final String REBALANCE_PROGRESS_CONFIG_MAP_KEY = "rebalanceProgressConfigMap" ;
31
+ /* test */ static final String ESTIMATED_TIME_TO_COMPLETION_KEY = "estimatedTimeToCompletionInMinutes" ;
32
+ /* test */ static final String COMPLETED_BYTE_MOVEMENT_KEY = "completedByteMovementPercentage" ;
33
+ /* test */ static final String EXECUTOR_STATE_KEY = "executorState.json" ;
34
+
35
+ /* test */ static final String TIME_COMPLETED = "0" ;
36
+ private static final String BYTE_MOVEMENT_ZERO = "0" ;
37
+ /* test */ static final String BYTE_MOVEMENT_COMPLETED = "100" ;
38
+
39
+ /**
40
+ * Updates the given KafkaRebalance ConfigMap with progress fields based on the progress of the Kafka rebalance operation.
41
+ *
42
+ * @param state The current state of the Kafka rebalance operation (e.g., ProposalReady, Rebalancing, Stopped, etc.).
43
+ * @param executorState The executor state information in JSON format, which is used to calculate progress fields.
44
+ * @param configMap The ConfigMap to be updated with progress information. If null, a new ConfigMap will be created.
45
+ */
46
+ public static void updateRebalanceConfigMapWithProgressFields (KafkaRebalanceState state , JsonNode executorState , ConfigMap configMap ) {
47
+ if (configMap == null ) {
48
+ return ;
49
+ }
50
+
51
+ Map <String , String > data = configMap .getData ();
52
+ if (data == null ) {
53
+ data = new HashMap <>();
54
+ configMap .setData (data );
55
+ }
56
+
57
+ switch (state ) {
58
+ case ProposalReady :
59
+ data .remove (ESTIMATED_TIME_TO_COMPLETION_KEY );
60
+ data .put (COMPLETED_BYTE_MOVEMENT_KEY , BYTE_MOVEMENT_ZERO );
61
+ data .remove (EXECUTOR_STATE_KEY );
62
+ break ;
63
+ case Rebalancing :
64
+ int taskStartTime = ExecutorStateProcessor .getTaskStartTime (executorState );
65
+ int totalDataToMove = ExecutorStateProcessor .getTotalDataToMove (executorState );
66
+ int finishedDataMovement = ExecutorStateProcessor .getFinishedDataMovement (executorState );
67
+
68
+ int estimatedTimeToCompletion = KafkaRebalanceProgressUtils .estimateTimeToCompletionInMinutes (
69
+ taskStartTime ,
70
+ Instant .now ().getEpochSecond (),
71
+ totalDataToMove ,
72
+ finishedDataMovement );
73
+
74
+ int completedByteMovement = KafkaRebalanceProgressUtils .estimateCompletedByteMovementPercentage (
75
+ totalDataToMove ,
76
+ finishedDataMovement );
77
+
78
+ data .put (ESTIMATED_TIME_TO_COMPLETION_KEY , String .valueOf (estimatedTimeToCompletion ));
79
+ data .put (COMPLETED_BYTE_MOVEMENT_KEY , String .valueOf (completedByteMovement ));
80
+ data .put (EXECUTOR_STATE_KEY , executorState .toString ());
81
+ break ;
82
+ case Stopped :
83
+ case NotReady :
84
+ data .remove (ESTIMATED_TIME_TO_COMPLETION_KEY );
85
+ // Use the value of completedByteMovementPercentage from previous update.
86
+ // Use the value of executorState object from previous update.
87
+ break ;
88
+ case Ready :
89
+ data .put (ESTIMATED_TIME_TO_COMPLETION_KEY , TIME_COMPLETED );
90
+ data .put (COMPLETED_BYTE_MOVEMENT_KEY , BYTE_MOVEMENT_COMPLETED );
91
+ data .remove (EXECUTOR_STATE_KEY );
92
+ break ;
93
+ default :
94
+ break ;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Updates the Kafka Rebalance {@link ConfigMap} with relevant state information depending on the current
100
+ * {@link KafkaRebalanceState}.
101
+ *
102
+ * @param reconciliation The reconciliation context
103
+ * @param state The KafkaRebalance state
104
+ * @param host The host address of the Cruise Control instance
105
+ * @param apiClient The API client to communicate with Cruise Control
106
+ * @param configMap The desired ConfigMap
107
+ *
108
+ * @return A {@link Future} representing the updated ConfigMap (or null if no update was required)
109
+ */
110
+ public static Future <ConfigMap > updateRebalanceConfigMap (Reconciliation reconciliation ,
111
+ KafkaRebalanceState state ,
112
+ String host ,
113
+ CruiseControlApi apiClient ,
114
+ ConfigMap configMap ) {
115
+ if (state == KafkaRebalanceState .Rebalancing ) {
116
+ return VertxUtil .completableFutureToVertxFuture (
117
+ apiClient .getCruiseControlState (reconciliation ,
118
+ host ,
119
+ CruiseControl .REST_API_PORT ,
120
+ false ))
121
+ .compose (response -> {
122
+ JsonNode executorState = response .getJson ().get ("ExecutorState" );
123
+ KafkaRebalanceConfigMapUtils .updateRebalanceConfigMapWithProgressFields (
124
+ KafkaRebalanceState .Rebalancing , executorState , configMap );
125
+ return Future .succeededFuture (configMap );
126
+ });
127
+ } else {
128
+ updateRebalanceConfigMapWithProgressFields (state , null , configMap );
129
+ return Future .succeededFuture (configMap );
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Updates the {@code KafkaRebalanceStatus} conditions by adding a new Warning condition
135
+ * based on the provided {@link Throwable}. If a Warning condition with the same reason
136
+ * and message already exists, no update is performed.
137
+ *
138
+ * @param status The {@link KafkaRebalanceStatus} object whose conditions will be updated.
139
+ * @param exception The {@link Throwable} containing the reason and message for the Warning condition.
140
+ */
141
+ public static void updateStatusCondition (KafkaRebalanceStatus status , Throwable exception ) {
142
+ List <Condition > conditions = status .getConditions ();
143
+
144
+ Condition warningCondition = StatusUtils .buildWarningCondition (exception .getCause ().toString (), exception .getMessage ());
145
+
146
+ // Do not update Warning condition if it already exists with same reason and message
147
+ for (Condition condition : conditions ) {
148
+ if (condition .getType ().equals ("Warning" )) {
149
+ if (condition .getReason ().equals (warningCondition .getReason ()) &&
150
+ condition .getMessage ().equals (warningCondition .getMessage ())) {
151
+ return ;
152
+ }
153
+ }
154
+
155
+ }
156
+ conditions .add (warningCondition );
157
+ }
158
+ }
0 commit comments