Browse Source

remove jmx, topic details, topic configs

Zhenya Taran 5 năm trước cách đây
mục cha
commit
0eab788fa1
17 tập tin đã thay đổi với 256 bổ sung342 xóa
  1. 13 13
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/mapper/ClusterMapper.java
  2. 1 3
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/ClustersStorage.java
  3. 7 25
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/KafkaCluster.java
  4. 16 0
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/KafkaMetrics.java
  5. 0 19
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/MetricsConstants.java
  6. 9 44
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/service/ClusterService.java
  7. 0 3
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/service/MetricsUpdateService.java
  8. 0 23
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/JmxConstants.java
  9. 0 111
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/JmxService.java
  10. 0 8
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/JmxTopicConstants.java
  11. 0 24
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/MBeanInfo.java
  12. 45 0
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/kafka/KafkaConstants.java
  13. 93 41
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/kafka/KafkaService.java
  14. 8 6
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/rest/MetricsRestController.java
  15. 10 0
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/zookeeper/ZooKeeperConstants.java
  16. 2 7
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/zookeeper/ZookeeperService.java
  17. 52 15
      kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml

+ 13 - 13
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/mapper/ClusterMapper.java

@@ -2,22 +2,22 @@ package com.provectus.kafka.ui.cluster.mapper;
 
 import com.provectus.kafka.ui.cluster.config.ClustersProperties;
 import com.provectus.kafka.ui.cluster.model.KafkaCluster;
-import com.provectus.kafka.ui.model.Cluster;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 
 @Mapper
-public interface ClusterMapper {
+public abstract class ClusterMapper {
 
-    @Mapping(target = "brokerCount", ignore = true)
-    @Mapping(target = "bytesInPerSec", ignore = true)
-    @Mapping(target = "bytesOutPerSec", ignore = true)
-    @Mapping(target = "defaultCluster", ignore = true)
-    @Mapping(target = "onlinePartitionCount", ignore = true)
-    @Mapping(target = "topicCount", ignore = true)
-    Cluster toOpenApiCluster(KafkaCluster kafkaCluster);
-
-    @Mapping(target = "metricsMap", ignore = true)
-    @Mapping(target = "status", ignore = true)
-    KafkaCluster toKafkaCluster(ClustersProperties.Cluster clusterProperties);
+    @Mapping(source = "name", target = "cluster.name")
+    @Mapping(target = "brokersMetrics", ignore = true)
+    @Mapping(target = "cluster", ignore = true)
+    @Mapping(target = "lastKafkaException", ignore = true)
+    @Mapping(target = "lastZookeeperException", ignore = true)
+    @Mapping(target = "topicConfigsMap", ignore = true)
+    @Mapping(target = "topicDetailsMap", ignore = true)
+    @Mapping(target = "topics", ignore = true)
+    @Mapping(target = "zkClient", ignore = true)
+    @Mapping(target = "zookeeperStatus", ignore = true)
+    @Mapping(target = "adminClient", ignore = true)
+    public abstract KafkaCluster toKafkaCluster(ClustersProperties.Cluster clusterProperties);
 }

+ 1 - 3
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/ClustersStorage.java

@@ -10,8 +10,6 @@ import javax.annotation.PostConstruct;
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.provectus.kafka.ui.cluster.model.MetricsConstants.CLUSTER_ID;
-
 @Component
 @RequiredArgsConstructor
 public class ClustersStorage {
@@ -35,7 +33,7 @@ public class ClustersStorage {
 
     public KafkaCluster getClusterById(String clusterId) {
         return kafkaClusters.stream()
-                .filter(cltr -> cltr.getMetricsMap().get(CLUSTER_ID).equals(clusterId))
+                .filter(cluster -> cluster.getId().equals(clusterId))
                 .findFirst()
                 .orElseThrow();
     }

+ 7 - 25
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/KafkaCluster.java

@@ -1,61 +1,43 @@
 package com.provectus.kafka.ui.cluster.model;
 
-import com.provectus.kafka.ui.model.ServerStatus;
-import com.provectus.kafka.ui.model.Topic;
-import com.provectus.kafka.ui.model.TopicDetails;
+import com.provectus.kafka.ui.model.*;
 import lombok.AccessLevel;
 import lombok.Data;
 import lombok.experimental.FieldDefaults;
 import org.I0Itec.zkclient.ZkClient;
 import org.apache.kafka.clients.admin.AdminClient;
 
-import javax.management.MBeanServerConnection;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
 
 @Data
 @FieldDefaults(level = AccessLevel.PRIVATE)
 public class KafkaCluster {
 
+    String id = "";
     String name;
     String jmxHost;
     String jmxPort;
     String bootstrapServers;
     String zookeeper;
 
-    Map<String, String> metricsMap = new ConcurrentHashMap<>();
+    Cluster cluster = new Cluster();
+    BrokersMetrics brokersMetrics = new BrokersMetrics();
+
     List<Topic> topics = new ArrayList<>();
     private Map<String, TopicDetails> topicDetailsMap = new ConcurrentHashMap<>();
+    private Map<String, List<TopicConfig>> topicConfigsMap = new ConcurrentHashMap<>();
+
 
-    MBeanServerConnection mBeanServerConnection;
     ZkClient zkClient;
     AdminClient adminClient;
-
-    ServerStatus status = ServerStatus.OFFLINE;
-    ServerStatus jmxStatus = ServerStatus.OFFLINE;
     ServerStatus zookeeperStatus = ServerStatus.OFFLINE;
 
     Exception lastKafkaException;
-    Exception lastJmxException;
     Exception lastZookeeperException;
 
-    public void putMetric(String metricKey, String metricValue) {
-        metricsMap.put(metricKey, metricValue);
-    }
-
-    public String getMetric(String metricKey) {
-        return metricsMap.get(metricKey);
-    }
-
-    public String getMetricsMapAsString() {
-        return metricsMap.keySet().stream()
-                .map(key -> key + "=" + metricsMap.get(key))
-                .collect(Collectors.joining(", ", "{", "}"));
-    }
-
     public TopicDetails getTopicDetails(String key) {
         var topicDetails = topicDetailsMap.get(key);
         if(topicDetails == null) {

+ 16 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/KafkaMetrics.java

@@ -0,0 +1,16 @@
+package com.provectus.kafka.ui.cluster.model;
+
+import lombok.Data;
+
+@Data
+public class KafkaMetrics {
+
+    Double bytesInPerSec;
+    Double bytesOutPerSec;
+    Integer brokersCount;
+    Integer topicCount;
+    Integer activeControllerCount;
+    Integer onlinePartitionCount;
+    Integer offlinePartitionCount;
+    Integer underReplicatedPartitions;
+}

+ 0 - 19
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/model/MetricsConstants.java

@@ -1,19 +0,0 @@
-package com.provectus.kafka.ui.cluster.model;
-
-public final class MetricsConstants {
-
-    private MetricsConstants() {}
-
-    public static final String CLUSTER_ID = "ClusterId";
-    public static final String BYTES_IN_PER_SEC = "BytesInPerSec";
-    public static final String BYTES_OUT_PER_SEC = "BytesOutPerSec";
-    public static final String BROKERS_COUNT = "BrokersCount";
-    public static final String TOPIC_COUNT = "TopicCount";
-    public static final String PARTITIONS_COUNT = "PartitionsCount";
-    public static final String ZOOKEEPER_STATUS = "ZooKeeperStatus";
-    public static final String ACTIVE_CONTROLLER_COUNT = "ActiveControllerCount";
-    public static final String ONLINE_PARTITION_COUNT = "OnlinePartitionCount";
-    public static final String OFFLINE_PARTITION_COUNT = "OfflinePartitionCount";
-    public static final String UNDER_REPLICATED_PARTITIONS = "UnderReplicatedPartitions";
-
-}

+ 9 - 44
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/service/ClusterService.java

@@ -1,15 +1,9 @@
 package com.provectus.kafka.ui.cluster.service;
 
-import com.provectus.kafka.ui.cluster.mapper.ClusterMapper;
 import com.provectus.kafka.ui.cluster.model.ClustersStorage;
 import com.provectus.kafka.ui.cluster.model.KafkaCluster;
-import com.provectus.kafka.ui.cluster.model.MetricsConstants;
-import com.provectus.kafka.ui.model.BrokerMetrics;
-import com.provectus.kafka.ui.model.Cluster;
-import com.provectus.kafka.ui.model.Topic;
-import com.provectus.kafka.ui.model.TopicDetails;
+import com.provectus.kafka.ui.model.*;
 import lombok.RequiredArgsConstructor;
-import org.mapstruct.factory.Mappers;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import reactor.core.publisher.Flux;
@@ -18,49 +12,23 @@ import reactor.core.publisher.Mono;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import static com.provectus.kafka.ui.cluster.model.MetricsConstants.CLUSTER_ID;
-
 @Service
 @RequiredArgsConstructor
 public class ClusterService {
 
     private final ClustersStorage clustersStorage;
 
-    private final ClusterMapper clusterMapper = Mappers.getMapper(ClusterMapper.class);
-
-
     public Mono<ResponseEntity<Flux<Cluster>>> getClusters() {
         List<Cluster> clusters = clustersStorage.getKafkaClusters()
                 .stream()
-                .map(kafkaCluster -> {
-                    Cluster cluster = clusterMapper.toOpenApiCluster(kafkaCluster);
-                    cluster.setId(kafkaCluster.getMetric(CLUSTER_ID));
-                    cluster.setBrokerCount(intValueOfOrNull(kafkaCluster.getMetric(MetricsConstants.BROKERS_COUNT)));
-                    cluster.setTopicCount(intValueOfOrNull(kafkaCluster.getMetric(MetricsConstants.TOPIC_COUNT)));
-                    cluster.setBytesInPerSec(intValueOfOrNull(kafkaCluster.getMetric(MetricsConstants.BYTES_IN_PER_SEC)));
-                    cluster.setBytesOutPerSec(intValueOfOrNull(kafkaCluster.getMetric(MetricsConstants.BYTES_OUT_PER_SEC)));
-                    cluster.setOnlinePartitionCount(intValueOfOrNull(kafkaCluster.getMetric(MetricsConstants.PARTITIONS_COUNT)));
-                    return cluster;
-                })
+                .map(KafkaCluster::getCluster)
                 .collect(Collectors.toList());
 
         return Mono.just(ResponseEntity.ok(Flux.fromIterable(clusters)));
     }
 
-    public Mono<ResponseEntity<BrokerMetrics>> getBrokerMetrics(String clusterId) {
-        KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
-
-        BrokerMetrics brokerMetrics = new BrokerMetrics();
-        brokerMetrics.setClusterId(cluster.getMetricsMap().get(CLUSTER_ID));
-        brokerMetrics.setBrokerCount(intValueOfOrNull(cluster.getMetric(MetricsConstants.BROKERS_COUNT)));
-        brokerMetrics.setBytesInPerSec(intValueOfOrNull(cluster.getMetric(MetricsConstants.BYTES_IN_PER_SEC)));
-        brokerMetrics.setZooKeeperStatus(intValueOfOrNull(cluster.getMetric(MetricsConstants.ZOOKEEPER_STATUS)));
-        brokerMetrics.setActiveControllers(intValueOfOrNull(cluster.getMetric(MetricsConstants.ACTIVE_CONTROLLER_COUNT)));
-        brokerMetrics.setOnlinePartitionCount(intValueOfOrNull(cluster.getMetric(MetricsConstants.ONLINE_PARTITION_COUNT)));
-        brokerMetrics.setOfflinePartitionCount(intValueOfOrNull(cluster.getMetric(MetricsConstants.OFFLINE_PARTITION_COUNT)));
-        brokerMetrics.setUnderReplicatedPartitionCount(intValueOfOrNull(cluster.getMetric(MetricsConstants.UNDER_REPLICATED_PARTITIONS)));
-
-        return Mono.just(ResponseEntity.ok(brokerMetrics));
+    public Mono<ResponseEntity<BrokersMetrics>> getBrokersMetrics(String clusterId) {
+        return Mono.just(ResponseEntity.ok(clustersStorage.getClusterById(clusterId).getBrokersMetrics()));
     }
 
     public Mono<ResponseEntity<Flux<Topic>>> getTopics(String clusterId) {
@@ -69,16 +37,13 @@ public class ClusterService {
         return Mono.just(ResponseEntity.ok(Flux.fromIterable(cluster.getTopics())));
     }
 
-    private Integer intValueOfOrNull(String s) {
-        try {
-            return Integer.valueOf(s);
-        } catch (NumberFormatException e) {
-            return null;
-        }
-    }
-
     public Mono<ResponseEntity<TopicDetails>> getTopicDetails(String clusterId, String topicName) {
         KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
         return Mono.just(ResponseEntity.ok(cluster.getTopicDetails(topicName)));
     }
+
+    public Mono<ResponseEntity<Flux<TopicConfig>>> getTopicConfigs(String clusterId, String topicName) {
+        KafkaCluster cluster = clustersStorage.getClusterById(clusterId);
+        return Mono.just(ResponseEntity.ok(Flux.fromIterable(cluster.getTopicConfigsMap().get(topicName))));
+    }
 }

+ 0 - 3
kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/service/MetricsUpdateService.java

@@ -1,7 +1,6 @@
 package com.provectus.kafka.ui.cluster.service;
 
 import com.provectus.kafka.ui.cluster.model.KafkaCluster;
-import com.provectus.kafka.ui.jmx.JmxService;
 import com.provectus.kafka.ui.kafka.KafkaService;
 import com.provectus.kafka.ui.zookeeper.ZookeeperService;
 import lombok.RequiredArgsConstructor;
@@ -14,7 +13,6 @@ import org.springframework.stereotype.Service;
 @Log4j2
 public class MetricsUpdateService {
 
-    private final JmxService jmxService;
     private final KafkaService kafkaService;
     private final ZookeeperService zookeeperService;
 
@@ -22,7 +20,6 @@ public class MetricsUpdateService {
     public void updateMetrics(KafkaCluster kafkaCluster) {
         log.debug("Start getting metrics for kafkaCluster: " + kafkaCluster.getName());
         kafkaService.loadClusterMetrics(kafkaCluster);
-//        jmxService.loadClusterMetrics(kafkaCluster);
         zookeeperService.checkZookeeperStatus(kafkaCluster);
     }
 }

+ 0 - 23
kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/JmxConstants.java

@@ -1,23 +0,0 @@
-package com.provectus.kafka.ui.jmx;
-
-import com.provectus.kafka.ui.cluster.model.MetricsConstants;
-
-import java.util.Map;
-
-import static java.util.Map.entry;
-
-public final class JmxConstants {
-
-    private JmxConstants() {}
-
-    public static final Map<MBeanInfo, String> mbeanToAttributeMap = Map.ofEntries(
-            entry(MBeanInfo.of("kafka.server:type=KafkaServer,name=ClusterId", "Value"), MetricsConstants.CLUSTER_ID),
-            entry(MBeanInfo.of("kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec", "Count"), MetricsConstants.BYTES_IN_PER_SEC),
-            entry(MBeanInfo.of("kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec", "Count"), MetricsConstants.BYTES_OUT_PER_SEC),
-            entry(MBeanInfo.of("kafka.controller:type=KafkaController,name=ActiveControllerCount", "Value"), MetricsConstants.ACTIVE_CONTROLLER_COUNT),
-            entry(MBeanInfo.of("kafka.controller:type=KafkaController,name=GlobalPartitionCount", "Value"), MetricsConstants.ONLINE_PARTITION_COUNT),
-            entry(MBeanInfo.of("kafka.controller:type=KafkaController,name=OfflinePartitionsCount", "Value"), MetricsConstants.OFFLINE_PARTITION_COUNT),
-            entry(MBeanInfo.of("kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions", "Value"), MetricsConstants.UNDER_REPLICATED_PARTITIONS)
-    );
-
-}

+ 0 - 111
kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/JmxService.java

@@ -1,111 +0,0 @@
-package com.provectus.kafka.ui.jmx;
-
-import com.provectus.kafka.ui.cluster.model.KafkaCluster;
-import com.provectus.kafka.ui.model.ServerStatus;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.log4j.Log4j2;
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Service;
-
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import javax.management.remote.JMXConnector;
-import javax.management.remote.JMXConnectorFactory;
-import javax.management.remote.JMXServiceURL;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-@Service
-@Log4j2
-@RequiredArgsConstructor
-public class JmxService {
-
-    @SneakyThrows
-    @Async
-    public void loadClusterMetrics(KafkaCluster kafkaCluster) {
-        log.debug("Start getting JMX metrics for kafkaCluster: " + kafkaCluster.getName());
-        boolean isConnected = false;
-        if (kafkaCluster.getMBeanServerConnection() != null) {
-            isConnected = isJmxConnected(kafkaCluster);
-        }
-        if (kafkaCluster.getMBeanServerConnection() == null || !isConnected) {
-            isConnected = createJmxConnection(kafkaCluster);
-        }
-
-        if (!isConnected) {
-            kafkaCluster.setJmxStatus(ServerStatus.OFFLINE);
-
-            return;
-        }
-
-        kafkaCluster.setJmxStatus(ServerStatus.ONLINE);
-        loadJmxClusterMetrics(kafkaCluster);
-        loadJmxTopicMetrics(kafkaCluster);
-    }
-
-    @SneakyThrows
-    private void loadJmxTopicMetrics(KafkaCluster kafkaCluster) {
-        Set<ObjectInstance> objectInstances = kafkaCluster.getMBeanServerConnection().queryMBeans(new ObjectName(
-                "kafka.cluster:type=Partition,name=UnderReplicated,topic=*,partition=*"), null);
-        Map<String, Integer> topicUrpMap = new HashMap<>();
-        for (ObjectInstance objectInstance : objectInstances) {
-            String topicName = objectInstance.getObjectName().getKeyProperty("topic");
-            if (topicName != null) {
-                topicUrpMap.putIfAbsent(topicName, 0);
-                Object attributeValue = kafkaCluster.getMBeanServerConnection().getAttribute(objectInstance.getObjectName(),"Value");
-                try {
-                    if (attributeValue != null && Integer.parseInt(attributeValue.toString()) == 1) {
-                        topicUrpMap.put(topicName, topicUrpMap.get(topicName) + 1);
-                    }
-                } catch (ArithmeticException e) {
-                    log.error(e);
-                }
-            }
-        }
-
-        for (Map.Entry<String, Integer> entry : topicUrpMap.entrySet()) {
-            kafkaCluster.getTopicDetails(entry.getKey()).setUnderReplicatedPartitions(entry.getValue());
-        }
-    }
-
-    @SneakyThrows
-    private void loadJmxClusterMetrics(KafkaCluster kafkaCluster) {
-        for (Map.Entry<MBeanInfo, String> mbeanToMetric : JmxConstants.mbeanToAttributeMap.entrySet()) {
-            MBeanInfo mBeanInfo = mbeanToMetric.getKey();
-            Object attributeValue = kafkaCluster.getMBeanServerConnection().getAttribute(new ObjectName(mBeanInfo.getName()), mBeanInfo.getAttribute());
-            kafkaCluster.putMetric(mbeanToMetric.getValue(), attributeValue.toString());
-        }
-    }
-
-    private boolean createJmxConnection(KafkaCluster kafkaCluster) {
-        try {
-            String url = "service:jmx:rmi:///jndi/rmi://" + kafkaCluster.getJmxHost() + ":" + kafkaCluster.getJmxPort() + "/jmxrmi";
-            JMXServiceURL serviceUrl = new JMXServiceURL(url);
-            JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
-            kafkaCluster.setMBeanServerConnection(jmxConnector.getMBeanServerConnection());
-
-            return true;
-        } catch (Exception e) {
-            log.error(e);
-            kafkaCluster.setLastJmxException(e);
-
-            return false;
-        }
-    }
-
-    private boolean isJmxConnected(KafkaCluster kafkaCluster) {
-        try {
-            kafkaCluster.getMBeanServerConnection().getMBeanCount();
-
-            return true;
-        } catch (IOException e) {
-            log.error(e);
-            kafkaCluster.setLastJmxException(e);
-
-            return false;
-        }
-    }
-}

+ 0 - 8
kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/JmxTopicConstants.java

@@ -1,8 +0,0 @@
-package com.provectus.kafka.ui.jmx;
-
-public final class JmxTopicConstants {
-
-    private JmxTopicConstants() {}
-
-
-}

+ 0 - 24
kafka-ui-api/src/main/java/com/provectus/kafka/ui/jmx/MBeanInfo.java

@@ -1,24 +0,0 @@
-package com.provectus.kafka.ui.jmx;
-
-public class MBeanInfo {
-
-    private String name;
-    private String attribute;
-
-    private MBeanInfo(String name, String attribute) {
-        this.name = name;
-        this.attribute = attribute;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getAttribute() {
-        return attribute;
-    }
-
-    public static MBeanInfo of(String name, String attribute) {
-        return new MBeanInfo(name, attribute);
-    }
-}

+ 45 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/kafka/KafkaConstants.java

@@ -0,0 +1,45 @@
+package com.provectus.kafka.ui.kafka;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+import static org.apache.kafka.common.config.TopicConfig.*;
+
+public final class KafkaConstants {
+
+    private KafkaConstants() {
+    }
+
+    public static String IN_BYTE_PER_SEC_METRIC = "incoming-byte-rate";
+    public static String IN_BYTE_PER_SEC_METRIC_DESCRIPTION = "The number of bytes read off all sockets per second";
+    public static String OUT_BYTE_PER_SEC_METRIC = "outgoing-byte-rate";
+    public static String OUT_BYTE_PER_SEC_METRIC_DESCRIPTION = "The number of outgoing bytes sent to all servers per second";
+
+    public static Map<String, String> TOPIC_DEFAULT_CONFIGS = Map.ofEntries(
+            new AbstractMap.SimpleEntry<>(CLEANUP_POLICY_CONFIG, CLEANUP_POLICY_DELETE),
+            new AbstractMap.SimpleEntry<>(COMPRESSION_TYPE_CONFIG, "producer"),
+            new AbstractMap.SimpleEntry<>(DELETE_RETENTION_MS_CONFIG, "86400000"),
+            new AbstractMap.SimpleEntry<>(FILE_DELETE_DELAY_MS_CONFIG, "60000"),
+            new AbstractMap.SimpleEntry<>(FLUSH_MESSAGES_INTERVAL_CONFIG, "9223372036854775807"),
+            new AbstractMap.SimpleEntry<>(FLUSH_MS_CONFIG, "9223372036854775807"),
+            new AbstractMap.SimpleEntry<>("follower.replication.throttled.replicas", ""),
+            new AbstractMap.SimpleEntry<>(INDEX_INTERVAL_BYTES_CONFIG, "4096"),
+            new AbstractMap.SimpleEntry<>("leader.replication.throttled.replicas", ""),
+            new AbstractMap.SimpleEntry<>(MAX_COMPACTION_LAG_MS_CONFIG, "9223372036854775807"),
+            new AbstractMap.SimpleEntry<>(MAX_MESSAGE_BYTES_CONFIG, "1000012"),
+            new AbstractMap.SimpleEntry<>(MESSAGE_TIMESTAMP_DIFFERENCE_MAX_MS_CONFIG, "9223372036854775807"),
+            new AbstractMap.SimpleEntry<>(MESSAGE_TIMESTAMP_TYPE_CONFIG, "CreateTime"),
+            new AbstractMap.SimpleEntry<>(MIN_CLEANABLE_DIRTY_RATIO_CONFIG, "0.5"),
+            new AbstractMap.SimpleEntry<>(MIN_COMPACTION_LAG_MS_CONFIG, "0"),
+            new AbstractMap.SimpleEntry<>(MIN_IN_SYNC_REPLICAS_CONFIG, "1"),
+            new AbstractMap.SimpleEntry<>(PREALLOCATE_CONFIG, "false"),
+            new AbstractMap.SimpleEntry<>(RETENTION_BYTES_CONFIG, "-1"),
+            new AbstractMap.SimpleEntry<>(RETENTION_MS_CONFIG, "604800000"),
+            new AbstractMap.SimpleEntry<>(SEGMENT_BYTES_CONFIG, "1073741824"),
+            new AbstractMap.SimpleEntry<>(SEGMENT_INDEX_BYTES_CONFIG, "10485760"),
+            new AbstractMap.SimpleEntry<>(SEGMENT_JITTER_MS_CONFIG, "0"),
+            new AbstractMap.SimpleEntry<>(SEGMENT_MS_CONFIG, "604800000"),
+            new AbstractMap.SimpleEntry<>(UNCLEAN_LEADER_ELECTION_ENABLE_CONFIG, "false"),
+            new AbstractMap.SimpleEntry<>(MESSAGE_DOWNCONVERSION_ENABLE_CONFIG, "true")
+    );
+}

+ 93 - 41
kafka-ui-api/src/main/java/com/provectus/kafka/ui/kafka/KafkaService.java

@@ -1,21 +1,20 @@
 package com.provectus.kafka.ui.kafka;
 
 import com.provectus.kafka.ui.cluster.model.KafkaCluster;
-import com.provectus.kafka.ui.cluster.model.MetricsConstants;
 import com.provectus.kafka.ui.model.*;
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
 import lombok.extern.log4j.Log4j2;
 import org.apache.kafka.clients.admin.*;
-import org.apache.kafka.common.KafkaFuture;
-import org.apache.kafka.common.Node;
-import org.apache.kafka.common.TopicPartitionInfo;
+import org.apache.kafka.common.*;
+import org.apache.kafka.common.config.ConfigResource;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
 
-import static com.provectus.kafka.ui.cluster.model.MetricsConstants.CLUSTER_ID;
+import static com.provectus.kafka.ui.kafka.KafkaConstants.*;
+import static org.apache.kafka.common.config.TopicConfig.MESSAGE_FORMAT_VERSION_CONFIG;
 
 @Service
 @RequiredArgsConstructor
@@ -35,14 +34,16 @@ public class KafkaService {
         }
 
         if (!isConnected) {
-            kafkaCluster.setStatus(ServerStatus.OFFLINE);
+            kafkaCluster.getCluster().setStatus(ServerStatus.OFFLINE);
 
             return;
         }
 
-        kafkaCluster.setStatus(ServerStatus.ONLINE);
+        kafkaCluster.setId(kafkaCluster.getAdminClient().describeCluster().clusterId().get());
+        kafkaCluster.getCluster().setId(kafkaCluster.getAdminClient().describeCluster().clusterId().get());
+        kafkaCluster.getCluster().setStatus(ServerStatus.ONLINE);
         loadMetrics(kafkaCluster);
-        loadTopics(kafkaCluster);
+        loadTopicsData(kafkaCluster);
     }
 
     private boolean createAdminClient(KafkaCluster kafkaCluster) {
@@ -75,68 +76,90 @@ public class KafkaService {
     }
 
     @SneakyThrows
-    private void loadTopics(KafkaCluster kafkaCluster) {
+    private void loadTopicsData(KafkaCluster kafkaCluster) {
         AdminClient adminClient = kafkaCluster.getAdminClient();
         ListTopicsOptions listTopicsOptions = new ListTopicsOptions();
         listTopicsOptions.listInternal(true);
         var topicListings = adminClient.listTopics(listTopicsOptions).names().get();
+        kafkaCluster.getCluster().setTopicCount(topicListings.size());
 
         DescribeTopicsResult topicDescriptionsWrapper = adminClient.describeTopics(topicListings);
         Map<String, KafkaFuture<TopicDescription>> topicDescriptionFuturesMap = topicDescriptionsWrapper.values();
         List<Topic> foundTopics = new ArrayList<>();
-        String clusterId = kafkaCluster.getMetricsMap().get(CLUSTER_ID);
+        resetMetrics(kafkaCluster);
 
         for (var entry : topicDescriptionFuturesMap.entrySet()) {
             var topicDescription = getTopicDescription(entry);
             if (topicDescription == null) continue;
-
-            Topic topic = collectTopicData(clusterId, topicDescription);
-            TopicDetails topicDetails = kafkaCluster.getTopicDetails(entry.getKey());
-            collectTopicDetailsData(topicDetails, topicDescription);
-
+            Topic topic = collectTopicData(kafkaCluster, topicDescription);
             foundTopics.add(topic);
         }
         kafkaCluster.setTopics(foundTopics);
     }
 
-    private void collectTopicDetailsData(TopicDetails topicDetails, TopicDescription topicDescription) {
-        int inSyncReplicas = 0, replicas = 0;
-        for (TopicPartitionInfo partition : topicDescription.partitions()) {
-            inSyncReplicas += partition.isr().size();
-            replicas += partition.replicas().size();
-        }
-
-        topicDetails.setReplicas(replicas);
-        topicDetails.setPartitionCount(topicDescription.partitions().size());
-        topicDetails.setInSyncReplicas(inSyncReplicas);
-        topicDetails.setReplicationFactor(topicDescription.partitions().size() > 0
-                ? topicDescription.partitions().get(0).replicas().size()
-                : null);
+    private void resetMetrics(KafkaCluster kafkaCluster) {
+        kafkaCluster.getBrokersMetrics().setOnlinePartitionCount(0);
+        kafkaCluster.getBrokersMetrics().setOfflinePartitionCount(0);
+        kafkaCluster.getBrokersMetrics().setUnderReplicatedPartitionCount(0);
     }
 
-    private Topic collectTopicData(String clusterId, TopicDescription topicDescription) {
-        var topic = new Topic().clusterId(clusterId);
+    private Topic collectTopicData(KafkaCluster kafkaCluster, TopicDescription topicDescription) {
+        var topic = new Topic().clusterId(kafkaCluster.getId());
         topic.setInternal(topicDescription.isInternal());
         topic.setName(topicDescription.name());
+
+        int inSyncReplicasCount = 0, replicasCount = 0;
         List<Partition> partitions = new ArrayList<>();
 
+        int urpCount = 0;
         for (TopicPartitionInfo partition : topicDescription.partitions()) {
             var partitionDto = new Partition();
             partitionDto.setLeader(partition.leader().id());
             partitionDto.setPartition(partition.partition());
             List<Replica> replicas = new ArrayList<>();
+
+            boolean isUrp = false;
             for (Node replicaNode : partition.replicas()) {
                 var replica = new Replica();
                 replica.setBroker(replicaNode.id());
                 replica.setLeader(partition.leader() != null && partition.leader().id() == replicaNode.id());
                 replica.setInSync(partition.isr().contains(replicaNode));
+                if (!replica.getInSync()) {
+                    isUrp = true;
+                }
                 replicas.add(replica);
+
+                inSyncReplicasCount += partition.isr().size();
+                replicasCount += partition.replicas().size();
+            }
+            if (isUrp) {
+                urpCount++;
             }
             partitionDto.setReplicas(replicas);
             partitions.add(partitionDto);
+
+            if (partition.leader() != null) {
+                kafkaCluster.getBrokersMetrics().setOnlinePartitionCount(kafkaCluster.getBrokersMetrics().getOnlinePartitionCount() + 1);
+            } else {
+                kafkaCluster.getBrokersMetrics().setOfflinePartitionCount(kafkaCluster.getBrokersMetrics().getOfflinePartitionCount() + 1);
+            }
         }
+        kafkaCluster.getCluster().setOnlinePartitionCount(kafkaCluster.getBrokersMetrics().getOnlinePartitionCount());
+        kafkaCluster.getBrokersMetrics().setUnderReplicatedPartitionCount(
+                kafkaCluster.getBrokersMetrics().getUnderReplicatedPartitionCount() + urpCount);
         topic.setPartitions(partitions);
 
+        TopicDetails topicDetails = kafkaCluster.getTopicDetails(topicDescription.name());
+        topicDetails.setReplicas(replicasCount);
+        topicDetails.setPartitionCount(topicDescription.partitions().size());
+        topicDetails.setInSyncReplicas(inSyncReplicasCount);
+        topicDetails.setReplicationFactor(topicDescription.partitions().size() > 0
+                ? topicDescription.partitions().get(0).replicas().size()
+                : null);
+        topicDetails.setUnderReplicatedPartitions(urpCount);
+
+        loadTopicConfig(kafkaCluster, topicDescription.name());
+
         return topic;
     }
 
@@ -152,17 +175,46 @@ public class KafkaService {
 
     private void loadMetrics(KafkaCluster kafkaCluster) throws InterruptedException, java.util.concurrent.ExecutionException {
         AdminClient adminClient = kafkaCluster.getAdminClient();
-        kafkaCluster.putMetric(MetricsConstants.BROKERS_COUNT, String.valueOf(adminClient.describeCluster().nodes().get().size()));
-        ListTopicsOptions listTopicsOptions = new ListTopicsOptions();
-        listTopicsOptions.listInternal(false);
-        Set<String> topicNames = adminClient.listTopics(listTopicsOptions).names().get();
-        kafkaCluster.putMetric(MetricsConstants.TOPIC_COUNT, String.valueOf(topicNames.size()));
-
-        int partitionsNum = 0;
-        DescribeTopicsResult describeTopicsResult = adminClient.describeTopics(topicNames);
-        for (TopicDescription topicDescription : describeTopicsResult.all().get().values()) {
-            partitionsNum += topicDescription.partitions().size();
+        int brokerCount = adminClient.describeCluster().nodes().get().size();
+        kafkaCluster.getCluster().setBrokerCount(brokerCount);
+        kafkaCluster.getBrokersMetrics().setBrokerCount(brokerCount);
+        kafkaCluster.getBrokersMetrics().setActiveControllers(adminClient.describeCluster().controller().get() != null ? 1 : 0);
+
+        for (Map.Entry<MetricName, ? extends Metric> metricNameEntry : adminClient.metrics().entrySet()) {
+            if (metricNameEntry.getKey().name().equals(IN_BYTE_PER_SEC_METRIC)
+                    && metricNameEntry.getKey().description().equals(IN_BYTE_PER_SEC_METRIC_DESCRIPTION)) {
+                kafkaCluster.getCluster().setBytesInPerSec((int) Math.round((double) metricNameEntry.getValue().metricValue()));
+            }
+            if (metricNameEntry.getKey().name().equals(OUT_BYTE_PER_SEC_METRIC)
+                    && metricNameEntry.getKey().description().equals(OUT_BYTE_PER_SEC_METRIC_DESCRIPTION)) {
+                kafkaCluster.getCluster().setBytesOutPerSec((int) Math.round((double) metricNameEntry.getValue().metricValue()));
+            }
         }
-        kafkaCluster.putMetric(MetricsConstants.PARTITIONS_COUNT, String.valueOf(partitionsNum));
+    }
+
+    @SneakyThrows
+    private void loadTopicConfig(KafkaCluster kafkaCluster, String topicName) {
+        AdminClient adminClient = kafkaCluster.getAdminClient();
+
+        Set<ConfigResource> resources = Collections.singleton(new ConfigResource(ConfigResource.Type.TOPIC, "messages"));
+        final Map<ConfigResource, Config> configs = adminClient.describeConfigs(resources).all().get();
+
+        if (configs.isEmpty()) return;
+
+        Collection<ConfigEntry> entries = configs.values().iterator().next().entries();
+        List<TopicConfig> topicConfigs = new ArrayList<>();
+        for (ConfigEntry entry : entries) {
+            TopicConfig topicConfig = new TopicConfig();
+            topicConfig.setName(entry.name());
+            topicConfig.setValue(entry.value());
+            if (topicConfig.getName().equals(MESSAGE_FORMAT_VERSION_CONFIG)) {
+                topicConfig.setDefaultValue(topicConfig.getValue());
+            } else {
+                topicConfig.setDefaultValue(TOPIC_DEFAULT_CONFIGS.get(entry.name()));
+            }
+            topicConfigs.add(topicConfig);
+        }
+
+        kafkaCluster.getTopicConfigsMap().put(topicName, topicConfigs);
     }
 }

+ 8 - 6
kafka-ui-api/src/main/java/com/provectus/kafka/ui/rest/MetricsRestController.java

@@ -2,10 +2,7 @@ package com.provectus.kafka.ui.rest;
 
 import com.provectus.kafka.ui.api.ClustersApi;
 import com.provectus.kafka.ui.cluster.service.ClusterService;
-import com.provectus.kafka.ui.model.BrokerMetrics;
-import com.provectus.kafka.ui.model.Cluster;
-import com.provectus.kafka.ui.model.Topic;
-import com.provectus.kafka.ui.model.TopicDetails;
+import com.provectus.kafka.ui.model.*;
 import lombok.RequiredArgsConstructor;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
@@ -25,8 +22,8 @@ public class MetricsRestController implements ClustersApi {
     }
 
     @Override
-    public Mono<ResponseEntity<BrokerMetrics>> getBrokersMetrics(String clusterId, ServerWebExchange exchange) {
-        return clusterService.getBrokerMetrics(clusterId);
+    public Mono<ResponseEntity<BrokersMetrics>> getBrokersMetrics(String clusterId, ServerWebExchange exchange) {
+        return clusterService.getBrokersMetrics(clusterId);
     }
 
     @Override
@@ -38,4 +35,9 @@ public class MetricsRestController implements ClustersApi {
     public Mono<ResponseEntity<TopicDetails>> getTopicDetails(String clusterId, String topicName, ServerWebExchange exchange) {
         return clusterService.getTopicDetails(clusterId, topicName);
     }
+
+    @Override
+    public Mono<ResponseEntity<Flux<TopicConfig>>> getTopicConfigs(String clusterId, String topicName, ServerWebExchange exchange) {
+        return clusterService.getTopicConfigs(clusterId, topicName);
+    }
 }

+ 10 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/zookeeper/ZooKeeperConstants.java

@@ -0,0 +1,10 @@
+package com.provectus.kafka.ui.zookeeper;
+
+public final class ZooKeeperConstants {
+
+    private ZooKeeperConstants() {}
+
+    public static int ONLINE = 1;
+    public static int OFFLINE = 0;
+
+}

+ 2 - 7
kafka-ui-api/src/main/java/com/provectus/kafka/ui/zookeeper/ZookeeperService.java

@@ -1,15 +1,12 @@
 package com.provectus.kafka.ui.zookeeper;
 
 import com.provectus.kafka.ui.cluster.model.KafkaCluster;
-import com.provectus.kafka.ui.model.ServerStatus;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 import org.I0Itec.zkclient.ZkClient;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
-import static com.provectus.kafka.ui.cluster.model.MetricsConstants.ZOOKEEPER_STATUS;
-
 @Service
 @RequiredArgsConstructor
 @Log4j2
@@ -27,14 +24,12 @@ public class ZookeeperService {
         }
 
         if (!isConnected) {
-            kafkaCluster.putMetric(ZOOKEEPER_STATUS, "0");
-            kafkaCluster.setZookeeperStatus(ServerStatus.OFFLINE);
+            kafkaCluster.getBrokersMetrics().setZooKeeperStatus(ZooKeeperConstants.OFFLINE);
 
             return;
         }
 
-        kafkaCluster.putMetric(ZOOKEEPER_STATUS, "1");
-        kafkaCluster.setZookeeperStatus(ServerStatus.ONLINE);
+        kafkaCluster.getBrokersMetrics().setZooKeeperStatus(ZooKeeperConstants.ONLINE);
     }
 
     private boolean createZookeeperConnection(KafkaCluster kafkaCluster) {

+ 52 - 15
kafka-ui-contract/src/main/resources/swagger/kafka-ui-api.yaml

@@ -48,7 +48,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/BrokerMetrics'
+                $ref: '#/components/schemas/BrokersMetrics'
 
   /clusters/{clusterId}/topics:
     get:
@@ -97,6 +97,33 @@ paths:
               schema:
                 $ref: '#/components/schemas/TopicDetails'
 
+  /clusters/{clusterId}/topics/{topicName}/config:
+    get:
+      tags:
+        - /clusters
+      summary: getTopicConfigs
+      operationId: getTopicConfigs
+      parameters:
+        - name: clusterId
+          in: path
+          required: true
+          schema:
+            type: string
+        - name: topicName
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/TopicConfig'
+
 components:
   schemas:
     Cluster:
@@ -131,7 +158,7 @@ components:
         - online
         - offline
 
-    BrokerMetrics:
+    BrokersMetrics:
       type: object
       properties:
         clusterId:
@@ -167,9 +194,9 @@ components:
       type: object
       properties:
         brokerId:
-          type: int
+          type: integer
         segmentSize:
-          type: int
+          type: integer
 
     Topic:
       type: object
@@ -189,9 +216,9 @@ components:
       type: object
       properties:
         partition:
-          type: int
+          type: integer
         leader:
-          type: int
+          type: integer
         replicas:
           type: array
           items:
@@ -201,7 +228,7 @@ components:
       type: object
       properties:
         broker:
-          type: int
+          type: integer
         leader:
           type: boolean
         inSync:
@@ -211,18 +238,28 @@ components:
       type: object
       properties:
         partitionCount:
-          type: int
+          type: integer
         replicationFactor:
-          type: int
+          type: integer
         replicas:
-          type: int
+          type: integer
         inSyncReplicas:
-          type: int
+          type: integer
         bytesInPerSec:
-          type: int
+          type: integer
         segmentSize:
-          type: int
+          type: integer
         segmentCount:
-          type: int
+          type: integer
         underReplicatedPartitions:
-          type: int
+          type: integer
+
+    TopicConfig:
+      type: object
+      properties:
+        name:
+          type: string
+        value:
+          type: string
+        defaultValue:
+          type: string