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 .fabric8 .kubernetes .api .model .ConfigMapBuilder ;
10
+ import io .strimzi .api .kafka .model .rebalance .KafkaRebalance ;
11
+ import io .strimzi .api .kafka .model .rebalance .KafkaRebalanceState ;
12
+ import io .strimzi .operator .cluster .model .ModelUtils ;
13
+ import io .strimzi .operator .cluster .model .cruisecontrol .ExecutorStateProcessor ;
14
+ import io .strimzi .operator .cluster .operator .resource .kubernetes .ConfigMapOperator ;
15
+ import io .strimzi .operator .common .Reconciliation ;
16
+ import io .strimzi .operator .common .operator .resource .ReconcileResult ;
17
+ import io .vertx .core .Future ;
18
+
19
+ import java .time .Instant ;
20
+ import java .util .Collections ;
21
+ import java .util .HashMap ;
22
+ import java .util .Map ;
23
+
24
+ /**
25
+ * Utility class for updating data in KafkaRebalance ConfigMap
26
+ */
27
+ public class KafkaRebalanceConfigMapUtils {
28
+
29
+ /* test */ static final String ESTIMATED_TIME_TO_COMPLETION_KEY = "estimatedTimeToCompletionInMinutes" ;
30
+ /* test */ static final String COMPLETED_BYTE_MOVEMENT_KEY = "completedByteMovementPercentage" ;
31
+ /* test */ static final String EXECUTOR_STATE_KEY = "executorState.json" ;
32
+ /* test */ static final String BROKER_LOAD_KEY = "brokerLoad.json" ;
33
+
34
+ /* test */ static final String TIME_COMPLETED = "0" ;
35
+ private static final String BYTE_MOVEMENT_ZERO = "0" ;
36
+ /* test */ static final String BYTE_MOVEMENT_COMPLETED = "100" ;
37
+
38
+ private static ConfigMap createConfigMap (KafkaRebalance kafkaRebalance ) {
39
+ return new ConfigMapBuilder ()
40
+ .withNewMetadata ()
41
+ .withNamespace (kafkaRebalance .getMetadata ().getNamespace ())
42
+ .withName (kafkaRebalance .getMetadata ().getName ())
43
+ .withLabels (Collections .singletonMap ("app" , "strimzi" ))
44
+ .withOwnerReferences (ModelUtils .createOwnerReference (kafkaRebalance , false ))
45
+ .endMetadata ()
46
+ .build ();
47
+ }
48
+
49
+ /**
50
+ * Updates the given KafkaRebalance ConfigMap with the broker load field.
51
+ *
52
+ * @param beforeAndAfterBrokerLoad The broker load information in JSON format, which is used to populate the broker load field.
53
+ * @param configMap The ConfigMap to be updated with progress information.
54
+ */
55
+ public static void updateRebalanceConfigMapWithLoadField (JsonNode beforeAndAfterBrokerLoad , ConfigMap configMap ) {
56
+ Map <String , String > map = configMap .getData ();
57
+ map .put (BROKER_LOAD_KEY , beforeAndAfterBrokerLoad .toString ());
58
+ }
59
+
60
+ /**
61
+ * Updates the given KafkaRebalance ConfigMap with progress fields based on the progress of the Kafka rebalance operation.
62
+ *
63
+ * @param state The current state of the Kafka rebalance operation (e.g., ProposalReady, Rebalancing, Stopped, etc.).
64
+ * @param executorState The executor state information in JSON format, which is used to calculate progress fields.
65
+ * @param configMap The ConfigMap to be updated with progress information. If null, a new ConfigMap will be created.
66
+ */
67
+ public static void updateRebalanceConfigMapWithProgressFields (KafkaRebalanceState state , JsonNode executorState , ConfigMap configMap ) {
68
+ if (configMap == null ) {
69
+ configMap = new ConfigMap ();
70
+ }
71
+
72
+ Map <String , String > data = configMap .getData ();
73
+ if (data == null ) {
74
+ data = new HashMap <>();
75
+ configMap .setData (data );
76
+ }
77
+
78
+ switch (state ) {
79
+ case ProposalReady :
80
+ data .remove (ESTIMATED_TIME_TO_COMPLETION_KEY );
81
+ data .put (COMPLETED_BYTE_MOVEMENT_KEY , BYTE_MOVEMENT_ZERO );
82
+ data .remove (EXECUTOR_STATE_KEY );
83
+ break ;
84
+ case Rebalancing :
85
+ try {
86
+ int taskStartTime = ExecutorStateProcessor .getTaskStartTime (executorState );
87
+ int totalDataToMove = ExecutorStateProcessor .getTotalDataToMove (executorState );
88
+ int finishedDataMovement = ExecutorStateProcessor .getFinishedDataMovement (executorState );
89
+
90
+ int estimatedTimeToCompletion = KafkaRebalanceProgressUtils .estimateTimeToCompletionInMinutes (
91
+ taskStartTime ,
92
+ Instant .now ().getEpochSecond (),
93
+ totalDataToMove ,
94
+ finishedDataMovement );
95
+
96
+ int completedByteMovement = KafkaRebalanceProgressUtils .estimateCompletedByteMovementPercentage (
97
+ totalDataToMove ,
98
+ finishedDataMovement );
99
+
100
+ data .put (ESTIMATED_TIME_TO_COMPLETION_KEY , String .valueOf (estimatedTimeToCompletion ));
101
+ data .put (COMPLETED_BYTE_MOVEMENT_KEY , String .valueOf (completedByteMovement ));
102
+ data .put (EXECUTOR_STATE_KEY , executorState .toString ());
103
+ } catch (IllegalArgumentException | ArithmeticException e ) {
104
+ // TODO: Have this caught by caller, logged and put into the `KafkaRebalance` status
105
+ throw new RuntimeException (e );
106
+ }
107
+ break ;
108
+ case Stopped :
109
+ case NotReady :
110
+ data .remove (ESTIMATED_TIME_TO_COMPLETION_KEY );
111
+ // Use the value of completedByteMovementPercentage from previous update.
112
+ // Use the value of executorState object from previous update.
113
+ break ;
114
+ case Ready :
115
+ data .put (ESTIMATED_TIME_TO_COMPLETION_KEY , TIME_COMPLETED );
116
+ data .put (COMPLETED_BYTE_MOVEMENT_KEY , BYTE_MOVEMENT_COMPLETED );
117
+ data .remove (EXECUTOR_STATE_KEY );
118
+ break ;
119
+ default :
120
+ break ;
121
+ }
122
+ }
123
+
124
+ static Future <ConfigMap > updateRebalanceConfigMap (Reconciliation reconciliation ,
125
+ ConfigMapOperator configMapOperator ,
126
+ KafkaRebalance kafkaRebalance ,
127
+ KafkaRebalanceState state ,
128
+ JsonNode executorState ,
129
+ JsonNode beforeAndAfterBrokerLoad ) {
130
+
131
+ Future <ConfigMap > oldKafkaRebalanceConfigMap ;
132
+ Future <ConfigMap > newKafkaRebalanceConfigMap ;
133
+
134
+ ConfigMap kafkaRebalanceConfigMap = createConfigMap (kafkaRebalance );
135
+ String namespace = kafkaRebalance .getMetadata ().getNamespace ();
136
+ String name = kafkaRebalance .getMetadata ().getName ();
137
+
138
+ switch (state ) {
139
+ case New :
140
+ case PendingProposal :
141
+ newKafkaRebalanceConfigMap = Future .succeededFuture (null );
142
+ break ;
143
+ case ProposalReady :
144
+ updateRebalanceConfigMapWithLoadField (beforeAndAfterBrokerLoad , kafkaRebalanceConfigMap );
145
+ updateRebalanceConfigMapWithProgressFields (state , null , kafkaRebalanceConfigMap );
146
+ newKafkaRebalanceConfigMap = Future .succeededFuture (kafkaRebalanceConfigMap );
147
+ break ;
148
+ case Rebalancing :
149
+ oldKafkaRebalanceConfigMap = configMapOperator .getAsync (namespace , name );
150
+ newKafkaRebalanceConfigMap = oldKafkaRebalanceConfigMap .flatMap (configMap -> {
151
+ updateRebalanceConfigMapWithProgressFields (state , executorState , configMap );
152
+ return Future .succeededFuture (configMap );
153
+ });
154
+ break ;
155
+ case Stopped :
156
+ case Ready :
157
+ case NotReady :
158
+ oldKafkaRebalanceConfigMap = configMapOperator .getAsync (namespace , name );
159
+ newKafkaRebalanceConfigMap = oldKafkaRebalanceConfigMap .flatMap (configMap -> {
160
+ updateRebalanceConfigMapWithProgressFields (state , null , configMap );
161
+ return Future .succeededFuture (configMap );
162
+ });
163
+ break ;
164
+ case ReconciliationPaused :
165
+ newKafkaRebalanceConfigMap = Future .succeededFuture ();
166
+ break ;
167
+
168
+ default :
169
+ newKafkaRebalanceConfigMap = Future .succeededFuture (null );
170
+ break ;
171
+ }
172
+
173
+ return newKafkaRebalanceConfigMap ;
174
+ }
175
+
176
+ static Future <ReconcileResult <ConfigMap >> reconcileKafkaRebalanceConfigMap (Reconciliation reconciliation ,
177
+ ConfigMapOperator configMapOperator ,
178
+ KafkaRebalance kafkaRebalance ,
179
+ KafkaRebalanceState state ,
180
+ JsonNode executorState ,
181
+ JsonNode beforeAndAfterBrokerLoad ) {
182
+ String namespace = kafkaRebalance .getMetadata ().getNamespace ();
183
+ String name = kafkaRebalance .getMetadata ().getName ();
184
+ Future <ConfigMap > configMapFuture = updateRebalanceConfigMap (reconciliation , configMapOperator , kafkaRebalance , state , executorState , beforeAndAfterBrokerLoad );
185
+ return configMapFuture .flatMap (configMap -> {
186
+ return configMapOperator .reconcile (reconciliation , namespace , name , configMap );
187
+ });
188
+ }
189
+
190
+ }
0 commit comments