From 7de0d0623b51bec537cfe687a62b351ceed3676a Mon Sep 17 00:00:00 2001
From: xue_meng_en <1836611252@qq.com>
Date: Thu, 15 Sep 2022 21:57:27 +0800
Subject: [PATCH] =?UTF-8?q?cm=E6=94=AF=E6=8C=81=E9=9B=86=E7=BE=A4=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF=E6=9F=A5=E8=AF=A2=E5=92=8C=E6=8E=A8=E9=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../applicationdemo/ApplicationDemo.java | 22 ++
.../applicationdemo/ApplicationServer.java | 52 +++
.../applicationdemo/RecvMasterInfoThread.java | 48 +++
build.sh | 1 +
pom.xml | 47 +++
.../org/opengauss/cmrestapi/CMRestAPI.java | 232 +++++++++++
.../opengauss/cmrestapi/CMRestAPIClient.java | 94 +++++
.../opengauss/cmrestapi/CMRestAPIServer.java | 367 ++++++++++++++++++
.../org/opengauss/cmrestapi/ErrorCode.java | 62 +++
.../opengauss/cmrestapi/InfoPushThread.java | 60 +++
.../opengauss/cmrestapi/InfoQueryThread.java | 51 +++
.../opengauss/cmrestapi/OGCmdExecuter.java | 346 +++++++++++++++++
.../cmrestapi/Role2PrimaryMonitor.java | 124 ++++++
src/main/resources/application.properties | 0
14 files changed, 1506 insertions(+)
create mode 100644 applicationdemo/src/main/java/com/application/applicationdemo/ApplicationDemo.java
create mode 100644 applicationdemo/src/main/java/com/application/applicationdemo/ApplicationServer.java
create mode 100644 applicationdemo/src/main/java/com/application/applicationdemo/RecvMasterInfoThread.java
create mode 100644 build.sh
create mode 100644 pom.xml
create mode 100644 src/main/java/org/opengauss/cmrestapi/CMRestAPI.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/CMRestAPIClient.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/CMRestAPIServer.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/ErrorCode.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/InfoPushThread.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/InfoQueryThread.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/OGCmdExecuter.java
create mode 100644 src/main/java/org/opengauss/cmrestapi/Role2PrimaryMonitor.java
create mode 100644 src/main/resources/application.properties
diff --git a/applicationdemo/src/main/java/com/application/applicationdemo/ApplicationDemo.java b/applicationdemo/src/main/java/com/application/applicationdemo/ApplicationDemo.java
new file mode 100644
index 0000000..266ff88
--- /dev/null
+++ b/applicationdemo/src/main/java/com/application/applicationdemo/ApplicationDemo.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package com.application.applicationdemo;
+
+public class ApplicationDemo {
+ public static void main(String[] args) {
+ new RecvMasterInfoThread().start();
+ }
+
+}
diff --git a/applicationdemo/src/main/java/com/application/applicationdemo/ApplicationServer.java b/applicationdemo/src/main/java/com/application/applicationdemo/ApplicationServer.java
new file mode 100644
index 0000000..c0a5f27
--- /dev/null
+++ b/applicationdemo/src/main/java/com/application/applicationdemo/ApplicationServer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package com.application.applicationdemo;
+
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @Title: ApplicationServer
+ * @author: xuemengen
+ * @Description: Listen and receive info push request.
+ * Created on: 2022/09/14
+ */
+@RestController
+public class ApplicationServer {
+ /**
+ * @Title: receiveMasterAndStandbyInfo
+ * @Description:
+ * Receive master and standby info.
+ * @param masterIpPort
+ * @param stanbysInfo
+ * @return
+ * boolean
+ */
+ @PutMapping("/CMRestAPI")
+ public boolean receiveMasterAndStandbyInfo(
+ @RequestParam(value = "MasterIpPort", required = false, defaultValue = "")String masterIpPort,
+ @RequestParam(value = "StanbysInfo", required = false, defaultValue = "")String stanbysInfo) {
+ if (masterIpPort != null && !"".equals(masterIpPort)) {
+ // handle master info
+ System.out.println("Received put master info request, current master info is " + masterIpPort);
+ }
+ if (stanbysInfo != null && !"".equals(stanbysInfo)) {
+ // handle standbys info
+ System.out.println("Received put standbys info request, current standbys info is " + stanbysInfo);
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/applicationdemo/src/main/java/com/application/applicationdemo/RecvMasterInfoThread.java b/applicationdemo/src/main/java/com/application/applicationdemo/RecvMasterInfoThread.java
new file mode 100644
index 0000000..677e731
--- /dev/null
+++ b/applicationdemo/src/main/java/com/application/applicationdemo/RecvMasterInfoThread.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package com.application.applicationdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @Title: RecvMasterInfoThread
+ * @author: xuemengen
+ * @Description: RecvMasterInfoThread
+ * Created on: 2022/09/14
+ */
+@SpringBootApplication
+public class RecvMasterInfoThread implements Runnable {
+ private Thread t;
+ private String threadName;
+
+ public RecvMasterInfoThread() {
+ threadName = "RecvMasterInfoThread";
+ }
+
+ void start() {
+ System.out.println("Starting " + threadName );
+ if (t == null) {
+ t = new Thread (this, threadName);
+ t.start ();
+ }
+ }
+
+ @Override
+ public void run() {
+ SpringApplication.run(RecvMasterInfoThread.class);
+ }
+
+}
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..bb9071f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1 @@
+mvn install package
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3cce4f7
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.3
+
+
+ org.opengauss
+ cmrestapi
+ 3.1.0-RELEASE
+ CMRestAPI
+ Project for CMRestAPI
+
+ 1.8
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ com.google.code.gson
+ gson
+ 2.9.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/src/main/java/org/opengauss/cmrestapi/CMRestAPI.java b/src/main/java/org/opengauss/cmrestapi/CMRestAPI.java
new file mode 100644
index 0000000..efea7de
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/CMRestAPI.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import org.opengauss.cmrestapi.OGCmdExecuter.CmdResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Title: CMRestAPI
+ * @author: xuemengen
+ * @Description:
+ * Main entry.
+ * (1) Initialization, acquisition and verification of information required by the program.
+ * (2) Start the cluster information query server thread -- InfoQueryThread.
+ * (3) Start the thread of monitoring database role change to primary -- Role2PrimaryMonitor.
+ * Created on: 2022/09/07
+ */
+public class CMRestAPI {
+ public final String APPLICATION_NAME = "CMRestAPI";
+ public static String envFile;
+ public static String dataPath;
+ public static List recvAddrList;
+ public static String prefix = "/CMRestAPI/RecvAddrList/";
+ public static String hostIp = null;
+ public static String port = null;
+ public static int nodeId;
+ public static String peerIpPorts = null;
+ public static OGCmdExecuter ogCmdExecuter = null;
+ public static String appWhiteListFile = null;
+ public static HashSet appWhiteList = null;
+ public static Long lastModified = 0l;
+ private static Logger logger = LoggerFactory.getLogger(CMRestAPI.class);
+ private static final String CHECK_GAUSSDB_PROCESS_CMD =
+ "ps ux | grep -v grep | grep \"bin/gaussdb -D \" | awk '{print $2}'";
+ /**
+ * @Title: main
+ * @Description:
+ * Main entry.
+ * @param args
+ * -e enfFile
+ * [-w appWhiteListFile]
+ */
+ public static void main(String[] args) {
+ parseAndCheckCmdLine(args);
+ checkEnvfileAndDataPath();
+ ogCmdExecuter = new OGCmdExecuter(envFile);
+ if (!checkGaussdbRunning()) {
+ logger.error("Gaussdb is not running, waiting for more than 30s, exit!");
+ System.exit(ErrorCode.ESRCH.getCode());
+ }
+ getClusterStaticInfo();
+ if (appWhiteListFile != null) {
+ checkAppWhiteListFile();
+ getAppWhiteList();
+ }
+ new Role2PrimaryMonitor().start();
+ new InfoQueryThread().start();
+ }
+
+ private static void getClusterStaticInfo() {
+ CmdResult cmdResult = ogCmdExecuter.cmctlViewNative();
+ if (cmdResult == null) {
+ logger.error("Failed to get static cluster info.");
+ System.exit(ErrorCode.EUNKNOWN.getCode());
+ }
+ if (cmdResult.statusCode != 0 || "".equals(cmdResult.resultString)) {
+ logger.error("Failed to get static cluster info.");
+ System.exit(cmdResult.statusCode);
+ }
+ nodeId = Integer.parseInt(matchRegex("node:(\\d+)\\s+", cmdResult.resultString));
+ dataPath = matchRegex("datanodeLocalDataPath :(.+)\\s+", cmdResult.resultString);
+ hostIp = matchRegex("datanodeLocalHAIP 1:(.+)\\s+", cmdResult.resultString);
+ port = matchRegex("datanodePort :(.*)\\s+", cmdResult.resultString);
+ Pattern pattern = Pattern.compile("datanodePeer\\d+HAIP 1:(.+)\\s+datanodePeer\\d+HAPort :(.+)\\s+");
+ Matcher matcher = pattern.matcher(cmdResult.resultString);
+ if (peerIpPorts == null) {
+ peerIpPorts = "";
+ }
+ boolean isFirst = true;
+ while (matcher.find()) {
+ String peerIp = matcher.group(1);
+ String peerPortString = matcher.group(2);
+ int peerPort = Integer.parseInt(peerPortString) - 1;
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ peerIpPorts += ",";
+ }
+ peerIpPorts += peerIp + ":" + peerPort;
+ }
+ }
+
+ private static void checkEnvfileAndDataPath() {
+ File envFileFile = new File(envFile);
+ if (!envFileFile.isFile()) {
+ logger.error("{} is not a file!", envFile);
+ System.exit(ErrorCode.EISDIR.getCode());
+ }
+ if (!envFileFile.exists()) {
+ logger.error("{} is not exist!", envFile);
+ System.exit(ErrorCode.ENOENT.getCode());
+ }
+ String cmd = "source " + envFile + "; gaussdb -V";
+ CmdResult cmdResult = OGCmdExecuter.execCmd(cmd);
+ if (cmdResult == null || cmdResult.statusCode != 0) {
+ logger.error("env file {} is invalid!", envFile);
+ System.exit(ErrorCode.EINVAL.getCode());
+ }
+ }
+
+ private static void checkAppWhiteListFile() {
+ File file = new File(appWhiteListFile);
+ if (!file.exists()) {
+ logger.error("{} is not exist!", appWhiteListFile);
+ System.exit(ErrorCode.ENOENT.getCode());
+ }
+ if (!file.isFile()) {
+ logger.error("{} is not a file!", appWhiteListFile);
+ System.exit(ErrorCode.EISDIR.getCode());
+ }
+ }
+
+ /**
+ * @Title: appWhiteListFileModified
+ * @Description:
+ * check whether appWhiteListFile was modified.
+ * @return
+ * boolean
+ */
+ public static boolean appWhiteListFileModified() {
+ if (new File(appWhiteListFile).lastModified() > lastModified) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @Title: getAppWhiteList
+ * @Description:
+ * get appWhiteList from appWhiteListFile.
+ * void
+ */
+ public static void getAppWhiteList() {
+ try {
+ if (appWhiteList == null) {
+ appWhiteList = new HashSet();
+ } else {
+ appWhiteList.clear();
+ }
+ BufferedReader br = new BufferedReader(new FileReader(appWhiteListFile));
+ String line = null;
+ while ((line = br.readLine()) != null) {
+ appWhiteList.add(line);
+ }
+ br.close();
+ } catch (IOException e) {
+ appWhiteList = null;
+ logger.error(e.getMessage());
+ }
+ }
+
+ private static boolean checkGaussdbRunning() {
+ boolean isRunning = false;
+ for (int i = 0; i < 10; ++i) {
+ CmdResult cmdResult = OGCmdExecuter.execCmd(CHECK_GAUSSDB_PROCESS_CMD);
+ if (cmdResult != null && cmdResult.statusCode == 0 &&
+ !"".equals(cmdResult.resultString)) {
+ isRunning = true;
+ break;
+ }
+ logger.info("gaussdb is not running, waiting");
+ try {
+ Thread.sleep(6000);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ }
+ }
+ return isRunning;
+ }
+
+ private static void parseAndCheckCmdLine(String[] args) {
+ int argsLen = args.length;
+ if (argsLen < 2) {
+ System.out.println("-e envFile is needed!");
+ System.exit(ErrorCode.EINVAL.getCode());
+ }
+ for (int i = 0; i < argsLen; ++i) {
+ switch (args[i]) {
+ case "-e":
+ envFile = args[++i];
+ break;
+ case "-w":
+ appWhiteListFile = args[++i];
+ break;
+ default:
+ System.out.println("option " + args[i] + " is not support");
+ System.exit(ErrorCode.EINVAL.getCode());
+ }
+ }
+ }
+
+ public static String matchRegex(String patternString, String targetString) {
+ Pattern pattern = Pattern.compile(patternString);
+ Matcher matcher = pattern.matcher(targetString);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/opengauss/cmrestapi/CMRestAPIClient.java b/src/main/java/org/opengauss/cmrestapi/CMRestAPIClient.java
new file mode 100644
index 0000000..c6a8aed
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/CMRestAPIClient.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+
+/**
+ * @Title: CMRestAPIClient
+ * @author: xuemengen
+ * @Description:
+ * Client for pushing newest master info to server in application or other places.
+ * Created on: 2022/09/07
+ */
+public class CMRestAPIClient {
+ private String url;
+ private WebClient webClient;
+ private Logger logger = LoggerFactory.getLogger(CMRestAPIClient.class);
+
+ public CMRestAPIClient(String uri) {
+ this.url = uri;
+ this.webClient = WebClient.create(uri);
+ }
+
+ /**
+ * @Title: pushMasterInfo
+ * @Description:
+ * Push newest master info (ip and port) to url.
+ * @param masterIpPort
+ * void
+ */
+ public void pushMasterInfo(String masterIpPort) {
+ logger.info("Sendind newest master info({}) to {}", masterIpPort, url);
+ MultiValueMap bodyValues = new LinkedMultiValueMap<>();
+ bodyValues.add("MasterIpPort", masterIpPort);
+
+ try {
+ String response = webClient.put()
+ .uri("")
+ .accept(MediaType.APPLICATION_JSON)
+ .body(BodyInserters.fromFormData(bodyValues))
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ logger.info("Receive response {} from server.", response);
+ } catch (WebClientRequestException e) {
+ logger.error("The server {} maybe offline.\nDetail:", url, e);
+ }
+ }
+
+ /**
+ * @Title: pushStandbysInfo
+ * @Description:
+ * Push current standbys' info(ip:port) to url.
+ * void
+ */
+ public void pushStandbysInfo() {
+ String standbysInfo = CMRestAPI.peerIpPorts;
+ logger.info("Sendind newest standbys info({}) to {}", standbysInfo, url);
+ MultiValueMap bodyValues = new LinkedMultiValueMap<>();
+ bodyValues.add("StanbysInfo", standbysInfo);
+
+ try {
+ String response = webClient.put()
+ .uri("")
+ .accept(MediaType.APPLICATION_JSON)
+ .body(BodyInserters.fromFormData(bodyValues))
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ logger.info("Receive response {} from server.", response);
+ } catch (WebClientRequestException e) {
+ logger.error("The server {} maybe offline.\nDetail:", url, e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/org/opengauss/cmrestapi/CMRestAPIServer.java b/src/main/java/org/opengauss/cmrestapi/CMRestAPIServer.java
new file mode 100644
index 0000000..804dbbc
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/CMRestAPIServer.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import javax.annotation.PreDestroy;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.gson.Gson;
+import java.io.File;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+
+import org.opengauss.cmrestapi.OGCmdExecuter.CmdResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import java.util.ArrayList;
+
+/**
+ * @Title: CMRestAPIServer
+ * @author: xuemengen
+ * @Description:
+ * Server for listening request from application, manager platform etc.
+ * Created on: 2022/09/07
+ */
+@RestController
+@RequestMapping("/CMRestAPI")
+public class CMRestAPIServer {
+ private final String UNKNOWN = "unknown";
+ private final String LOCALHOST = "127.0.0.1";
+ private final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
+ private final String SEPARATOR = ",";
+ private Logger logger = LoggerFactory.getLogger(CMRestAPIServer.class);
+ private OGCmdExecuter ogCmdExcuter = new OGCmdExecuter(CMRestAPI.envFile);
+
+ private static final String[] IP_HEADER_CANDIDATES = {
+ "X-Forwarded-For",
+ "Proxy-Client-IP",
+ "WL-Proxy-Client-IP",
+ "HTTP_X_FORWARDED_FOR",
+ "HTTP_X_FORWARDED",
+ "HTTP_X_CLUSTER_CLIENT_IP",
+ "HTTP_CLIENT_IP",
+ "HTTP_FORWARDED_FOR",
+ "HTTP_FORWARDED",
+ "HTTP_VIA",
+ "REMOTE_ADDR"
+ };
+
+ /**
+ * @Title: NodeStatus
+ * @author: xuemengen
+ * @Description:
+ * Database node status.
+ * Created on: 2022/09/07
+ */
+ class NodeStatus {
+ String nodeIp;
+ String cmServerState;
+ String dnRole;
+ String dnState;
+ public NodeStatus(String nodeIp, String cmServerState, String dnRole, String dnState) {
+ this.nodeIp = nodeIp;
+ this.cmServerState = cmServerState;
+ this.dnRole = dnRole;
+ this.dnState = dnState;
+ }
+ }
+
+ /**
+ * @Title: ClusterStatus
+ * @author: xuemengen
+ * @Description:
+ * Cluster status.
+ * Created on: 2022/09/07
+ */
+ class ClusterStatus {
+ String clusterState;
+ ArrayList nodesStatus;
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ logger.info("Destroying CMRestAPI.");
+ }
+
+ private String getClientIp(HttpServletRequest request) {
+ String ipAddress = null;
+ /*
+ * In the multi-proxy scenario, the header is extracted to
+ * obtain the IP address list, and the first IP address is taken.
+ */
+ for (String header : IP_HEADER_CANDIDATES) {
+ String ipList = request.getHeader(header);
+ if (ipList == null || ipList.length() == 0 || UNKNOWN.equalsIgnoreCase(ipList)) {
+ continue;
+ }
+ ipAddress = ipList.split(SEPARATOR)[0];
+ }
+
+ /*
+ * The getRemoteAddr method is used to obtain
+ * the IP address without a proxy or SLB.
+ */
+ if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getRemoteAddr();
+ }
+
+ /*
+ * If the local IP address is used, the local IP
+ * address is configured based on the NIC.
+ */
+ if (LOCALHOST.equals(ipAddress) || LOCALHOST_IPV6.equals(ipAddress)) {
+ try {
+ ipAddress = Inet4Address.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ ipAddress = "localhost";
+ logger.error("Error when get localhost ip.\nDetail:", e.getMessage());
+ }
+ }
+ return ipAddress;
+ }
+
+ /**
+ * @Title: checkAppWhiteList
+ * @Description:
+ * Check whether client ip is in appWhiteList.
+ * Notice: if the appWhiteListFile does not exist, the verification
+ * is not required, then return true.
+ * @param request
+ * @return
+ * boolean
+ */
+ private boolean checkAppWhiteList(String clientIp) {
+ if (CMRestAPI.appWhiteListFile == null || !new File(CMRestAPI.appWhiteListFile).exists()) {
+ return true;
+ }
+ if (CMRestAPI.appWhiteListFileModified()) {
+ CMRestAPI.getAppWhiteList();
+ }
+ if (CMRestAPI.appWhiteList == null) {
+ return false;
+ }
+ return CMRestAPI.appWhiteList.contains(clientIp);
+ }
+
+ /**
+ * @Title: getClusterStatus
+ * @Description:
+ * Receive get ClusterStatus request.
+ * @param request
+ * @return
+ * ResponseEntity
+ */
+ @GetMapping("/ClusterStatus")
+ public ResponseEntity getClusterStatus(HttpServletRequest request) {
+ String clientIp = getClientIp(request);
+ logger.info("Received get cluster status request from {}", clientIp);
+ if (!checkAppWhiteList(clientIp)) {
+ logger.error(HttpStatus.UNAUTHORIZED.toString() + "client " + clientIp);
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(HttpStatus.UNAUTHORIZED.toString());
+ }
+ CmdResult cmdResult = ogCmdExcuter.getClusterStatus();
+ if (cmdResult == null) {
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("{\"msg\": \"Exec query command failed!\"}");
+ }
+ if (cmdResult.statusCode != 0) {
+ String msg = null;
+ if (cmdResult.statusCode == 124) {
+ msg = "{\"msg\": \"Exec query command timeout!\"}";
+ } else {
+ msg = cmdResult.resultString;
+ }
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(msg);
+ }
+ String[] nodesStatus = cmdResult.resultString.split("\\s+-+\\s+");
+ String clusterState = CMRestAPI.matchRegex("cluster_state.*: (.*)\\s+", nodesStatus[0]);
+ ClusterStatus clusterStatus = new ClusterStatus();
+ clusterStatus.clusterState = clusterState;
+ clusterStatus.nodesStatus = new ArrayList();
+ for(int i = 1; i < nodesStatus.length; ++i) {
+ if (nodesStatus[i] != null && !nodesStatus[i].trim().isEmpty()) {
+ String nodeIp = CMRestAPI.matchRegex("node_ip.*: (.*)\\s+", nodesStatus[i]);
+ String cmServerState = CMRestAPI.matchRegex("type.*CMServer\\s+instance_state.*: (.*)\\s+", nodesStatus[i]);
+ String dnRole = CMRestAPI.matchRegex("type.*Datanode\\s+instance_state.*: (.*)\\s+", nodesStatus[i]);
+ String dnState = CMRestAPI.matchRegex("HA_state.*: (.*)\\s+", nodesStatus[i]);
+ clusterStatus.nodesStatus.add(new NodeStatus(nodeIp, cmServerState, dnRole, dnState));
+ }
+ }
+
+ Gson clusterGson = new Gson();
+ String result = clusterGson.toJson(clusterStatus);
+ logger.info(result);
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body(result);
+ }
+
+ /**
+ * @Title: getNodeStatus
+ * @Description:
+ * Receive get NodeStatus request. Return status of current node if nodeId is not provided.
+ * @param request
+ * @return
+ * ResponseEntity
+ */
+ @GetMapping("/NodeStatus")
+ ResponseEntity getNodeStatus(HttpServletRequest request,
+ @RequestParam(value="nodeId", required = false, defaultValue = "0")int nodeId) {
+ String clientIp = getClientIp(request);
+ logger.info("Received get node status request from {}", clientIp);
+ if (!checkAppWhiteList(clientIp)) {
+ logger.error(HttpStatus.UNAUTHORIZED.toString() + "client " + clientIp);
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(HttpStatus.UNAUTHORIZED.toString());
+ }
+ if (nodeId == 0) {
+ nodeId = CMRestAPI.nodeId;
+ }
+ CmdResult cmdResult = ogCmdExcuter.getNodeStatus(nodeId);
+ if (cmdResult == null) {
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("{\"msg\": \"Exec query command failed!\"}");
+ }
+ if (cmdResult.statusCode != 0) {
+ String msg = null;
+ if (cmdResult.statusCode == 124) {
+ msg = "{\"msg\": \"Exec query command timeout!\"}";
+ } else {
+ msg = cmdResult.resultString;
+ }
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(msg);
+ }
+ NodeStatus nodeStatus = null;
+ if (cmdResult.resultString != null && !cmdResult.resultString.trim().isEmpty()) {
+ String nodeIp = CMRestAPI.matchRegex("node_ip.*: (.*)\\s+", cmdResult.resultString);
+ String cmServerState = CMRestAPI.matchRegex("type.*CMServer\\s+instance_state.*: (.*)\\s+", cmdResult.resultString);
+ String dnRole = CMRestAPI.matchRegex("type.*Datanode\\s+instance_state.*: (.*)\\s+", cmdResult.resultString);
+ String dnState = CMRestAPI.matchRegex("HA_state.*: (.*)\\s+", cmdResult.resultString);
+ nodeStatus = new NodeStatus(nodeIp, cmServerState, dnRole, dnState);
+ }
+
+ Gson clusterGson = new Gson();
+ String result = clusterGson.toJson(nodeStatus);
+ logger.info(result);
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body(result);
+ }
+
+ /**
+ * @Title: registerOrUpdateRecvAddr
+ * @Description:
+ * If key does not exist, register the address of receiving master info, else update.
+ * key = prefix("/CMRestAPI/RecvAddrList/") + clientIp + "/" + appName.
+ * value = url
+ * @param request
+ * @param url
+ * @param app
+ * @return
+ * ResponseEntity
+ */
+ @PutMapping("/RecvAddr")
+ public ResponseEntity registerOrUpdateRecvAddr(HttpServletRequest request, @RequestParam(value = "url")String url,
+ @RequestParam(value = "app", required = false, defaultValue = "")String app) {
+ String clientIp = getClientIp(request);
+ logger.info("Received put recvaddr request from {}:{}.", clientIp, app);
+ if (!checkAppWhiteList(clientIp)) {
+ logger.error(HttpStatus.UNAUTHORIZED.toString() + "client " + clientIp);
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(HttpStatus.UNAUTHORIZED.toString());
+ }
+ CmdResult cmdResult = ogCmdExcuter.saveRecvAddr(clientIp, app, url);
+ if (cmdResult == null) {
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("{\"msg\": \"Exec put command failed!\"}");
+ }
+ if (cmdResult.statusCode != 0) {
+ String msg = null;
+ if (cmdResult.statusCode == 124) {
+ msg = "{\"msg\": \"Exec put command timeout!\"}";
+ } else {
+ msg = cmdResult.resultString;
+ }
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(msg);
+ }
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body("Register receive address successfully.");
+ }
+
+ /**
+ * @Title: deleteRegisterAddr
+ * @Description:
+ * Delete register address.
+ * key = prefix("/CMRestAPI/RecvAddrList/") + clientIp + "/" + appName.
+ * @param request
+ * @param app
+ * @return
+ * ResponseEntity
+ */
+ @DeleteMapping("/RecvAddr")
+ public ResponseEntity deleteRegisterAddr(HttpServletRequest request,
+ @RequestParam(value = "app", required = false, defaultValue = "")String app) {
+ String clientIp = getClientIp(request);
+ logger.info("Received delete RecvAddr request from {}.", clientIp);
+ if (!checkAppWhiteList(clientIp)) {
+ logger.error(HttpStatus.UNAUTHORIZED.toString() + "client " + clientIp);
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(HttpStatus.UNAUTHORIZED.toString());
+ }
+ CmdResult cmdResult = ogCmdExcuter.deleteRecvAddr(clientIp, app);
+ if (cmdResult == null) {
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("{\"msg\": \"Exec delete command failed!\"}");
+ }
+ if (cmdResult.statusCode != 0) {
+ String msg = null;
+ if (cmdResult.statusCode == 124) {
+ msg = "{\"msg\": \"Exec delete command timeout!\"}";
+ } else {
+ msg = cmdResult.resultString;
+ }
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(msg);
+ }
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body("Deleted successfully.");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/opengauss/cmrestapi/ErrorCode.java b/src/main/java/org/opengauss/cmrestapi/ErrorCode.java
new file mode 100644
index 0000000..c9e525b
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/ErrorCode.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+/**
+ * @Title: ErrorCode
+ * @author: xuemengen
+ * @Description:
+ * Define error CODE according to linux error CODE.
+ * Created on: 2022/09/08
+ */
+public enum ErrorCode {
+ ENOENT(2, "No such file or directory"),
+ ESRCH(3, "No such process"),
+ ENOTDIR(20, "Not a directory"),
+ EISDIR(21, "Is a directory"),
+ EINVAL(22, "Invalid argument"),
+ EHOSTDOWN(112, "Host is down"),
+ EUNKNOWN(255, "Unknown error");
+
+ private final int CODE;
+ private final String DESCRIPTION;
+
+ ErrorCode(int value, String description) {
+ this.CODE = value;
+ this.DESCRIPTION = description;
+ }
+
+ /**
+ * @Title: GetCode
+ * @Description:
+ * Get CODE.
+ * @return
+ * int: error CODE
+ */
+ public int getCode() {
+ return CODE;
+ }
+
+ /**
+ * @Title: GetDescription
+ * @Description:
+ * Get DESCRIPTION
+ * @return
+ * String: error CODE DESCRIPTION
+ */
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/opengauss/cmrestapi/InfoPushThread.java b/src/main/java/org/opengauss/cmrestapi/InfoPushThread.java
new file mode 100644
index 0000000..3bc8449
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/InfoPushThread.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @Title: InfoPushThread
+ * @author: xuemengen
+ * @Description: InfoPushThread
+ * Created on: 2022/09/08
+ */
+public class InfoPushThread implements Runnable {
+ private Thread thread;
+ private final String THREAD_NAME;
+ private String recvAddrUrl;
+ private String masterIpPort;
+ private Logger logger = LoggerFactory.getLogger(InfoPushThread.class);
+
+ InfoPushThread(int threadNo, String recvAddrUrl, String masterIpPort) {
+ this.THREAD_NAME = "InfoPushThread-" + threadNo;
+ this.masterIpPort = masterIpPort;
+ this.recvAddrUrl = recvAddrUrl;
+ }
+
+ @Override
+ public void run() {
+ CMRestAPIClient client = new CMRestAPIClient(recvAddrUrl);
+ client.pushMasterInfo(masterIpPort);
+ client.pushStandbysInfo();
+ }
+
+ /**
+ * @Title: start
+ * @Description:
+ * Start entry of infoPushThread.
+ * void
+ */
+ public void start () {
+ logger.info("Starting thread {}, recvAddrUrl={}, masterIpPort={}",
+ THREAD_NAME, recvAddrUrl, masterIpPort);
+ if (thread == null) {
+ thread = new Thread(this, THREAD_NAME);
+ thread.start();
+ }
+ }
+}
diff --git a/src/main/java/org/opengauss/cmrestapi/InfoQueryThread.java b/src/main/java/org/opengauss/cmrestapi/InfoQueryThread.java
new file mode 100644
index 0000000..9f1b981
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/InfoQueryThread.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @Title: InfoQueryThread
+ * @author: xuemengen
+ * @Description: InfoQueryThread
+ * Start class of server.
+ * Created on: 2022/09/08
+ */
+@SpringBootApplication
+public class InfoQueryThread implements Runnable {
+ private Thread thread;
+ private final String THREAD_NAME;
+ private Logger logger = LoggerFactory.getLogger(InfoQueryThread.class);
+
+ public InfoQueryThread() {
+ THREAD_NAME = "InfoQueryThread";
+ }
+
+ public void start() {
+ logger.info("Starting thread {}", THREAD_NAME);
+ if (thread == null) {
+ thread = new Thread(this, THREAD_NAME);
+ thread.start ();
+ }
+ }
+
+ @Override
+ public void run() {
+ SpringApplication.run(InfoQueryThread.class);
+ }
+}
diff --git a/src/main/java/org/opengauss/cmrestapi/OGCmdExecuter.java b/src/main/java/org/opengauss/cmrestapi/OGCmdExecuter.java
new file mode 100644
index 0000000..414988b
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/OGCmdExecuter.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @Title: OGCmdExecuter
+ * @author: xuemengen
+ * @Description:
+ * openGauss command executer.
+ * Created on: 2022/09/08
+ */
+public class OGCmdExecuter {
+ private final String SOURCE_ENV_CMD;
+ private static Logger logger = LoggerFactory.getLogger(OGCmdExecuter.class);
+
+ public OGCmdExecuter(String envFile) {
+ this.SOURCE_ENV_CMD = "source " + envFile + "; ";
+ }
+
+ /**
+ * @Title: CmdResult
+ * @author: xuemengen
+ * @Description: Result of executing command.
+ * Created on: 2022/09/08
+ */
+ static class CmdResult {
+ int statusCode;
+ String resultString;
+ public CmdResult(int s, String res) {
+ statusCode = s;
+ resultString = res;
+ }
+ }
+
+ /**
+ * @Title: execCmd
+ * @Description:
+ * Execute shell command
+ * @param command
+ * @return
+ * CmdResult
+ */
+ public static CmdResult execCmd(String command) {
+ try {
+ logger.debug("Excuting command: {}.",command);
+ String[] cmd = new String[]{"/bin/sh", "-c", command};
+ Process process = Runtime.getRuntime().exec(cmd);
+ BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ StringBuffer sb = new StringBuffer();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line).append(System.lineSeparator());
+ }
+ int statusCode = process.waitFor();
+ String resultString = sb.toString();
+ logger.debug("Result:\nstatusCode: {}\nresultString: {}", statusCode, resultString);
+ return new CmdResult(statusCode, resultString);
+ } catch (IOException | InterruptedException exp) {
+ logger.error("Exception happend when excute shell command: {}.\nDetail:\n{}", command, exp);
+ }
+ return null;
+ }
+
+ private String getCmctlCmd(String action, String options) {
+ return "cm_ctl " + action + " " + options;
+ }
+
+ /**
+ * @Title: gsctlQuery
+ * @Description:
+ * Execute gs_ctl query -D DATAPATH.
+ * @return
+ * CmdResult
+ */
+ public CmdResult gsctlQuery() {
+ String cmd = SOURCE_ENV_CMD + "timeout 5 "
+ + "gs_ctl query -D " + CMRestAPI.dataPath;
+ return execCmd(cmd);
+ }
+
+ /**
+ * @Title: cmctlQuery
+ * @Description:
+ * cm_ctl query
+ * @param options
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlQuery(String options) {
+ String cmd = SOURCE_ENV_CMD + "timeout 5 " + getCmctlCmd("query", options);
+ return execCmd(cmd);
+ }
+
+ /**
+ * @Title: cmctlViewNode
+ * @Description:
+ * Execute cm_ctl view -n nodeId
+ * @param nodeId
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlViewNode(String nodeId) {
+ String options = "";
+ if (nodeId == null || !"".equals(nodeId) ) {
+ options = "-n " + nodeId;
+ }
+ String cmd = SOURCE_ENV_CMD + "timeout 5 " + getCmctlCmd("view", options);
+ return execCmd(cmd);
+ }
+
+ /**
+ * @Title: cmctlViewAll
+ * @Description:
+ * Execute cm_ctl view -n nodeId
+ * @param nodeId
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlViewAll() {
+ String cmd = SOURCE_ENV_CMD + "timeout 5 " + getCmctlCmd("view", null);
+ return execCmd(cmd);
+ }
+
+ /**
+ * @Title: cmctlViewNative
+ * @Description:
+ * Execute cm_ctl view -N. Get static config info of current node.
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlViewNative() {
+ String cmd = SOURCE_ENV_CMD + "timeout 5 " + getCmctlCmd("view", "-N");
+ return execCmd(cmd);
+ }
+
+ /**
+ * @Title: cmctlDdb
+ * @Description:
+ * cm_ctl ddb DCC_CMD
+ * DCC_CMD:
+ * [--prefix] --get key
+ * --put key value
+ * [--prefix] --delete key
+ * --cluster_info
+ * --leader_info
+ * @param action
+ * @param hasPrefix
+ * @param key
+ * @param value
+ * @return
+ * CmdResult
+ */
+ private CmdResult cmctlDdb(String action, boolean hasPrefix, String key, String value) {
+ String cmd = SOURCE_ENV_CMD + "cm_ctl ddb ";
+ if (hasPrefix) {
+ cmd += "--prefix ";
+ }
+ cmd += action;
+ if (!"--cluster_info".equals(action) && !"--leader_info".equals(action)) {
+ cmd += " " + key;
+ if ("--put".equals(action)) {
+ cmd += " " + value;
+ }
+ }
+ return execCmd(cmd);
+ }
+
+ /**
+ * @Title: cmctlDdbPut
+ * @Description:
+ * cm_ctl ddb --put key value
+ * @param key
+ * @param value
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlDdbPut(String key, String value) {
+ return cmctlDdb("--put", false, key, value);
+ }
+
+ /**
+ * @Title: cmctlDdbGet
+ * @Description:
+ * cm_ctl ddb [--prefix] --get
+ * @param key
+ * @param hasPrefix
+ * @return
+ * CmdResult
+ */
+ private CmdResult cmctlDdbGet(String key, Boolean hasPrefix) {
+ return cmctlDdb("--get", hasPrefix, key, null);
+ }
+
+ /**
+ * @Title: cmctlDdbGet
+ * @Description:
+ * cm_ctl ddb --get
+ * @param key
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlDdbGet(String key) {
+ return cmctlDdbGet(key, false);
+ }
+
+ /**
+ * @Title: cmctlDdbGetPrefix
+ * @Description:
+ * cm_ctl ddb --prefix --get
+ * @param key
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlDdbGetPrefix(String key) {
+ return cmctlDdbGet(key, true);
+ }
+
+ /**
+ * @Title: cmctlDdbDelete
+ * @Description:
+ * cm_ctl ddb --delete
+ * @param key
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlDdbDelete(String key) {
+ return cmctlDdb("--delete", false, key, null);
+ }
+
+ /**
+ * @Title: cmctlDdbDeletePrefix
+ * @Description:
+ * cm_ctl ddb --prefix --delete
+ * @param key
+ * @return
+ * CmdResult
+ */
+ public CmdResult cmctlDdbDeletePrefix(String key) {
+ return cmctlDdb("--delete", true, key, null);
+ }
+
+ /**
+ * @Title: deleteRecvAddr
+ * @Description:
+ * Delete receive address
+ * @param clientIp
+ * @param app
+ * @return
+ * CmdResult
+ */
+ public CmdResult deleteRecvAddr(String clientIp, String app) {
+ String key = CMRestAPI.prefix + clientIp;
+ if (app == null || "".equals(app)) {
+ return cmctlDdbDeletePrefix(key);
+ }
+ key += "/" + app;
+ return cmctlDdbDelete(key);
+ }
+
+ /**
+ * @Title: getRecvAddrList
+ * @Description:
+ * Get receive address list from dcc.
+ * @return
+ * Map: [.., {"clientIp/app", recvAddr}, ..]
+ */
+ public Map getRecvAddrList() {
+ CmdResult cmdResult = cmctlDdbGetPrefix(CMRestAPI.prefix);
+ if (cmdResult.statusCode != 0 || cmdResult.resultString.matches("Key not found")) {
+ return null;
+ }
+ Map clientIpRecvAddrMap = new HashMap<>();
+ Pattern pattern = Pattern.compile(CMRestAPI.prefix + "(.*[\\s]+.*)[\\s]+?");
+ Matcher matcher = pattern.matcher(cmdResult.resultString);
+ while (matcher.find()) {
+ String keyValue = matcher.group(1);
+ String[] kv = keyValue.split("\\s+");
+ clientIpRecvAddrMap.put(kv[0], kv[1]);
+ }
+ return clientIpRecvAddrMap;
+ }
+
+ /**
+ * @Title: saveRecvAddr
+ * @Description:
+ * Save recvaddr to ddb
+ * @param clientIp
+ * @param app
+ * @param recvAddr
+ * @return
+ * CmdResult
+ */
+ public CmdResult saveRecvAddr(String clientIp, String app, String recvAddr) {
+ String key = CMRestAPI.prefix + clientIp;
+ if (app != null && !"".equals(app)) {
+ key += "/" + app;
+ }
+ return cmctlDdbPut(key, recvAddr);
+ }
+
+ /**
+ * @Title: getClusterStatus
+ * @Description:
+ * Get cluster status by executing cm_ctl query -v.
+ * @return
+ * CmdResult
+ */
+ public CmdResult getClusterStatus() {
+ return cmctlQuery("-v");
+ }
+
+ /**
+ * @Title: getNodeStatus
+ * @Description:
+ * Get node status by executing cm_ctl query -v -i nodeId.
+ * @param nodeId
+ * @return
+ * CmdResult
+ */
+ public CmdResult getNodeStatus(int nodeId) {
+ return cmctlQuery("-v -n " + nodeId);
+ }
+}
diff --git a/src/main/java/org/opengauss/cmrestapi/Role2PrimaryMonitor.java b/src/main/java/org/opengauss/cmrestapi/Role2PrimaryMonitor.java
new file mode 100644
index 0000000..434a707
--- /dev/null
+++ b/src/main/java/org/opengauss/cmrestapi/Role2PrimaryMonitor.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
+ *
+ * CM is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+package org.opengauss.cmrestapi;
+
+import java.util.Map;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.opengauss.cmrestapi.OGCmdExecuter.CmdResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @Title: Role2PrimaryMonitor
+ * @author: xuemengen
+ * @Description:
+ * Monitor of data instance role change to primary.
+ * Created on: 2022/09/08
+ */
+public class Role2PrimaryMonitor implements Runnable {
+ private Thread thread;
+ private final String THREAD_NAME;
+ private OGCmdExecuter ogCmdExecuter;
+ private String currentLocalRole;
+ private String masterIpPort;
+ private Logger logger = LoggerFactory.getLogger(Role2PrimaryMonitor.class);
+
+ public Role2PrimaryMonitor() {
+ THREAD_NAME = "RoleChangeToPrimaryMonitor";
+ currentLocalRole = "";
+ ogCmdExecuter = new OGCmdExecuter(CMRestAPI.envFile);
+ masterIpPort = CMRestAPI.hostIp + ":" + CMRestAPI.port;
+ }
+
+ /**
+ * @Title: Role2Primary
+ * @Description:
+ * Check whether dn role change to primary.
+ * @return
+ * boolean
+ */
+ private boolean roleChanged2Primary() {
+ CmdResult cmdResult = ogCmdExecuter.gsctlQuery();
+ if (cmdResult.statusCode != 0) {
+ logger.error("Exec gs_ctl query cmd failed!");
+ return false;
+ }
+ String localRole = null;
+ Pattern pattern = Pattern.compile(".+local_role.+: (.+)\\s+");
+ Matcher matcher = pattern.matcher(cmdResult.resultString);
+ if (matcher.find()) {
+ localRole = matcher.group(1);
+ }
+ if (!currentLocalRole.equals(localRole)) {
+ // update currentLocalRole
+ currentLocalRole = localRole;
+ if ("Primary".equals(localRole)) {
+ logger.info("Role change to primary happened, current node becomes to primary! "
+ + "Current master ip:port={}.", masterIpPort);
+ return true;
+ } else {
+ logger.info("Current local role change to {}.", localRole);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @Title: run
+ * @Description:
+ * Main entry of monitor.
+ * @return
+ * void
+ */
+ @Override
+ public void run() {
+ try {
+ for (;;) {
+ Thread.sleep(1000);
+ boolean hasRoleChanged2Priamry = roleChanged2Primary();
+ if (!hasRoleChanged2Priamry) {
+ continue;
+ }
+ Map ipPortRecvAddrs = ogCmdExecuter.getRecvAddrList();
+ Collection recvAddrList = ipPortRecvAddrs.values();
+ int i = 0;
+ for (String url : recvAddrList) {
+ ++i;
+ // start a new PushInfoThread to send masterIpPort to every recvAddr
+ new InfoPushThread(i, url, masterIpPort).start();
+ }
+ }
+ } catch (InterruptedException e) {
+ logger.error("{}", e);
+ }
+ }
+
+ /**
+ * @Title: start
+ * @Description:
+ * Start this thread.
+ * void
+ */
+ public void start() {
+ logger.info("Starting thread {}.", THREAD_NAME);
+ if (thread == null) {
+ thread = new Thread(this, THREAD_NAME);
+ thread.start ();
+ }
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..e69de29
--
Gitee