5
5
import com .devshawn .kafka .gitops .domain .plan .DesiredPlan ;
6
6
import com .devshawn .kafka .gitops .domain .plan .PlanOverview ;
7
7
import com .devshawn .kafka .gitops .domain .plan .TopicConfigPlan ;
8
+ import com .devshawn .kafka .gitops .domain .plan .TopicDetailsPlan ;
8
9
import com .devshawn .kafka .gitops .domain .plan .TopicPlan ;
9
10
import com .devshawn .kafka .gitops .domain .state .AclDetails ;
10
11
import com .devshawn .kafka .gitops .domain .state .DesiredState ;
11
12
import com .devshawn .kafka .gitops .domain .state .TopicDetails ;
12
13
import com .devshawn .kafka .gitops .enums .PlanAction ;
13
14
import com .devshawn .kafka .gitops .exception .PlanIsUpToDateException ;
14
15
import com .devshawn .kafka .gitops .exception .ReadPlanInputException ;
16
+ import com .devshawn .kafka .gitops .exception .ValidationException ;
15
17
import com .devshawn .kafka .gitops .exception .WritePlanOutputException ;
16
18
import com .devshawn .kafka .gitops .service .KafkaService ;
17
19
import com .devshawn .kafka .gitops .util .PlanUtil ;
18
20
import com .fasterxml .jackson .databind .ObjectMapper ;
19
21
import org .apache .kafka .clients .admin .Config ;
20
22
import org .apache .kafka .clients .admin .ConfigEntry ;
21
- import org .apache .kafka .clients .admin .TopicListing ;
23
+ import org .apache .kafka .clients .admin .TopicDescription ;
22
24
import org .apache .kafka .common .acl .AclBinding ;
23
25
import org .apache .kafka .common .config .ConfigResource ;
24
26
import org .slf4j .LoggerFactory ;
@@ -47,37 +49,71 @@ public PlanManager(ManagerConfig managerConfig, KafkaService kafkaService, Objec
47
49
}
48
50
49
51
public void planTopics (DesiredState desiredState , DesiredPlan .Builder desiredPlan ) {
50
- List < TopicListing > topics = kafkaService .getTopics ();
51
- List <String > topicNames = topics .stream ().map (TopicListing :: name ).collect (Collectors .toList ());
52
+ Map < String , TopicDescription > topics = kafkaService .getTopics ();
53
+ List <String > topicNames = topics .entrySet (). stream ().map (Map . Entry :: getKey ).collect (Collectors .toList ());
52
54
Map <String , List <ConfigEntry >> topicConfigs = fetchTopicConfigurations (topicNames );
53
55
54
56
desiredState .getTopics ().forEach ((key , value ) -> {
57
+ TopicDetailsPlan .Builder topicDetailsPlan = new TopicDetailsPlan .Builder ();
58
+ topicDetailsPlan .setPartitionsAction (PlanAction .NO_CHANGE )
59
+ .setReplicationAction (PlanAction .NO_CHANGE );
60
+
55
61
TopicPlan .Builder topicPlan = new TopicPlan .Builder ()
56
- .setName (key )
57
- .setTopicDetails (value );
62
+ .setName (key );
58
63
59
64
if (!topicNames .contains (key )) {
60
65
log .info ("[PLAN] Topic {} does not exist; it will be created." , key );
61
66
topicPlan .setAction (PlanAction .ADD );
67
+ topicDetailsPlan .setPartitionsAction (PlanAction .ADD )
68
+ .setPartitions (value .getPartitions ())
69
+ .setReplicationAction (PlanAction .ADD )
70
+ .setReplication (value .getReplication ().get ());
71
+ planTopicConfigurations (key , value , topicConfigs .get (key ), topicPlan );
62
72
} else {
63
73
log .info ("[PLAN] Topic {} exists, it will not be created." , key );
64
74
topicPlan .setAction (PlanAction .NO_CHANGE );
75
+
76
+ TopicDescription topicDescription = topics .get (key );
77
+ boolean topicDetailsUpdated = false ;
78
+ if (value .getPartitions ().intValue () != topicDescription .partitions ().size ()) {
79
+ if ( value .getPartitions ().intValue () < topicDescription .partitions ().size ()) {
80
+ throw new ValidationException ("Removing the partition number is not supported by Apache Kafka "
81
+ + "(topic: " + key + " (" +topicDescription .partitions ().size ()+" -> " +value .getPartitions ().intValue ()+"))" );
82
+ }
83
+ topicDetailsPlan .setPartitions (value .getPartitions ())
84
+ .setPreviousPartitions (topicDescription .partitions ().size ());
85
+ topicDetailsPlan .setPartitionsAction (PlanAction .UPDATE );
86
+ topicDetailsUpdated = true ;
87
+ }
88
+ if (value .getReplication ().isPresent () &&
89
+ ( value .getReplication ().get ().intValue () != topicDescription .partitions ().get (0 ).replicas ().size ()) ) {
90
+ topicDetailsPlan .setReplication (value .getReplication ().get ())
91
+ .setPreviousReplication (topicDescription .partitions ().get (0 ).replicas ().size ());
92
+ topicDetailsPlan .setReplicationAction (PlanAction .UPDATE );
93
+ topicDetailsUpdated = true ;
94
+ }
95
+ if (topicDetailsUpdated ) {
96
+ topicPlan .setAction (PlanAction .UPDATE );
97
+ }
98
+
65
99
planTopicConfigurations (key , value , topicConfigs .get (key ), topicPlan );
66
100
}
67
101
102
+ topicPlan .setTopicDetailsPlan (topicDetailsPlan .build ());
103
+
68
104
desiredPlan .addTopicPlans (topicPlan .build ());
69
105
});
70
106
71
- topics .forEach (currentTopic -> {
72
- boolean shouldIgnore = desiredState .getPrefixedTopicsToIgnore ().stream ().anyMatch (it -> currentTopic . name () .startsWith (it ));
107
+ topics .forEach (( currentTopicName , currentTopicDescription ) -> {
108
+ boolean shouldIgnore = desiredState .getPrefixedTopicsToIgnore ().stream ().anyMatch (it -> currentTopicName .startsWith (it ));
73
109
if (shouldIgnore ) {
74
- log .info ("[PLAN] Ignoring topic {} due to prefix" , currentTopic . name () );
110
+ log .info ("[PLAN] Ignoring topic {} due to prefix" , currentTopicName );
75
111
return ;
76
112
}
77
113
78
- if (!managerConfig .isDeleteDisabled () && desiredState .getTopics ().getOrDefault (currentTopic . name () , null ) == null ) {
114
+ if (!managerConfig .isDeleteDisabled () && desiredState .getTopics ().getOrDefault (currentTopicName , null ) == null ) {
79
115
TopicPlan topicPlan = new TopicPlan .Builder ()
80
- .setName (currentTopic . name () )
116
+ .setName (currentTopicName )
81
117
.setAction (PlanAction .REMOVE )
82
118
.build ();
83
119
@@ -88,7 +124,7 @@ public void planTopics(DesiredState desiredState, DesiredPlan.Builder desiredPla
88
124
89
125
private void planTopicConfigurations (String topicName , TopicDetails topicDetails , List <ConfigEntry > configs , TopicPlan .Builder topicPlan ) {
90
126
Map <String , TopicConfigPlan > configPlans = new HashMap <>();
91
- List <ConfigEntry > customConfigs = configs .stream ()
127
+ List <ConfigEntry > customConfigs = configs == null ? new ArrayList <>() : configs .stream ()
92
128
.filter (it -> it .source () == ConfigEntry .ConfigSource .DYNAMIC_TOPIC_CONFIG )
93
129
.collect (Collectors .toList ());
94
130
@@ -105,7 +141,9 @@ private void planTopicConfigurations(String topicName, TopicDetails topicDetails
105
141
} else if (newConfig == null ) {
106
142
topicConfigPlan .setAction (PlanAction .REMOVE );
107
143
configPlans .put (currentConfig .name (), topicConfigPlan .build ());
108
- topicPlan .setAction (PlanAction .UPDATE );
144
+ if (topicPlan .getAction () == null || topicPlan .getAction ().equals (PlanAction .NO_CHANGE )) {
145
+ topicPlan .setAction (PlanAction .UPDATE );
146
+ }
109
147
}
110
148
});
111
149
@@ -120,12 +158,16 @@ private void planTopicConfigurations(String topicName, TopicDetails topicDetails
120
158
if (currentConfig == null ) {
121
159
topicConfigPlan .setAction (PlanAction .ADD );
122
160
configPlans .put (key , topicConfigPlan .build ());
123
- topicPlan .setAction (PlanAction .UPDATE );
161
+ if (topicPlan .getAction () == null || topicPlan .getAction ().equals (PlanAction .NO_CHANGE )) {
162
+ topicPlan .setAction (PlanAction .UPDATE );
163
+ }
124
164
} else if (!currentConfig .value ().equals (value )) {
125
165
topicConfigPlan .setPreviousValue (currentConfig .value ())
126
166
.setAction (PlanAction .UPDATE );
127
167
configPlans .put (key , topicConfigPlan .build ());
128
- topicPlan .setAction (PlanAction .UPDATE );
168
+ if (topicPlan .getAction () == null || topicPlan .getAction ().equals (PlanAction .NO_CHANGE )) {
169
+ topicPlan .setAction (PlanAction .UPDATE );
170
+ }
129
171
}
130
172
});
131
173
0 commit comments