This is an automated email from the ASF dual-hosted git repository.

nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-webui.git


The following commit(s) were added to refs/heads/main by this push:
     new 46c88c8  [Feature] Introduce cluster management interface (#200)
46c88c8 is described below

commit 46c88c8114e8fd75fdebd98fe2e9bb14e895a73e
Author: s7monk <[email protected]>
AuthorDate: Mon May 6 11:30:39 2024 +0800

    [Feature] Introduce cluster management interface (#200)
---
 .../web/server/controller/ClusterController.java   |  97 +++++++++++
 .../paimon/web/server/data/model/ClusterInfo.java  |  48 ++++++
 .../web/server/data/result/enums/Status.java       |   7 +-
 .../paimon/web/server/mapper/ClusterMapper.java    |  35 ++++
 .../paimon/web/server/service/ClusterService.java  |  37 ++++
 .../server/service/impl/ClusterServiceImpl.java    |  57 +++++++
 .../src/main/resources/i18n/messages.properties    |   2 +
 .../main/resources/i18n/messages_en_US.properties  |   2 +
 .../main/resources/i18n/messages_zh_CN.properties  |   2 +
 .../src/main/resources/mapper/ClusterMapper.xml    |  67 ++++++++
 .../server/controller/ClusterControllerTest.java   | 189 +++++++++++++++++++++
 scripts/sql/paimon-mysql.sql                       |  13 ++
 12 files changed, 555 insertions(+), 1 deletion(-)

diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/ClusterController.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/ClusterController.java
new file mode 100644
index 0000000..60c7918
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/ClusterController.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.ClusterInfo;
+import org.apache.paimon.web.server.data.result.PageR;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.data.result.enums.Status;
+import org.apache.paimon.web.server.service.ClusterService;
+import org.apache.paimon.web.server.util.PageSupport;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/** Cluster api controller. */
+@RestController
+@RequestMapping("/api/cluster")
+public class ClusterController {
+
+    @Autowired private ClusterService clusterService;
+
+    @SaCheckPermission("system:cluster:query")
+    @GetMapping("/{id}")
+    public R<ClusterInfo> getCluster(@PathVariable("id") Integer id) {
+        ClusterInfo clusterInfo = clusterService.getById(id);
+        if (clusterInfo == null) {
+            return R.failed(Status.CLUSTER_NOT_EXIST);
+        }
+        return R.succeed(clusterInfo);
+    }
+
+    @SaCheckPermission("system:cluster:list")
+    @GetMapping("/list")
+    public PageR<ClusterInfo> listClusters(ClusterInfo clusterInfo) {
+        IPage<ClusterInfo> page = PageSupport.startPage();
+        List<ClusterInfo> clusterInfos = clusterService.listUsers(page, 
clusterInfo);
+        return PageR.<ClusterInfo>builder()
+                .success(true)
+                .total(page.getTotal())
+                .data(clusterInfos)
+                .build();
+    }
+
+    @SaCheckPermission("system:cluster:add")
+    @PostMapping
+    public R<Void> add(@Validated @RequestBody ClusterInfo clusterInfo) {
+        if (!clusterService.checkClusterNameUnique(clusterInfo)) {
+            return R.failed(Status.CLUSTER_NAME_ALREADY_EXISTS, 
clusterInfo.getClusterName());
+        }
+
+        return clusterService.save(clusterInfo) ? R.succeed() : R.failed();
+    }
+
+    @SaCheckPermission("system:cluster:update")
+    @PutMapping
+    public R<Void> update(@Validated @RequestBody ClusterInfo clusterInfo) {
+        if (!clusterService.checkClusterNameUnique(clusterInfo)) {
+            return R.failed(Status.CLUSTER_NAME_ALREADY_EXISTS, 
clusterInfo.getClusterName());
+        }
+
+        return clusterService.updateById(clusterInfo) ? R.succeed() : 
R.failed();
+    }
+
+    @SaCheckPermission("system:cluster:delete")
+    @DeleteMapping("/{clusterIds}")
+    public R<Void> delete(@PathVariable Integer[] clusterIds) {
+        return clusterService.deleteClusterByIds(clusterIds) > 0 ? R.succeed() 
: R.failed();
+    }
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/ClusterInfo.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/ClusterInfo.java
new file mode 100644
index 0000000..edf319e
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/ClusterInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.server.data.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/** Cluster table model. */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("cluster")
+public class ClusterInfo extends BaseModel {
+
+    private static final long serialVersionUID = 1L;
+
+    private String clusterName;
+
+    private String host;
+
+    private Integer port;
+
+    private String type;
+
+    private Boolean enabled;
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
index 0f6b314..29baaa1 100644
--- 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
@@ -77,8 +77,13 @@ public enum Status {
     TABLE_DROP_ERROR(10510, "table.drop.error"),
     TABLE_RENAME_ERROR(10511, "table.rename.error"),
 
+    /** ------------cdc-----------------. */
     CDC_JOB_EXIST_ERROR(10601, "cdc.job.exist.error"),
-    CDC_JOB_NO_EXIST_ERROR(10602, "cdc.job.not.exist.error");
+    CDC_JOB_NO_EXIST_ERROR(10602, "cdc.job.not.exist.error"),
+
+    /** ------------cluster-----------------. */
+    CLUSTER_NOT_EXIST(10701, "cluster.not.exist"),
+    CLUSTER_NAME_ALREADY_EXISTS(10702, "cluster.name.exist");
 
     private final int code;
     private final String msg;
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/mapper/ClusterMapper.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/mapper/ClusterMapper.java
new file mode 100644
index 0000000..d3dc649
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/mapper/ClusterMapper.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.server.mapper;
+
+import org.apache.paimon.web.server.data.model.ClusterInfo;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/** Cluster mapper. */
+@Mapper
+public interface ClusterMapper extends BaseMapper<ClusterInfo> {
+
+    List<ClusterInfo> listClusters(IPage<ClusterInfo> page, @Param("cluster") 
ClusterInfo cluster);
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/ClusterService.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/ClusterService.java
new file mode 100644
index 0000000..0ed8e56
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/ClusterService.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.server.service;
+
+import org.apache.paimon.web.server.data.model.ClusterInfo;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/** Cluster Service. */
+public interface ClusterService extends IService<ClusterInfo> {
+
+    List<ClusterInfo> listUsers(IPage<ClusterInfo> page, @Param("cluster") 
ClusterInfo cluster);
+
+    boolean checkClusterNameUnique(ClusterInfo cluster);
+
+    int deleteClusterByIds(Integer[] clusterIds);
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/ClusterServiceImpl.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/ClusterServiceImpl.java
new file mode 100644
index 0000000..45a389e
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/ClusterServiceImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.server.service.impl;
+
+import org.apache.paimon.web.server.data.model.ClusterInfo;
+import org.apache.paimon.web.server.mapper.ClusterMapper;
+import org.apache.paimon.web.server.service.ClusterService;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** The implementation of {@link ClusterService}. */
+@Service
+public class ClusterServiceImpl extends ServiceImpl<ClusterMapper, ClusterInfo>
+        implements ClusterService {
+
+    @Autowired private ClusterMapper clusterMapper;
+
+    @Override
+    public List<ClusterInfo> listUsers(IPage<ClusterInfo> page, ClusterInfo 
cluster) {
+        return clusterMapper.listClusters(page, cluster);
+    }
+
+    @Override
+    public boolean checkClusterNameUnique(ClusterInfo cluster) {
+        int clusterId = cluster.getId() == null ? -1 : cluster.getId();
+        ClusterInfo info =
+                this.lambdaQuery().eq(ClusterInfo::getClusterName, 
cluster.getClusterName()).one();
+        return info == null || info.getId() == clusterId;
+    }
+
+    @Override
+    public int deleteClusterByIds(Integer[] clusterIds) {
+        return clusterMapper.deleteBatchIds(Arrays.asList(clusterIds));
+    }
+}
diff --git a/paimon-web-server/src/main/resources/i18n/messages.properties 
b/paimon-web-server/src/main/resources/i18n/messages.properties
index 35f3a68..96ae9d1 100644
--- a/paimon-web-server/src/main/resources/i18n/messages.properties
+++ b/paimon-web-server/src/main/resources/i18n/messages.properties
@@ -51,3 +51,5 @@ table.drop.error=Exception calling Paimon Catalog API to drop 
a table.
 table.rename.error=Exception calling Paimon Catalog API to rename a table.
 cdc.job.exist.error=Paimon CDC job exists.
 cdc.job.not.exist.error=Paimon CDC job is not exist.
+cluster.not.exist=This cluster is not exist.
+cluster.name.exist=This cluster name {0} already exists.
diff --git 
a/paimon-web-server/src/main/resources/i18n/messages_en_US.properties 
b/paimon-web-server/src/main/resources/i18n/messages_en_US.properties
index 35f3a68..96ae9d1 100644
--- a/paimon-web-server/src/main/resources/i18n/messages_en_US.properties
+++ b/paimon-web-server/src/main/resources/i18n/messages_en_US.properties
@@ -51,3 +51,5 @@ table.drop.error=Exception calling Paimon Catalog API to drop 
a table.
 table.rename.error=Exception calling Paimon Catalog API to rename a table.
 cdc.job.exist.error=Paimon CDC job exists.
 cdc.job.not.exist.error=Paimon CDC job is not exist.
+cluster.not.exist=This cluster is not exist.
+cluster.name.exist=This cluster name {0} already exists.
diff --git 
a/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties 
b/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties
index 96ca9fa..d61de0c 100644
--- a/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties
+++ b/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties
@@ -51,3 +51,5 @@ table.drop.error=\u8C03\u7528 Paimon API \u5220\u9664 Table 
\u65F6\u53D1\u751F\u
 table.rename.error=\u8C03\u7528 Paimon API \u91CD\u547D\u540D Table 
\u65F6\u53D1\u751F\u5F02\u5E38
 cdc.job.exist.error=paimon cdc\u4F5C\u4E1A\u5DF2\u5B58\u5728
 cdc.job.not.exist.error=paimon cdc\u4F5C\u4E1A\u4E0D\u5B58\u5728
+cluster.not.exist=\u6B64\u96C6\u7FA4\u4E0D\u5B58\u5728
+cluster.name.exist=\u6B64\u96C6\u7FA4\u540D{0}\u5DF2\u7ECF\u5B58\u5728
diff --git a/paimon-web-server/src/main/resources/mapper/ClusterMapper.xml 
b/paimon-web-server/src/main/resources/mapper/ClusterMapper.xml
new file mode 100644
index 0000000..3f4c9ca
--- /dev/null
+++ b/paimon-web-server/src/main/resources/mapper/ClusterMapper.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd";>
+<mapper namespace="org.apache.paimon.web.server.mapper.ClusterMapper">
+
+    <resultMap id="SysClusterResult" 
type="org.apache.paimon.web.server.data.model.ClusterInfo">
+        <id     property="id"       column="id"      />
+        <result property="clusterName"     column="cluster_name"    />
+        <result property="host"     column="host"    />
+        <result property="port"     column="port"    />
+        <result property="type"        column="type"        />
+        <result property="enabled"     column="enabled"     />
+        <result property="createTime"   column="create_time"  />
+        <result property="updateTime"   column="update_time"  />
+    </resultMap>
+
+    <sql id="selectClusterVo">
+        select id, cluster_name, host, port, `type`, enabled, create_time, 
update_time from cluster
+    </sql>
+
+    <select id="listClusters" 
parameterType="org.apache.paimon.web.server.data.model.ClusterInfo" 
resultMap="SysClusterResult">
+        <include refid="selectClusterVo"/>
+        <if test="cluster.id != null and cluster.id != 0">
+            AND id = #{cluster.id}
+        </if>
+        <if test="cluster.clusterName != null and cluster.clusterName != ''">
+            AND cluser_name like concat('%', #{cluster.clusterName}, '%')
+        </if>
+        <if test="cluster.host != null and cluster.host != ''">
+            AND host = #{cluster.host}
+        </if>
+        <if test="cluster.port != null">
+            AND port = #{cluster.port}
+        </if>
+        <if test="cluster.type != null and cluster.type != ''">
+            AND `type` = #{cluster.type}
+        </if>
+        <if test="cluster.enabled != null">
+            AND enabled = #{cluster.enabled}
+        </if>
+        <if test="cluster.params.beginTime != null and 
cluster.params.beginTime != ''"><!-- Start time search -->
+            AND date_format(create_time,'%y%m%d') &gt;= 
date_format(#{cluster.params.beginTime},'%y%m%d')
+        </if>
+        <if test="cluster.params.endTime != null and cluster.params.endTime != 
''"><!-- End time search -->
+            AND date_format(create_time,'%y%m%d') &lt;= 
date_format(#{cluster.params.endTime},'%y%m%d')
+        </if>
+    </select>
+</mapper>
\ No newline at end of file
diff --git 
a/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/ClusterControllerTest.java
 
b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/ClusterControllerTest.java
new file mode 100644
index 0000000..3a816ca
--- /dev/null
+++ 
b/paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/ClusterControllerTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.ClusterInfo;
+import org.apache.paimon.web.server.data.result.PageR;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** Test for {@link ClusterController}. */
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class ClusterControllerTest extends ControllerTestBase {
+
+    private static final String clusterPath = "/api/cluster";
+
+    private static final int clusterId = 1;
+    private static final String clusterName = "flink_test_cluster";
+
+    @Test
+    @Order(1)
+    public void testAddCluster() throws Exception {
+        ClusterInfo cluster = new ClusterInfo();
+        cluster.setId(clusterId);
+        cluster.setClusterName(clusterName);
+        cluster.setHost("127.0.0.1");
+        cluster.setPort(8083);
+        cluster.setType("Flink");
+        cluster.setEnabled(true);
+
+        mockMvc.perform(
+                        MockMvcRequestBuilders.post(clusterPath)
+                                .cookie(cookie)
+                                .content(ObjectMapperUtils.toJSON(cluster))
+                                .contentType(MediaType.APPLICATION_JSON_VALUE)
+                                .accept(MediaType.APPLICATION_JSON_VALUE))
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andDo(MockMvcResultHandlers.print());
+    }
+
+    @Test
+    @Order(2)
+    public void testGetCluster() throws Exception {
+        String responseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.get(clusterPath + "/" + 
clusterId)
+                                        .cookie(cookie)
+                                        
.contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        
.accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        R<ClusterInfo> r =
+                ObjectMapperUtils.fromJSON(responseString, new 
TypeReference<R<ClusterInfo>>() {});
+        assertEquals(200, r.getCode());
+        assertNotNull(r.getData());
+        assertEquals(clusterName, r.getData().getClusterName());
+        assertEquals("127.0.0.1", r.getData().getHost());
+        assertEquals(8083, r.getData().getPort());
+        assertEquals("Flink", r.getData().getType());
+        assertTrue(r.getData().getEnabled());
+    }
+
+    @Test
+    @Order(3)
+    public void testListClusters() throws Exception {
+        String responseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.get(clusterPath + 
"/list")
+                                        .cookie(cookie)
+                                        
.contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        
.accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        PageR<ClusterInfo> r =
+                ObjectMapperUtils.fromJSON(
+                        responseString, new 
TypeReference<PageR<ClusterInfo>>() {});
+        assertTrue(
+                r.getData() != null
+                        && ((r.getTotal() > 0 && r.getData().size() > 0)
+                                || (r.getTotal() == 0 && r.getData().size() == 
0)));
+
+        ClusterInfo clusterInfo = r.getData().get(0);
+        assertEquals(clusterName, clusterInfo.getClusterName());
+        assertEquals("127.0.0.1", clusterInfo.getHost());
+        assertEquals(8083, clusterInfo.getPort());
+        assertEquals("Flink", clusterInfo.getType());
+        assertTrue(clusterInfo.getEnabled());
+    }
+
+    @Test
+    @Order(4)
+    public void testUpdateCluster() throws Exception {
+        String newClusterName = clusterName + "-edit";
+        ClusterInfo cluster = new ClusterInfo();
+        cluster.setId(clusterId);
+        cluster.setClusterName(newClusterName);
+        cluster.setHost("127.0.0.1");
+        cluster.setPort(8083);
+        cluster.setType("Flink");
+        cluster.setEnabled(true);
+
+        mockMvc.perform(
+                        MockMvcRequestBuilders.put(clusterPath)
+                                .cookie(cookie)
+                                .content(ObjectMapperUtils.toJSON(cluster))
+                                .contentType(MediaType.APPLICATION_JSON_VALUE)
+                                .accept(MediaType.APPLICATION_JSON_VALUE))
+                .andExpect(MockMvcResultMatchers.status().isOk());
+
+        String responseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.get(clusterPath + "/" + 
clusterId)
+                                        .cookie(cookie)
+                                        
.contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        
.accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        R<ClusterInfo> r =
+                ObjectMapperUtils.fromJSON(responseString, new 
TypeReference<R<ClusterInfo>>() {});
+        assertEquals(200, r.getCode());
+        assertNotNull(r.getData());
+        assertEquals(r.getData().getClusterName(), newClusterName);
+    }
+
+    @Test
+    @Order(5)
+    public void testDeleteCluster() throws Exception {
+        String delResponseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.delete(
+                                                clusterPath + "/" + clusterId 
+ "," + clusterId)
+                                        .cookie(cookie)
+                                        
.contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        
.accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        R<?> result = ObjectMapperUtils.fromJSON(delResponseString, R.class);
+        assertEquals(200, result.getCode());
+    }
+}
diff --git a/scripts/sql/paimon-mysql.sql b/scripts/sql/paimon-mysql.sql
index db44cab..9c4b5b9 100644
--- a/scripts/sql/paimon-mysql.sql
+++ b/scripts/sql/paimon-mysql.sql
@@ -126,6 +126,19 @@ CREATE TABLE if not exists `catalog`
     options       varchar(512)                         null
     ) engine = innodb;
 
+DROP TABLE IF EXISTS `cluster`;
+CREATE TABLE if not exists `cluster`
+(
+    `id`          int(11)     not null auto_increment primary key comment 'id',
+    `cluster_name`  varchar(100)  comment 'cluster name',
+    `host`     varchar(100)  comment 'host',
+    `port`   int(11) COMMENT 'port',
+    `enabled`   tinyint(1)  NOT NULL DEFAULT 1 COMMENT 'enabled',
+    `type`    varchar(100)  comment 'cluster type',
+    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create 
time',
+    `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update 
time'
+    )  ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `cdc_job_definition`;
 CREATE TABLE if not exists `cdc_job_definition`
 (

Reply via email to