From 21b31e1feffdf7df5b21d0aaa9f9c446cd0e8eae Mon Sep 17 00:00:00 2001 From: yunye Date: Mon, 23 Dec 2024 21:12:39 +0800 Subject: [PATCH] 1. add server load v3 api; 2. add nacos cluster control v3 api; 3. add server load v3 api; 4. add deprecated annotation to old api; 5. add alternatives to old api; 6. add unit test for core v3 admin api. --- .../core/controller/CoreOpsController.java | 7 +- .../controller/NacosClusterController.java | 18 +- .../controller/ServerLoaderController.java | 19 +- .../controller/v2/CoreOpsV2Controller.java | 15 +- .../v2/NacosClusterControllerV2.java | 14 +- .../controller/v3/CoreOpsControllerV3.java | 114 ++++++ .../v3/NacosClusterControllerV3.java | 142 +++++++ .../v3/ServerLoaderControllerV3.java | 364 ++++++++++++++++++ .../core/model/form/v3/RaftCommandForm.java | 89 +++++ .../model/response/ServerLoaderMetric.java | 120 ++++++ .../model/response/ServerLoaderMetrics.java | 145 +++++++ .../com/alibaba/nacos/core/utils/Commons.java | 4 + .../v3/CoreOpsControllerV3Test.java | 112 ++++++ .../v3/NacosClusterControllerV3Test.java | 134 +++++++ .../v3/ServerLoaderControllerV3Test.java | 158 ++++++++ 15 files changed, 1422 insertions(+), 33 deletions(-) mode change 100644 => 100755 core/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterControllerV2.java create mode 100644 core/src/main/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3.java create mode 100644 core/src/main/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3.java create mode 100644 core/src/main/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3.java create mode 100644 core/src/main/java/com/alibaba/nacos/core/model/form/v3/RaftCommandForm.java create mode 100644 core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetric.java create mode 100644 core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetrics.java create mode 100644 core/src/test/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3Test.java create mode 100644 core/src/test/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3Test.java create mode 100644 core/src/test/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3Test.java diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java b/core/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java index 948b08b6018..7376fc45d29 100644 --- a/core/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java +++ b/core/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java @@ -46,6 +46,7 @@ */ @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT + "/ops") +@Deprecated public class CoreOpsController { private final ProtocolManager protocolManager; @@ -66,7 +67,7 @@ public CoreOpsController(ProtocolManager protocolManager, IdGeneratorManager idG @PostMapping(value = "/raft") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "POST {contextPath:nacos}/v3/admin/core/ops/raft") public RestResult raftOps(@RequestBody Map commands) { return protocolManager.getCpProtocol().execute(commands); } @@ -77,7 +78,7 @@ public RestResult raftOps(@RequestBody Map commands) { * @return {@link RestResult} */ @GetMapping(value = "/idInfo") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/ops/ids") public RestResult>> idInfo() { Map> info = new HashMap<>(10); idGeneratorManager.getGeneratorMap().forEach((resource, idGenerator) -> info.put(resource, idGenerator.info())); @@ -86,7 +87,7 @@ public RestResult>> idInfo() { @PutMapping(value = "/log") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "PUT {contextPath:nacos}/v3/admin/core/ops/log") public String setLogLevel(@RequestParam String logName, @RequestParam String logLevel) { Loggers.setLogLevel(logName, logLevel); return HttpServletResponse.SC_OK + ""; diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java b/core/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java index 68d5162ded4..9569a29bd27 100644 --- a/core/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java +++ b/core/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java @@ -48,6 +48,7 @@ */ @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT + "/cluster") +@Deprecated public class NacosClusterController { private final ServerMemberManager memberManager; @@ -58,7 +59,7 @@ public NacosClusterController(ServerMemberManager memberManager) { @GetMapping(value = "/self") @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self") public RestResult self() { return RestResultUtils.success(memberManager.getSelf()); } @@ -71,7 +72,7 @@ public RestResult self() { */ @GetMapping(value = "/nodes") @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/core/cluster/nodes") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/admin/core/cluster/node/list") public RestResult> listNodes( @RequestParam(value = "keyword", required = false) String ipKeyWord) { Collection members = memberManager.allMembers(); @@ -103,7 +104,7 @@ public RestResult> listSimpleNodes() { @GetMapping("/health") @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self/health") public RestResult getHealth() { return RestResultUtils.success(memberManager.getSelf().getState().name()); } @@ -116,7 +117,8 @@ public RestResult getHealth() { */ @Deprecated @PostMapping(value = {"/report"}) - @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Secured(resource = Commons.NACOS_CORE_CONTEXT + + "/cluster", action = ActionTypes.WRITE, signType = SignType.CONSOLE) @Compatibility(apiType = ApiType.INNER_API) public RestResult report(@RequestBody Member node) { if (!node.check()) { @@ -136,8 +138,9 @@ public RestResult report(@RequestBody Member node) { * @return {@link RestResult} */ @PostMapping(value = "/switch/lookup") - @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.WRITE, signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Secured(resource = Commons.NACOS_CORE_CONTEXT + + "/cluster", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "PUT {contextPath:nacos}/v3/admin/core/cluster/lookup") public RestResult switchLookup(@RequestParam(name = "type") String type) { try { memberManager.switchLookup(type); @@ -155,7 +158,8 @@ public RestResult switchLookup(@RequestParam(name = "type") String type) * @throws Exception {@link Exception} */ @PostMapping("/server/leave") - @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Secured(resource = Commons.NACOS_CORE_CONTEXT + + "/cluster", action = ActionTypes.WRITE, signType = SignType.CONSOLE) @Compatibility(apiType = ApiType.CONSOLE_API) public RestResult leave(@RequestBody Collection params, @RequestParam(defaultValue = "true") Boolean notifyOtherMembers) throws Exception { diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java b/core/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java index 9a959591da8..aef7a00b944 100644 --- a/core/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java +++ b/core/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java @@ -66,6 +66,7 @@ */ @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/loader") +@Deprecated public class ServerLoaderController { private static final Logger LOGGER = LoggerFactory.getLogger(ServerLoaderController.class); @@ -109,7 +110,7 @@ public ServerLoaderController(ConnectionManager connectionManager, ServerMemberM */ @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.READ) @GetMapping("/current") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/core/loader/current") public ResponseEntity> currentClients() { Map stringConnectionMap = connectionManager.currentClients(); return ResponseEntity.ok().body(stringConnectionMap); @@ -122,7 +123,7 @@ public ResponseEntity> currentClients() { */ @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) @GetMapping("/reloadCurrent") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/core/loader/reloadCurrent") public ResponseEntity reloadCount(@RequestParam Integer count, @RequestParam(value = "redirectAddress", required = false) String redirectAddress) { connectionManager.loadCount(count, redirectAddress); @@ -130,14 +131,14 @@ public ResponseEntity reloadCount(@RequestParam Integer count, } /** - * According to the total number of sdk connections of all nodes in the nacos cluster, - * intelligently balance the number of sdk connections of each node in the nacos cluster. + * According to the total number of sdk connections of all nodes in the nacos cluster, intelligently balance the + * number of sdk connections of each node in the nacos cluster. * * @return state json. */ @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) @GetMapping("/smartReloadCluster") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/core/loader/smartReloadCluster") public ResponseEntity smartReload(HttpServletRequest request, @RequestParam(value = "loaderFactor", required = false) String loaderFactorStr, @RequestParam(value = "force", required = false) String force) { @@ -248,7 +249,7 @@ public void onException(Throwable e) { */ @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) @GetMapping("/reloadClient") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/core/loader/reloadClient") public ResponseEntity reloadSingle(@RequestParam String connectionId, @RequestParam(value = "redirectAddress", required = false) String redirectAddress) { connectionManager.loadSingle(connectionId, redirectAddress); @@ -262,7 +263,7 @@ public ResponseEntity reloadSingle(@RequestParam String connectionId, */ @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.READ) @GetMapping("/cluster") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/core/loader/cluster") public ResponseEntity> loaderMetrics() { Map serverLoadMetrics = getServerLoadMetrics(); @@ -320,8 +321,8 @@ public void onException(Throwable e) { } try { - ServerLoaderInfoResponse handle = serverLoaderInfoRequestHandler - .handle(new ServerLoaderInfoRequest(), new RequestMeta()); + ServerLoaderInfoResponse handle = serverLoaderInfoRequestHandler.handle(new ServerLoaderInfoRequest(), + new RequestMeta()); ServerLoaderMetrics metrics = new ServerLoaderMetrics(); metrics.setAddress(serverMemberManager.getSelf().getAddress()); metrics.setMetric(handle.getLoaderMetrics()); diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java b/core/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java index d59fe0b3fba..98c52f61d77 100644 --- a/core/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java +++ b/core/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java @@ -49,6 +49,7 @@ @Beta @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/ops") +@Deprecated public class CoreOpsV2Controller { private final ProtocolManager protocolManager; @@ -63,18 +64,16 @@ public CoreOpsV2Controller(ProtocolManager protocolManager, IdGeneratorManager i /** * Temporarily overpassed the raft operations interface. *

- * { - * "groupId": "xxx", - * "command": "transferLeader or doSnapshot or resetRaftCluster or removePeer" - * "value": "ip:{raft_port}" - * } + * { "groupId": "xxx", "command": "transferLeader or doSnapshot or resetRaftCluster or removePeer" "value": + * "ip:{raft_port}" } *

+ * * @param commands transferLeader or doSnapshot or resetRaftCluster or removePeer * @return {@link RestResult} */ @PostMapping(value = "/raft") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "POST {contextPath:nacos}/v3/admin/core/ops/raft") public RestResult raftOps(@RequestBody Map commands) { return protocolManager.getCpProtocol().execute(commands); } @@ -85,7 +84,7 @@ public RestResult raftOps(@RequestBody Map commands) { * @return {@link RestResult} */ @GetMapping(value = "/ids") - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/ops/ids") public RestResult> ids() { List result = new ArrayList<>(); idGeneratorManager.getGeneratorMap().forEach((resource, idGenerator) -> { @@ -105,7 +104,7 @@ public RestResult> ids() { @PutMapping(value = "/log") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "PUT {contextPath:nacos}/v3/admin/core/ops/log") public RestResult updateLog(@RequestBody LogUpdateRequest logUpdateRequest) { Loggers.setLogLevel(logUpdateRequest.getLogName(), logUpdateRequest.getLogLevel()); return RestResultUtils.success(); diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterControllerV2.java b/core/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterControllerV2.java old mode 100644 new mode 100755 index af3a2286c76..8412430243a --- a/core/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterControllerV2.java +++ b/core/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterControllerV2.java @@ -55,6 +55,7 @@ @NacosApi @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/cluster") +@Deprecated public class NacosClusterControllerV2 { private final NacosClusterOperationService nacosClusterOperationService; @@ -65,7 +66,7 @@ public NacosClusterControllerV2(NacosClusterOperationService nacosClusterOperati @GetMapping(value = "/node/self") @Secured(action = ActionTypes.READ, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self") public Result self() { return Result.success(nacosClusterOperationService.self()); } @@ -79,7 +80,7 @@ public Result self() { */ @GetMapping(value = "/node/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/list") public Result> listNodes(@RequestParam(value = "address", required = false) String address, @RequestParam(value = "state", required = false) String state) throws NacosException { @@ -88,7 +89,8 @@ public Result> listNodes(@RequestParam(value = "address", req try { nodeState = NodeState.valueOf(state.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { - throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_STATE, "Illegal state: " + state); + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_STATE, + "Illegal state: " + state); } } return Result.success(nacosClusterOperationService.listNodes(address, nodeState)); @@ -96,7 +98,7 @@ public Result> listNodes(@RequestParam(value = "address", req @GetMapping(value = "/node/self/health") @Secured(action = ActionTypes.READ, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self/health") public Result selfHealth() { return Result.success(nacosClusterOperationService.selfHealth()); } @@ -112,7 +114,7 @@ public Result selfHealth() { */ @PutMapping(value = "/node/list") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "PUT {contextPath:nacos}/v3/admin/core/cluster/node/list") public Result updateNodes(@RequestBody List nodes) throws NacosApiException { if (nodes == null || nodes.size() == 0) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, @@ -129,7 +131,7 @@ public Result updateNodes(@RequestBody List nodes) throws Nacos */ @PutMapping(value = "/lookup") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) - @Compatibility(apiType = ApiType.ADMIN_API) + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "PUT {contextPath:nacos}/v3/admin/core/cluster/lookup") public Result updateLookup(LookupUpdateRequest request) throws NacosException { if (request == null || request.getType() == null) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3.java b/core/src/main/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3.java new file mode 100644 index 00000000000..63d6b501e27 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3.java @@ -0,0 +1,114 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.controller.v3; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; +import com.alibaba.nacos.core.model.form.v3.RaftCommandForm; +import com.alibaba.nacos.core.model.request.LogUpdateRequest; +import com.alibaba.nacos.core.model.vo.IdGeneratorVO; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.nacos.core.utils.Commons.NACOS_ADMIN_CORE_CONTEXT_V3; + +/** + * Kernel modules operate and maintain HTTP interfaces v3. + * + * @author yunye + * @since 3.0.0-beta + */ +@NacosApi +@RestController +@RequestMapping(NACOS_ADMIN_CORE_CONTEXT_V3 + "/ops") +public class CoreOpsControllerV3 { + + private final ProtocolManager protocolManager; + + private final IdGeneratorManager idGeneratorManager; + + public CoreOpsControllerV3(ProtocolManager protocolManager, IdGeneratorManager idGeneratorManager) { + this.protocolManager = protocolManager; + this.idGeneratorManager = idGeneratorManager; + } + + /** + * Temporarily overpassed the raft operations interface. + *

+ * { "groupId": "xxx", "command": "transferLeader or doSnapshot or resetRaftCluster or removePeer" "value": + * "ip:{raft_port}" } + *

+ * + * @param form RaftCommandForm + * @return {@link RestResult} + */ + @PostMapping(value = "/raft") + @Secured(resource = Commons.NACOS_ADMIN_CORE_CONTEXT_V3 + + "/ops", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result raftOps(@RequestBody RaftCommandForm form) { + return Result.success(protocolManager.getCpProtocol().execute(form.toMap()).getData()); + } + + /** + * Gets the current health of the ID generator. + * + * @return {@link RestResult} + */ + @GetMapping(value = "/ids") + @Secured(resource = Commons.NACOS_ADMIN_CORE_CONTEXT_V3 + + "/ops", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result> ids() { + List result = new ArrayList<>(); + idGeneratorManager.getGeneratorMap().forEach((resource, idGenerator) -> { + IdGeneratorVO vo = new IdGeneratorVO(); + vo.setResource(resource); + + IdGeneratorVO.IdInfo info = new IdGeneratorVO.IdInfo(); + info.setCurrentId(idGenerator.currentId()); + info.setWorkerId(idGenerator.workerId()); + vo.setInfo(info); + + result.add(vo); + }); + + return Result.success(result); + } + + @PutMapping(value = "/log") + @Secured(resource = Commons.NACOS_ADMIN_CORE_CONTEXT_V3 + + "/ops", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result updateLog(@RequestBody LogUpdateRequest logUpdateRequest) { + Loggers.setLogLevel(logUpdateRequest.getLogName(), logUpdateRequest.getLogLevel()); + return Result.success(); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3.java b/core/src/main/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3.java new file mode 100644 index 00000000000..d4185db2f52 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.controller.v3; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.NodeState; +import com.alibaba.nacos.core.model.request.LookupUpdateRequest; +import com.alibaba.nacos.core.service.NacosClusterOperationService; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +import static com.alibaba.nacos.core.utils.Commons.NACOS_ADMIN_CORE_CONTEXT_V3; + +/** + * Cluster communication interface v3. + * + * @author yunye + * @since 3.0.0-beta + */ +@NacosApi +@RestController +@RequestMapping(NACOS_ADMIN_CORE_CONTEXT_V3 + "/cluster") +public class NacosClusterControllerV3 { + + private final NacosClusterOperationService nacosClusterOperationService; + + public NacosClusterControllerV3(NacosClusterOperationService nacosClusterOperationService) { + this.nacosClusterOperationService = nacosClusterOperationService; + } + + @GetMapping(value = "/node/self") + @Secured(action = ActionTypes.READ, resource = Commons.NACOS_ADMIN_CORE_CONTEXT_V3 + + "/cluster", signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result self() { + return Result.success(nacosClusterOperationService.self()); + } + + /** + * The console displays the list of cluster members. + * + * @param address match address + * @param state match state + * @return members that matches condition + */ + @GetMapping(value = "/node/list") + @Secured(action = ActionTypes.READ, resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/cluster", signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result> listNodes(@RequestParam(value = "address", required = false) String address, + @RequestParam(value = "state", required = false) String state) throws NacosException { + + NodeState nodeState = null; + if (StringUtils.isNoneBlank(state)) { + try { + nodeState = NodeState.valueOf(state.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_STATE, + "Illegal state: " + state); + } + } + return Result.success(nacosClusterOperationService.listNodes(address, nodeState)); + } + + @GetMapping(value = "/node/self/health") + @Secured(action = ActionTypes.READ, resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/cluster", signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result selfHealth() { + return Result.success(nacosClusterOperationService.selfHealth()); + } + + // The client can get all the nacos node information in the current + // cluster according to this interface + + /** + * Other nodes return their own metadata information. + * + * @param nodes List of {@link Member} + * @return {@link RestResult} + */ + @PutMapping(value = "/node/list") + @Secured(action = ActionTypes.WRITE, resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/cluster", signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result updateNodes(@RequestBody List nodes) throws NacosApiException { + if (nodes == null || nodes.size() == 0) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "required parameter 'nodes' is missing"); + } + return Result.success(nacosClusterOperationService.updateNodes(nodes)); + } + + /** + * Addressing mode switch. + * + * @param request {@link LookupUpdateRequest} + * @return {@link RestResult} + */ + @PutMapping(value = "/lookup") + @Secured(action = ActionTypes.WRITE, resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/cluster", signType = SignType.CONSOLE, apiType = ApiType.ADMIN_API) + public Result updateLookup(@RequestBody LookupUpdateRequest request) throws NacosException { + if (request == null || request.getType() == null) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "required parameter 'type' is missing"); + } + return Result.success(nacosClusterOperationService.updateLookup(request)); + } + +} diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3.java b/core/src/main/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3.java new file mode 100644 index 00000000000..0bc44f00069 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3.java @@ -0,0 +1,364 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.controller.v3; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.remote.RequestCallBack; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.request.ServerLoaderInfoRequest; +import com.alibaba.nacos.api.remote.request.ServerReloadRequest; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ServerLoaderInfoResponse; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.cluster.remote.ClusterRpcClientProxy; +import com.alibaba.nacos.core.model.response.ServerLoaderMetric; +import com.alibaba.nacos.core.model.response.ServerLoaderMetrics; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.core.ServerLoaderInfoRequestHandler; +import com.alibaba.nacos.core.remote.core.ServerReloaderRequestHandler; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.alibaba.nacos.core.utils.Commons.NACOS_ADMIN_CORE_CONTEXT_V3; + +/** + * controller to control server loader v3. + * + * @author yunye + * @since 3.0.0-beta + */ +@NacosApi +@RestController +@RequestMapping(NACOS_ADMIN_CORE_CONTEXT_V3 + "/loader") +public class ServerLoaderControllerV3 { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerLoaderControllerV3.class); + + private static final String X_REAL_IP = "X-Real-IP"; + + private static final String X_FORWARDED_FOR = "X-Forwarded-For"; + + private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ","; + + private final ConnectionManager connectionManager; + + private final ServerMemberManager serverMemberManager; + + private final ClusterRpcClientProxy clusterRpcClientProxy; + + private final ServerReloaderRequestHandler serverReloaderRequestHandler; + + private final ServerLoaderInfoRequestHandler serverLoaderInfoRequestHandler; + + public ServerLoaderControllerV3(ConnectionManager connectionManager, ServerMemberManager serverMemberManager, + ClusterRpcClientProxy clusterRpcClientProxy, ServerReloaderRequestHandler serverReloaderRequestHandler, + ServerLoaderInfoRequestHandler serverLoaderInfoRequestHandler) { + this.connectionManager = connectionManager; + this.serverMemberManager = serverMemberManager; + this.clusterRpcClientProxy = clusterRpcClientProxy; + this.serverReloaderRequestHandler = serverReloaderRequestHandler; + this.serverLoaderInfoRequestHandler = serverLoaderInfoRequestHandler; + } + + /** + * Get current clients. + * + * @return state json. + */ + @GetMapping("/current") + @Secured(resource = NACOS_ADMIN_CORE_CONTEXT_V3 + "/loader", action = ActionTypes.READ, apiType = ApiType.ADMIN_API) + public Result> currentClients() { + Map stringConnectionMap = connectionManager.currentClients(); + return Result.success(stringConnectionMap); + } + + /** + * Rebalance the number of sdk connections on the current server. + * + * @return state json. + */ + @Secured(resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/loader", action = ActionTypes.WRITE, apiType = ApiType.ADMIN_API) + @GetMapping("/reloadCurrent") + public Result reloadCount(@RequestParam Integer count, + @RequestParam(value = "redirectAddress", required = false) String redirectAddress) { + connectionManager.loadCount(count, redirectAddress); + return Result.success(); + } + + + /** + * According to the total number of sdk connections of all nodes in the nacos cluster, intelligently balance the + * number of sdk connections of each node in the nacos cluster. + * + * @return state json. + */ + @GetMapping("/smartReloadCluster") + @Secured(resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/loader", action = ActionTypes.WRITE, apiType = ApiType.ADMIN_API) + public Result smartReload(HttpServletRequest request, + @RequestParam(value = "loaderFactor", defaultValue = "0.1f") String loaderFactorStr) { + + LOGGER.info("Smart reload request receive,requestIp={}", getRemoteIp(request)); + + ServerLoaderMetrics serverLoadMetrics = getServerLoadMetrics(); + List details = serverLoadMetrics.getDetail(); + float loaderFactor = Float.parseFloat(loaderFactorStr); + int overLimitCount = (int) (serverLoadMetrics.getAvg() * (1 + loaderFactor)); + int lowLimitCount = (int) (serverLoadMetrics.getAvg() * (1 - loaderFactor)); + + List overLimitServer = new ArrayList<>(); + List lowLimitServer = new ArrayList<>(); + + for (ServerLoaderMetric metric : details) { + int sdkCount = metric.getSdkConCount(); + if (sdkCount > overLimitCount) { + overLimitServer.add(metric); + } + if (sdkCount < lowLimitCount) { + lowLimitServer.add(metric); + } + } + + // desc by sdkConCount + overLimitServer.sort((o1, o2) -> { + Integer sdkCount1 = o1.getSdkConCount(); + Integer sdkCount2 = o2.getSdkConCount(); + return sdkCount2.compareTo(sdkCount1); + }); + + LOGGER.info("Over load limit server list ={}", overLimitServer); + + //asc by sdkConCount + lowLimitServer.sort((o1, o2) -> { + Integer sdkCount1 = o1.getSdkConCount(); + Integer sdkCount2 = o2.getSdkConCount(); + return sdkCount1.compareTo(sdkCount2); + }); + + LOGGER.info("Low load limit server list ={}", lowLimitServer); + AtomicBoolean result = new AtomicBoolean(true); + + for (int i = 0; i < overLimitServer.size() & i < lowLimitServer.size(); i++) { + ServerReloadRequest serverLoaderInfoRequest = new ServerReloadRequest(); + serverLoaderInfoRequest.setReloadCount(overLimitCount); + serverLoaderInfoRequest.setReloadServer(lowLimitServer.get(i).getAddress()); + Member member = serverMemberManager.find(overLimitServer.get(i).getAddress()); + + LOGGER.info("Reload task submit ,fromServer ={},toServer={}, ", overLimitServer.get(i).getAddress(), + lowLimitServer.get(i).getAddress()); + + if (serverMemberManager.getSelf().equals(member)) { + try { + serverReloaderRequestHandler.handle(serverLoaderInfoRequest, new RequestMeta()); + } catch (NacosException e) { + LOGGER.error("Fail to loader self server", e); + result.set(false); + } + } else { + + try { + clusterRpcClientProxy.asyncRequest(member, serverLoaderInfoRequest, new RequestCallBack() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public long getTimeout() { + return 100L; + } + + @Override + public void onResponse(Response response) { + if (response == null || !response.isSuccess()) { + LOGGER.error("Fail to loader member={},response={}", member.getAddress(), response); + result.set(false); + + } + } + + @Override + public void onException(Throwable e) { + LOGGER.error("Fail to loader member={}", member.getAddress(), e); + result.set(false); + } + }); + } catch (NacosException e) { + LOGGER.error("Fail to loader member={}", member.getAddress(), e); + result.set(false); + } + } + } + + return result.get() ? Result.success() : Result.failure(ErrorCode.SERVER_ERROR); + } + + /** + * Send a ConnectResetRequest to this connection according to the sdk connection ID. + * + * @return state json. + */ + @GetMapping("/reloadClient") + @Secured(resource = NACOS_ADMIN_CORE_CONTEXT_V3 + + "/loader", action = ActionTypes.WRITE, apiType = ApiType.ADMIN_API) + public Result reloadSingle(@RequestParam String connectionId, + @RequestParam(value = "redirectAddress", required = false) String redirectAddress) { + connectionManager.loadSingle(connectionId, redirectAddress); + return Result.success(); + } + + /** + * Get current clients. + * + * @return state json. + */ + @GetMapping("/cluster") + @Secured(resource = NACOS_ADMIN_CORE_CONTEXT_V3 + "/loader", action = ActionTypes.READ, apiType = ApiType.ADMIN_API) + public Result loaderMetrics() { + return Result.success(getServerLoadMetrics()); + } + + private ServerLoaderMetrics getServerLoadMetrics() { + + List responseList = new CopyOnWriteArrayList<>(); + + // default include self. + int memberSize = serverMemberManager.allMembersWithoutSelf().size(); + CountDownLatch countDownLatch = new CountDownLatch(memberSize); + for (Member member : serverMemberManager.allMembersWithoutSelf()) { + if (MemberUtil.isSupportedLongCon(member)) { + ServerLoaderInfoRequest serverLoaderInfoRequest = new ServerLoaderInfoRequest(); + + try { + clusterRpcClientProxy.asyncRequest(member, serverLoaderInfoRequest, new RequestCallBack() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public long getTimeout() { + return 200L; + } + + @Override + public void onResponse(Response response) { + if (response instanceof ServerLoaderInfoResponse) { + ServerLoaderMetric.Builder builder = ServerLoaderMetric.Builder.newBuilder(); + builder.withAddress(member.getAddress()) + .convertFromMap(((ServerLoaderInfoResponse) response).getLoaderMetrics()); + responseList.add(builder.build()); + } + countDownLatch.countDown(); + } + + @Override + public void onException(Throwable e) { + LOGGER.error("Get metrics fail,member={}", member.getAddress(), e); + countDownLatch.countDown(); + } + }); + } catch (NacosException e) { + LOGGER.error("Get metrics fail,member={}", member.getAddress(), e); + countDownLatch.countDown(); + } + } else { + countDownLatch.countDown(); + } + } + + try { + ServerLoaderInfoResponse handle = serverLoaderInfoRequestHandler.handle(new ServerLoaderInfoRequest(), + new RequestMeta()); + ServerLoaderMetric.Builder builder = ServerLoaderMetric.Builder.newBuilder(); + builder.withAddress(serverMemberManager.getSelf().getAddress()).convertFromMap(handle.getLoaderMetrics()); + responseList.add(builder.build()); + } catch (NacosException e) { + LOGGER.error("Get self metrics fail", e); + } + + try { + countDownLatch.await(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LOGGER.warn("Get metrics timeout,metrics info may not complete."); + } + int max = Integer.MIN_VALUE; + int min = Integer.MAX_VALUE; + int total = 0; + + for (ServerLoaderMetric serverLoaderMetric : responseList) { + int sdkConCount = serverLoaderMetric.getSdkConCount(); + + if (max < sdkConCount) { + max = sdkConCount; + } + if (min > sdkConCount) { + min = sdkConCount; + } + total += sdkConCount; + } + + responseList.sort(Comparator.comparing(ServerLoaderMetric::getAddress)); + ServerLoaderMetrics serverLoaderMetrics = new ServerLoaderMetrics(); + serverLoaderMetrics.setDetail(responseList); + serverLoaderMetrics.setMemberCount(serverMemberManager.allMembers().size()); + serverLoaderMetrics.setMetricsCount(responseList.size()); + serverLoaderMetrics.setCompleted(responseList.size() == serverMemberManager.allMembers().size()); + serverLoaderMetrics.setMax(max); + serverLoaderMetrics.setMin(min); + serverLoaderMetrics.setAvg(total / responseList.size()); + serverLoaderMetrics.setThreshold(String.valueOf(serverLoaderMetrics.getAvg() * 1.1d)); + serverLoaderMetrics.setTotal(total); + + return serverLoaderMetrics; + } + + private static String getRemoteIp(HttpServletRequest request) { + String xForwardedFor = request.getHeader(X_FORWARDED_FOR); + if (!StringUtils.isBlank(xForwardedFor)) { + return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim(); + } + String nginxHeader = request.getHeader(X_REAL_IP); + return StringUtils.isBlank(nginxHeader) ? request.getRemoteAddr() : nginxHeader; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/model/form/v3/RaftCommandForm.java b/core/src/main/java/com/alibaba/nacos/core/model/form/v3/RaftCommandForm.java new file mode 100644 index 00000000000..e6915132b5c --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/model/form/v3/RaftCommandForm.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.model.form.v3; + +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.core.distributed.raft.utils.JRaftConstants; +import com.alibaba.nacos.core.model.form.NacosForm; + +import java.util.HashMap; +import java.util.Map; + +/** + * Raft command form. + * + * @author yunye + * @since 3.0.0-beta + */ +public class RaftCommandForm implements NacosForm { + + /** + * Target raft group id. + */ + private String groupId; + + /** + * Raft command. Valid values: "transferLeader", "doSnapshot", "resetRaftCluster", "removePeer". + */ + private String command; + + /** + * Command value. The format: {raft_server_ip}:{raft_port}[,{raft_server_ip}:{raft_port}] + */ + private String value; + + @Override + public void validate() throws NacosApiException { + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + /** + * convert to raft execute arguments. + * + * @return args map. + */ + public Map toMap() { + Map map = new HashMap<>(4); + map.put(JRaftConstants.GROUP_ID, groupId); + map.put(JRaftConstants.COMMAND_NAME, command); + map.put(JRaftConstants.COMMAND_VALUE, value); + return map; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetric.java b/core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetric.java new file mode 100644 index 00000000000..c3e15b59291 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetric.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.model.response; + +import com.alibaba.nacos.common.utils.StringUtils; + +import java.util.Map; + +/** + * Server loader metric. + * + * @author yunye + * @since 3.0.0-beta + */ +public class ServerLoaderMetric { + + private String address; + + private int sdkConCount; + + private int conCount; + + private String load; + + private String cpu; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getSdkConCount() { + return sdkConCount; + } + + public void setSdkConCount(int sdkConCount) { + this.sdkConCount = sdkConCount; + } + + public int getConCount() { + return conCount; + } + + public void setConCount(int conCount) { + this.conCount = conCount; + } + + public String getLoad() { + return load; + } + + public void setLoad(String load) { + this.load = load; + } + + public String getCpu() { + return cpu; + } + + public void setCpu(String cpu) { + this.cpu = cpu; + } + + public static class Builder { + + private ServerLoaderMetric serverLoaderMetric = new ServerLoaderMetric(); + + public static Builder newBuilder() { + return new Builder(); + } + + public ServerLoaderMetric build() { + return serverLoaderMetric; + } + + public Builder withAddress(String address) { + serverLoaderMetric.setAddress(address); + return this; + } + + /** + * convert map to {@link ServerLoaderMetric}. + * + * @param metric map of server loader metric + * @return builder + */ + public Builder convertFromMap(Map metric) { + serverLoaderMetric.setSdkConCount(convertInt(metric, "sdkConCount", 0)); + serverLoaderMetric.setConCount(convertInt(metric, "conCount", 0)); + serverLoaderMetric.setLoad(metric.get("load")); + serverLoaderMetric.setCpu(metric.get("cpu")); + return this; + } + + private int convertInt(Map metric, String key, int defaultValue) { + String value = metric.get(key); + if (StringUtils.isNotBlank(value)) { + return Integer.parseInt(value); + } + return defaultValue; + } + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetrics.java b/core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetrics.java new file mode 100644 index 00000000000..2e99c3dcf7e --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/model/response/ServerLoaderMetrics.java @@ -0,0 +1,145 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.model.response; + +import java.util.List; + +/** + * Server loader metric summary. + * + * @author yunye + * @since 3.0.0-beta + */ +public class ServerLoaderMetrics { + + /** + * load metric of all servers. + */ + private List detail; + + /** + * The number of all server nodes. + */ + private int memberCount; + + /** + * The number of server nodes with load metric data. + */ + private int metricsCount; + + /** + * Whether all server nodes return load indicators. + */ + private boolean completed; + + /** + * The maximum number of SDK connections for the server node. + */ + private int max; + + /** + * The minimum number of SDK connections for the server node. + */ + private int min; + + /** + * total / server size. + */ + private int avg; + + /** + * (total / server size) * 1.1 . + */ + private String threshold; + + /** + * The total number of SDK connections for all server nodes. + */ + private int total; + + public List getDetail() { + return detail; + } + + public void setDetail(List detail) { + this.detail = detail; + } + + public int getMemberCount() { + return memberCount; + } + + public void setMemberCount(int memberCount) { + this.memberCount = memberCount; + } + + public int getMetricsCount() { + return metricsCount; + } + + public void setMetricsCount(int metricsCount) { + this.metricsCount = metricsCount; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public int getMax() { + return max; + } + + public void setMax(int max) { + this.max = max; + } + + public int getMin() { + return min; + } + + public void setMin(int min) { + this.min = min; + } + + public int getAvg() { + return avg; + } + + public void setAvg(int avg) { + this.avg = avg; + } + + public String getThreshold() { + return threshold; + } + + public void setThreshold(String threshold) { + this.threshold = threshold; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/Commons.java b/core/src/main/java/com/alibaba/nacos/core/utils/Commons.java index 1a6d95b1d33..4e0f30d3814 100644 --- a/core/src/main/java/com/alibaba/nacos/core/utils/Commons.java +++ b/core/src/main/java/com/alibaba/nacos/core/utils/Commons.java @@ -29,11 +29,15 @@ public final class Commons { public static final String NACOS_SERVER_VERSION_V2 = "/v2"; + public static final String NACOS_SERVER_VERSION_V3 = "/v3"; + public static final String DEFAULT_NACOS_CORE_CONTEXT = NACOS_SERVER_VERSION + "/core"; public static final String NACOS_CORE_CONTEXT = DEFAULT_NACOS_CORE_CONTEXT; public static final String NACOS_CORE_CONTEXT_V2 = NACOS_SERVER_VERSION_V2 + "/core"; + public static final String NACOS_ADMIN_CORE_CONTEXT_V3 = NACOS_SERVER_VERSION_V3 + "/admin/core"; + } diff --git a/core/src/test/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3Test.java b/core/src/test/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3Test.java new file mode 100644 index 00000000000..454c1c2a69d --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/controller/v3/CoreOpsControllerV3Test.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.core.controller.v3; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.consistency.IdGenerator; +import com.alibaba.nacos.consistency.cp.CPProtocol; +import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; +import com.alibaba.nacos.core.distributed.id.SnowFlowerIdGenerator; +import com.alibaba.nacos.core.model.form.v3.RaftCommandForm; +import com.alibaba.nacos.core.model.request.LogUpdateRequest; +import com.alibaba.nacos.core.model.vo.IdGeneratorVO; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.env.MockEnvironment; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link CoreOpsControllerV3} unit test. + * + * @author yunye + * @since 3.0.0-bate + */ +@ExtendWith(MockitoExtension.class) +class CoreOpsControllerV3Test { + + private final MockEnvironment mockEnvironment = new MockEnvironment(); + + @InjectMocks + private CoreOpsControllerV3 coreOpsControllerV3; + + @Mock + private ProtocolManager protocolManager; + + @Mock + private IdGeneratorManager idGeneratorManager; + + @BeforeEach + void setUp() { + EnvUtil.setEnvironment(mockEnvironment); + } + + @Test + void testRaftOps() { + Mockito.when(protocolManager.getCpProtocol()).thenAnswer(invocationOnMock -> { + CPProtocol cpProtocol = Mockito.mock(CPProtocol.class); + Mockito.when(cpProtocol.execute(Mockito.anyMap())).thenReturn(RestResultUtils.success("test")); + return cpProtocol; + }); + + Result result = coreOpsControllerV3.raftOps(new RaftCommandForm()); + assertEquals("test", result.getData()); + } + + @Test + void testIds() { + mockEnvironment.setProperty("nacos.core.snowflake.worker-id", "1"); + + Map idGeneratorMap = new HashMap<>(); + idGeneratorMap.put("resource", new SnowFlowerIdGenerator()); + Mockito.when(idGeneratorManager.getGeneratorMap()).thenReturn(idGeneratorMap); + Result> res = coreOpsControllerV3.ids(); + + assertEquals(ErrorCode.SUCCESS.getCode(), res.getCode()); + assertEquals(1, res.getData().size()); + assertEquals("resource", res.getData().get(0).getResource()); + assertEquals(1L, res.getData().get(0).getInfo().getWorkerId().longValue()); + assertEquals(0L, res.getData().get(0).getInfo().getCurrentId().longValue()); + } + + @Test + void testUpdateLog() { + LogUpdateRequest request = new LogUpdateRequest(); + request.setLogName("core"); + request.setLogLevel("debug"); + Result res = coreOpsControllerV3.updateLog(request); + + assertEquals(ErrorCode.SUCCESS.getCode(), res.getCode()); + assertTrue(Loggers.CORE.isDebugEnabled()); + } +} diff --git a/core/src/test/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3Test.java b/core/src/test/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3Test.java new file mode 100644 index 00000000000..21a15ce6f89 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/controller/v3/NacosClusterControllerV3Test.java @@ -0,0 +1,134 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.core.controller.v3; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.NodeState; +import com.alibaba.nacos.core.model.request.LookupUpdateRequest; +import com.alibaba.nacos.core.service.NacosClusterOperationService; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.env.MockEnvironment; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link NacosClusterControllerV3} unit test. + * + * @author yunye + * @since 3.0.0-bate + */ +@ExtendWith(MockitoExtension.class) +class NacosClusterControllerV3Test { + + private final MockEnvironment mockEnvironment = new MockEnvironment(); + + @InjectMocks + private NacosClusterControllerV3 nacosClusterControllerV3; + + @Mock + private NacosClusterOperationService nacosClusterOperationService; + + @BeforeEach + void setUp() { + EnvUtil.setEnvironment(mockEnvironment); + } + + @Test + void testSelf() { + Member self = new Member(); + when(nacosClusterOperationService.self()).thenReturn(self); + + Result result = nacosClusterControllerV3.self(); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals(self, result.getData()); + } + + @Test + void testListNodes() throws NacosException { + Member member1 = new Member(); + member1.setIp("1.1.1.1"); + member1.setPort(8848); + member1.setState(NodeState.DOWN); + Member member2 = new Member(); + member2.setIp("2.2.2.2"); + member2.setPort(8848); + + List members = Arrays.asList(member1, member2); + Mockito.when(nacosClusterOperationService.listNodes(any(), any())).thenReturn(members); + + Result> result = nacosClusterControllerV3.listNodes("1.1.1.1", null); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertTrue(result.getData().stream().findFirst().isPresent()); + assertEquals("1.1.1.1:8848", result.getData().stream().findFirst().get().getAddress()); + } + + @Test + void testSelfHealth() { + String selfHealth = "UP"; + when(nacosClusterOperationService.selfHealth()).thenReturn(selfHealth); + + Result result = nacosClusterControllerV3.selfHealth(); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals(selfHealth, result.getData()); + } + + @Test + void testUpdateNodes() throws NacosApiException { + Member member = new Member(); + member.setIp("1.1.1.1"); + member.setPort(8848); + member.setAddress("test"); + when(nacosClusterOperationService.updateNodes(any())).thenReturn(true); + Result result = nacosClusterControllerV3.updateNodes(Collections.singletonList(member)); + verify(nacosClusterOperationService).updateNodes(any()); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertTrue(result.getData()); + } + + @Test + void testUpdateLookup() throws NacosException { + LookupUpdateRequest request = new LookupUpdateRequest(); + request.setType("test"); + + when(nacosClusterOperationService.updateLookup(any())).thenReturn(true); + Result result = nacosClusterControllerV3.updateLookup(request); + verify(nacosClusterOperationService).updateLookup(any()); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertTrue(result.getData()); + } +} diff --git a/core/src/test/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3Test.java b/core/src/test/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3Test.java new file mode 100644 index 00000000000..2857562864d --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/controller/v3/ServerLoaderControllerV3Test.java @@ -0,0 +1,158 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.core.controller.v3; + +import com.alibaba.nacos.api.ability.ServerAbilities; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.remote.ability.ServerRemoteAbility; +import com.alibaba.nacos.api.remote.response.ServerLoaderInfoResponse; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.cluster.remote.ClusterRpcClientProxy; +import com.alibaba.nacos.core.model.response.ServerLoaderMetrics; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.core.ServerLoaderInfoRequestHandler; +import com.alibaba.nacos.core.remote.core.ServerReloaderRequestHandler; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link ServerLoaderControllerV3} unit test. + * + * @author yunye + * @since 3.0.0-bate + */ +@ExtendWith(MockitoExtension.class) +class ServerLoaderControllerV3Test { + + @InjectMocks + private ServerLoaderControllerV3 serverLoaderControllerV3; + + @Mock + private ConnectionManager connectionManager; + + @Mock + private ServerMemberManager serverMemberManager; + + @Mock + private ServerLoaderInfoRequestHandler serverLoaderInfoRequestHandler; + + @Mock + private ClusterRpcClientProxy clusterRpcClientProxy; + + @Mock + private ServerReloaderRequestHandler serverReloaderRequestHandler; + + @Test + void testCurrentClients() { + Mockito.when(connectionManager.currentClients()).thenReturn(new HashMap<>()); + + Result> result = serverLoaderControllerV3.currentClients(); + assertEquals(0, result.getData().size()); + } + + @Test + void testReloadCount() { + Result result = serverLoaderControllerV3.reloadCount(1, "1.1.1.1"); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals(ErrorCode.SUCCESS.getMsg(), result.getMessage()); + } + + @Test + void testSmartReload() throws NacosException { + EnvUtil.setEnvironment(new MockEnvironment()); + Member member = new Member(); + member.setIp("1.1.1.1"); + member.setPort(8848); + Mockito.when(serverMemberManager.allMembersWithoutSelf()).thenReturn(Collections.singletonList(member)); + + Map metrics = new HashMap<>(); + metrics.put("conCount", "1"); + metrics.put("sdkConCount", "1"); + ServerLoaderInfoResponse serverLoaderInfoResponse = new ServerLoaderInfoResponse(); + serverLoaderInfoResponse.setLoaderMetrics(metrics); + Mockito.when(serverLoaderInfoRequestHandler.handle(Mockito.any(), Mockito.any())) + .thenReturn(serverLoaderInfoResponse); + + Mockito.when(serverMemberManager.getSelf()).thenReturn(member); + + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + Result result = serverLoaderControllerV3.smartReload(httpServletRequest, "1"); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals(ErrorCode.SUCCESS.getMsg(), result.getMessage()); + } + + @Test + void testReloadSingle() { + Result result = serverLoaderControllerV3.reloadSingle("111", "1.1.1.1"); + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals(ErrorCode.SUCCESS.getMsg(), result.getMessage()); + } + + @Test + void testLoaderMetrics() throws NacosException { + EnvUtil.setEnvironment(new MockEnvironment()); + Member member = new Member(); + member.setIp("1.1.1.1"); + member.setPort(8848); + ServerAbilities serverAbilities = new ServerAbilities(); + ServerRemoteAbility serverRemoteAbility = new ServerRemoteAbility(); + serverRemoteAbility.setSupportRemoteConnection(true); + serverAbilities.setRemoteAbility(serverRemoteAbility); + member.setAbilities(serverAbilities); + Mockito.when(serverMemberManager.allMembersWithoutSelf()).thenReturn(Collections.singletonList(member)); + + Map metrics = new HashMap<>(); + metrics.put("sdkConCount", "1"); + metrics.put("conCount", "2"); + metrics.put("load", "3"); + metrics.put("cpu", "4"); + ServerLoaderInfoResponse serverLoaderInfoResponse = new ServerLoaderInfoResponse(); + serverLoaderInfoResponse.setLoaderMetrics(metrics); + Mockito.when(serverLoaderInfoRequestHandler.handle(Mockito.any(), Mockito.any())) + .thenReturn(serverLoaderInfoResponse); + + Mockito.when(serverMemberManager.getSelf()).thenReturn(member); + + Result result = serverLoaderControllerV3.loaderMetrics(); + + assertEquals(1, result.getData().getDetail().size()); + assertEquals(1, result.getData().getDetail().get(0).getSdkConCount()); + assertEquals(2, result.getData().getDetail().get(0).getConCount()); + assertEquals("3", result.getData().getDetail().get(0).getLoad()); + assertEquals("4", result.getData().getDetail().get(0).getCpu()); + assertEquals("1.1.1.1:8848", result.getData().getDetail().get(0).getAddress()); + } +}