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