This is an automated email from the ASF dual-hosted git repository. liuhongyu pushed a commit to branch feat/upstream-manual-status in repository https://gitbox.apache.org/repos/asf/shenyu.git
commit 911f043ee903cf29b13c7f05b9783fbc9f362298 Author: liuhy <[email protected]> AuthorDate: Wed Apr 1 20:05:39 2026 +0800 feat: add manual discovery upstream status --- .../plans/2026-04-01-upstream-manual-status.md | 88 +++++++++++++++++++++ .../2026-04-01-upstream-manual-status-design.md | 92 ++++++++++++++++++++++ .../admin/controller/UpstreamController.java | 71 +++++++++++++++++ .../admin/mapper/DiscoveryUpstreamMapper.java | 11 +++ .../admin/model/dto/DiscoveryUpstreamDTO.java | 25 ++++++ .../admin/model/dto/UpstreamManualStatusDTO.java | 74 +++++++++++++++++ .../admin/model/entity/DiscoveryUpstreamDO.java | 42 ++++++++++ .../shenyu/admin/model/vo/DiscoveryUpstreamVO.java | 23 ++++++ .../admin/service/DiscoveryUpstreamService.java | 10 +++ .../service/impl/DiscoveryUpstreamServiceImpl.java | 60 +++++++++++++- .../shenyu/admin/transfer/DiscoveryTransfer.java | 11 +++ .../shenyu/admin/utils/CommonUpstreamUtils.java | 2 + .../mappers/discovery-upstream-sqlmap.xml | 21 ++++- .../src/main/resources/sql-script/h2/schema.sql | 1 + .../service/DiscoveryUpstreamServiceTest.java | 53 ++++++++++++- .../shenyu/common/dto/DiscoveryUpstreamData.java | 43 +++++++++- .../dto/convert/selector/CommonUpstream.java | 34 ++++++++ .../common/dto/convert/selector/GrpcUpstream.java | 17 ++++ .../common/enums/UpstreamManualStatusEnum.java | 60 ++++++++++++++ .../shenyu/loadbalancer/entity/Upstream.java | 51 ++++++++++++ .../loadbalancer/factory/LoadBalancerFactory.java | 13 ++- .../factory/LoadBalancerFactoryTest.java | 26 ++++++ .../divide/handler/DivideUpstreamDataHandler.java | 1 + .../handler/DivideUpstreamDataHandlerTest.java | 20 +++++ .../handler/GrpcDiscoveryUpstreamDataHandler.java | 1 + .../grpc/loadbalance/picker/ShenyuPicker.java | 5 ++ .../handler/WebSocketUpstreamDataHandler.java | 1 + 27 files changed, 850 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/plans/2026-04-01-upstream-manual-status.md b/docs/superpowers/plans/2026-04-01-upstream-manual-status.md new file mode 100644 index 0000000000..cc377a0e92 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-upstream-manual-status.md @@ -0,0 +1,88 @@ +# Upstream Manual Status Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add persisted manual upstream offline control and make Admin, sync payloads, and Gateway selection honor it end to end. + +**Architecture:** Introduce a shared `UpstreamManualStatusEnum`, persist it on `discovery_upstream`, update Admin service/controller flows to publish sync events after manual changes, and carry the new field through sync DTOs into Gateway cache objects where load-balancer selection filters forced-offline upstreams. + +**Tech Stack:** Java, Spring MVC, MyBatis, Maven, JUnit 5, Mockito + +--- + +### Task 1: Add Failing Admin Tests + +**Files:** +- Modify: `shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java` +- Modify: `shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SyncDataServiceTest.java` +- Test: `shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java` + +- [ ] **Step 1: Write failing tests for manual status update and status short-circuit** +- [ ] **Step 2: Write failing assertions that sync payload exposes `manualStatus`** +- [ ] **Step 3: Run admin tests to verify they fail for missing field and behavior** +- [ ] **Step 4: Keep failures focused on the new contract** + +### Task 2: Add Failing Gateway Tests + +**Files:** +- Modify: `shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactoryTest.java` +- Modify: `shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/test/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandlerTest.java` +- Test: `shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactoryTest.java` + +- [ ] **Step 1: Add a failing load-balancer test that excludes `FORCE_OFFLINE` upstreams** +- [ ] **Step 2: Add a failing divide handler test that maps sync payload `manualStatus` into cached upstreams** +- [ ] **Step 3: Run targeted gateway tests to verify red state** + +### Task 3: Implement Shared Enum And DTO Changes + +**Files:** +- Create: `shenyu-common/src/main/java/org/apache/shenyu/common/enums/UpstreamManualStatusEnum.java` +- Modify: `shenyu-common/src/main/java/org/apache/shenyu/common/dto/DiscoveryUpstreamData.java` +- Modify: `shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java` + +- [ ] **Step 1: Add the shared enum with `NONE` and `FORCE_OFFLINE`** +- [ ] **Step 2: Extend sync DTO and cached upstream entity with `manualStatus`** +- [ ] **Step 3: Keep defaults backward compatible with `NONE`** + +### Task 4: Implement Admin Persistence And API + +**Files:** +- Modify: `shenyu-admin/src/main/resources/sql-script/h2/schema.sql` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/DiscoveryUpstreamDO.java` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoveryUpstreamDTO.java` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DiscoveryUpstreamVO.java` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/DiscoveryUpstreamMapper.java` +- Modify: `shenyu-admin/src/main/resources/mappers/discovery-upstream-sqlmap.xml` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/service/DiscoveryUpstreamService.java` +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java` +- Create: `shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/UpstreamManualStatusDTO.java` +- Create: `shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/UpstreamController.java` + +- [ ] **Step 1: Persist `manual_status` and map it through DO/DTO/VO/Mapper** +- [ ] **Step 2: Add service methods to change manual status and publish fresh discovery events** +- [ ] **Step 3: Add `/upstream/offline` and `/upstream/online` controller endpoints** + +### Task 5: Implement Heartbeat Short-Circuit And Gateway Filtering + +**Files:** +- Modify: `shenyu-admin/src/main/java/org/apache/shenyu/admin/service/register/AbstractShenyuClientRegisterServiceImpl.java` +- Modify: `shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java` +- Modify: `shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java` +- Modify: `shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java` +- Modify: `shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactory.java` + +- [ ] **Step 1: Prevent alive/status recovery when the DB record is `FORCE_OFFLINE`** +- [ ] **Step 2: Map synced `manualStatus` into plugin-specific upstream cache objects** +- [ ] **Step 3: Filter forced-offline upstreams before selection** + +### Task 6: Verify Green State + +**Files:** +- Modify: `docs/superpowers/specs/2026-04-01-upstream-manual-status-design.md` +- Modify: `docs/superpowers/plans/2026-04-01-upstream-manual-status.md` + +- [ ] **Step 1: Run targeted Maven tests for admin, loadbalancer, and divide modules** +- [ ] **Step 2: Run a focused compile if any cross-module breakage appears** +- [ ] **Step 3: Review git diff for unintended changes** +- [ ] **Step 4: Commit with one feature commit** diff --git a/docs/superpowers/specs/2026-04-01-upstream-manual-status-design.md b/docs/superpowers/specs/2026-04-01-upstream-manual-status-design.md new file mode 100644 index 0000000000..8678b76d98 --- /dev/null +++ b/docs/superpowers/specs/2026-04-01-upstream-manual-status-design.md @@ -0,0 +1,92 @@ +# Upstream Manual Status Design + +## Goal + +Add a persisted manual upstream control flag that lets Admin force a discovery upstream offline without being overwritten by heartbeat recovery, and make Gateway honor that flag during upstream selection. + +## Background + +Today `discovery_upstream.upstream_status` is used for automatic liveness. Admin-triggered manual offline and automatic health recovery share the same status channel, so a heartbeat or recovery path can bring a manually disabled upstream back into traffic. + +## Chosen Approach + +Use a separate manual status field. + +- Persist `manual_status` on `discovery_upstream` with default `NONE`. +- Represent manual control with a shared enum `NONE` and `FORCE_OFFLINE`. +- Keep `upstream_status` for automatic health only. +- Make Admin manual APIs write only `manualStatus`. +- Let heartbeat or recovery logic skip `status=true` updates when `manualStatus == FORCE_OFFLINE`. +- Include `manualStatus` in discovery sync payloads and Gateway cache objects. +- Filter `FORCE_OFFLINE` upstreams before load-balancer selection. + +This keeps automatic and manual state independent and avoids hidden coupling. + +## Alternatives Considered + +### Reuse `upstream_status` + +Rejected because heartbeat and health check would continue to overwrite manual operations. + +### Keep manual state only in Gateway memory + +Rejected because it would not survive restarts or sync across Admin and Gateway nodes. + +## Data Model + +Add `manual_status varchar(32) not null default 'NONE'` to `discovery_upstream`. + +Shared enum: + +- `NONE` +- `FORCE_OFFLINE` + +`/upstream/online` resets the field to `NONE`. + +## Admin API + +Add a new Admin controller rooted at `/upstream` with: + +- `POST /upstream/offline` +- `POST /upstream/online` + +Request body will identify the upstream by `selectorId` and `url`. + +Behavior: + +- Look up the related discovery handler by selector id. +- Update only `manual_status`. +- Publish a fresh `DISCOVER_UPSTREAM` event built from current DB data so gateways receive the new flag immediately. + +## Status Update Rules + +Automatic writers keep their current responsibility for `upstream_status`. + +Additional rule: + +- If a write intends to mark an upstream alive (`status=true`) and the record is `FORCE_OFFLINE`, skip the status update. + +This protects the manual offline decision from heartbeat recovery without blocking automatic offline transitions. + +## Sync Contract + +Extend `DiscoveryUpstreamData` and all transfer paths to include `manualStatus`. + +Admin event producers and Gateway sync consumers will continue to use the same payload shape, now with one extra field. + +## Gateway Behavior + +Extend cached upstream objects with `manualStatus`. + +Gateway will filter out `FORCE_OFFLINE` upstreams before selection. This ensures: + +- Manually offline nodes are never chosen. +- Existing health-check metadata can still be retained. +- Re-enabling an upstream only requires Admin to push a new sync event with `manualStatus=NONE`. + +## Testing Strategy + +- Admin service tests for manual status update and status recovery short-circuit. +- Sync/transfer tests for `manualStatus` propagation. +- Load-balancer tests for filtering `FORCE_OFFLINE`. +- Divide discovery handler test for mapping sync payload to cached upstream manual status. diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/UpstreamController.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/UpstreamController.java new file mode 100644 index 0000000000..2e3a6344b4 --- /dev/null +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/UpstreamController.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.admin.controller; + +import jakarta.validation.Valid; +import org.apache.shenyu.admin.aspect.annotation.RestApi; +import org.apache.shenyu.admin.model.dto.UpstreamManualStatusDTO; +import org.apache.shenyu.admin.model.result.ShenyuAdminResult; +import org.apache.shenyu.admin.service.DiscoveryUpstreamService; +import org.apache.shenyu.admin.utils.ShenyuResultMessage; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * Upstream controller. + */ +@RestApi("/upstream") +public class UpstreamController { + + private final DiscoveryUpstreamService discoveryUpstreamService; + + public UpstreamController(final DiscoveryUpstreamService discoveryUpstreamService) { + this.discoveryUpstreamService = discoveryUpstreamService; + } + + /** + * manual offline. + * + * @param upstreamManualStatusDTO upstream request + * @return result + */ + @PostMapping("/offline") + public ShenyuAdminResult offline(@Valid @RequestBody final UpstreamManualStatusDTO upstreamManualStatusDTO) { + discoveryUpstreamService.changeManualStatusBySelectorIdAndUrl( + upstreamManualStatusDTO.getSelectorId(), + upstreamManualStatusDTO.getUrl(), + UpstreamManualStatusEnum.FORCE_OFFLINE); + return ShenyuAdminResult.success(ShenyuResultMessage.UPDATE_SUCCESS); + } + + /** + * manual online. + * + * @param upstreamManualStatusDTO upstream request + * @return result + */ + @PostMapping("/online") + public ShenyuAdminResult online(@Valid @RequestBody final UpstreamManualStatusDTO upstreamManualStatusDTO) { + discoveryUpstreamService.changeManualStatusBySelectorIdAndUrl( + upstreamManualStatusDTO.getSelectorId(), + upstreamManualStatusDTO.getUrl(), + UpstreamManualStatusEnum.NONE); + return ShenyuAdminResult.success(ShenyuResultMessage.UPDATE_SUCCESS); + } +} diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/DiscoveryUpstreamMapper.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/DiscoveryUpstreamMapper.java index ad049bcc79..2f17f256c4 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/DiscoveryUpstreamMapper.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/DiscoveryUpstreamMapper.java @@ -170,4 +170,15 @@ public interface DiscoveryUpstreamMapper extends ExistProvider { */ int updateStatusByUrl(@Param("discoveryHandlerId") String discoveryHandlerId, @Param("upstreamUrl") String upstreamUrl, @Param("upstreamStatus") int upstreamStatus); + /** + * update manual status by url. + * + * @param discoveryHandlerId discoveryHandlerId + * @param upstreamUrl upstreamUrl + * @param manualStatus manualStatus + * @return effect + */ + int updateManualStatusByUrl(@Param("discoveryHandlerId") String discoveryHandlerId, @Param("upstreamUrl") String upstreamUrl, + @Param("manualStatus") String manualStatus); + } diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoveryUpstreamDTO.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoveryUpstreamDTO.java index dcceeb0bf4..b817dd4a9e 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoveryUpstreamDTO.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoveryUpstreamDTO.java @@ -20,11 +20,13 @@ package org.apache.shenyu.admin.model.dto; import org.apache.shenyu.admin.mapper.DiscoveryUpstreamMapper; import org.apache.shenyu.admin.mapper.NamespaceMapper; import org.apache.shenyu.admin.validation.annotation.Existed; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.io.Serializable; import java.sql.Timestamp; +import java.util.Objects; /** * discovery upstream dto. @@ -92,6 +94,11 @@ public class DiscoveryUpstreamDTO implements Serializable { */ private Timestamp dateUpdated; + /** + * manual status. + */ + private String manualStatus; + /** * getId. * @@ -284,4 +291,22 @@ public class DiscoveryUpstreamDTO implements Serializable { public void setNamespaceId(final String namespaceId) { this.namespaceId = namespaceId; } + + /** + * get manualStatus. + * + * @return manualStatus + */ + public String getManualStatus() { + return manualStatus; + } + + /** + * set manualStatus. + * + * @param manualStatus manualStatus + */ + public void setManualStatus(final String manualStatus) { + this.manualStatus = Objects.isNull(manualStatus) ? null : UpstreamManualStatusEnum.normalize(manualStatus); + } } diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/UpstreamManualStatusDTO.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/UpstreamManualStatusDTO.java new file mode 100644 index 0000000000..8d1b3317b2 --- /dev/null +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/UpstreamManualStatusDTO.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.admin.model.dto; + +import jakarta.validation.constraints.NotBlank; + +/** + * Manual upstream status request. + */ +public class UpstreamManualStatusDTO { + + /** + * selector id. + */ + @NotBlank(message = "selectorId can't be null") + private String selectorId; + + /** + * upstream url. + */ + @NotBlank(message = "url can't be null") + private String url; + + /** + * get selectorId. + * + * @return selectorId + */ + public String getSelectorId() { + return selectorId; + } + + /** + * set selectorId. + * + * @param selectorId selectorId + */ + public void setSelectorId(final String selectorId) { + this.selectorId = selectorId; + } + + /** + * get url. + * + * @return url + */ + public String getUrl() { + return url; + } + + /** + * set url. + * + * @param url url + */ + public void setUrl(final String url) { + this.url = url; + } +} diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/DiscoveryUpstreamDO.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/DiscoveryUpstreamDO.java index 93f899b8cc..68060754d5 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/DiscoveryUpstreamDO.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/DiscoveryUpstreamDO.java @@ -18,6 +18,7 @@ package org.apache.shenyu.admin.model.entity; import org.apache.shenyu.admin.model.dto.DiscoveryUpstreamDTO; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import org.apache.shenyu.common.utils.UUIDUtils; import org.springframework.util.StringUtils; @@ -66,6 +67,11 @@ public class DiscoveryUpstreamDO extends BaseDO { */ private String namespaceId; + /** + * manualStatus. + */ + private String manualStatus = UpstreamManualStatusEnum.NONE.name(); + /** * DiscoveryUpstreamDO. */ @@ -242,6 +248,24 @@ public class DiscoveryUpstreamDO extends BaseDO { this.namespaceId = namespaceId; } + /** + * get manualStatus. + * + * @return manualStatus + */ + public String getManualStatus() { + return manualStatus; + } + + /** + * set manualStatus. + * + * @param manualStatus manualStatus + */ + public void setManualStatus(final String manualStatus) { + this.manualStatus = UpstreamManualStatusEnum.normalize(manualStatus); + } + /** * buildDiscoveryUpstreamDO. * @@ -259,6 +283,7 @@ public class DiscoveryUpstreamDO extends BaseDO { .weight(item.getWeight()) .props(item.getProps()) .url(item.getUrl()) + .manualStatus(item.getManualStatus()) .namespaceId(item.getNamespaceId()) .dateCreated(currentTime) .dateUpdated(currentTime).build(); @@ -327,6 +352,11 @@ public class DiscoveryUpstreamDO extends BaseDO { */ private String namespaceId; + /** + * manualStatus. + */ + private String manualStatus = UpstreamManualStatusEnum.NONE.name(); + /** * id. * @@ -446,6 +476,17 @@ public class DiscoveryUpstreamDO extends BaseDO { return this; } + /** + * build manualStatus. + * + * @param manualStatus manualStatus + * @return this + */ + public DiscoveryUpstreamBuilder manualStatus(final String manualStatus) { + this.manualStatus = UpstreamManualStatusEnum.normalize(manualStatus); + return this; + } + /** * build. * @@ -462,6 +503,7 @@ public class DiscoveryUpstreamDO extends BaseDO { discoveryUpstreamDO.setWeight(this.weight); discoveryUpstreamDO.setProps(this.props); discoveryUpstreamDO.setNamespaceId(this.namespaceId); + discoveryUpstreamDO.setManualStatus(this.manualStatus); discoveryUpstreamDO.setDateCreated(this.dateCreated); discoveryUpstreamDO.setDateUpdated(this.dateUpdated); return discoveryUpstreamDO; diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DiscoveryUpstreamVO.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DiscoveryUpstreamVO.java index a6bfcd42f2..6c888f600c 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DiscoveryUpstreamVO.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DiscoveryUpstreamVO.java @@ -59,6 +59,11 @@ public class DiscoveryUpstreamVO { */ private String startupTime; + /** + * manual status. + */ + private String manualStatus; + /** * getId. * @@ -202,4 +207,22 @@ public class DiscoveryUpstreamVO { public void setStartupTime(final String startupTime) { this.startupTime = startupTime; } + + /** + * get manualStatus. + * + * @return manualStatus + */ + public String getManualStatus() { + return manualStatus; + } + + /** + * set manualStatus. + * + * @param manualStatus manualStatus + */ + public void setManualStatus(final String manualStatus) { + this.manualStatus = manualStatus; + } } diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/DiscoveryUpstreamService.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/DiscoveryUpstreamService.java index d808c194b6..55d2e836bc 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/DiscoveryUpstreamService.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/DiscoveryUpstreamService.java @@ -23,6 +23,7 @@ import org.apache.shenyu.admin.model.vo.DiscoveryUpstreamVO; import org.apache.shenyu.admin.service.configs.ConfigsImportContext; import org.apache.shenyu.common.dto.DiscoverySyncData; import org.apache.shenyu.common.dto.DiscoveryUpstreamData; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import java.util.List; @@ -118,6 +119,15 @@ public interface DiscoveryUpstreamService { */ void changeStatusBySelectorIdAndUrl(String selectorId, String url, Boolean enabled); + /** + * changeManualStatusBySelectorIdAndUrl. + * + * @param selectorId selectorId + * @param url url + * @param manualStatus manualStatus + */ + void changeManualStatusBySelectorIdAndUrl(String selectorId, String url, UpstreamManualStatusEnum manualStatus); + /** * Import the discoveryUpstream data list. * diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java index b626f314fa..ad4fa015b9 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java @@ -19,6 +19,7 @@ package org.apache.shenyu.admin.service.impl; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; +import org.apache.shenyu.admin.listener.DataChangedEvent; import org.apache.shenyu.admin.discovery.DiscoveryProcessor; import org.apache.shenyu.admin.discovery.DiscoveryProcessorHolder; import org.apache.shenyu.admin.mapper.DiscoveryHandlerMapper; @@ -44,6 +45,10 @@ import org.apache.shenyu.admin.transfer.DiscoveryTransfer; import org.apache.shenyu.admin.utils.ShenyuResultMessage; import org.apache.shenyu.common.dto.DiscoverySyncData; import org.apache.shenyu.common.dto.DiscoveryUpstreamData; +import org.apache.shenyu.common.enums.ConfigGroupEnum; +import org.apache.shenyu.common.enums.DataEventTypeEnum; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @@ -74,6 +79,8 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { private final DiscoveryProcessorHolder discoveryProcessorHolder; + private final ApplicationEventPublisher eventPublisher; + public DiscoveryUpstreamServiceImpl(final DiscoveryUpstreamMapper discoveryUpstreamMapper, final DiscoveryHandlerMapper discoveryHandlerMapper, final ProxySelectorMapper proxySelectorMapper, @@ -81,7 +88,8 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { final DiscoveryRelMapper discoveryRelMapper, final SelectorMapper selectorMapper, final PluginMapper pluginMapper, - final DiscoveryProcessorHolder discoveryProcessorHolder) { + final DiscoveryProcessorHolder discoveryProcessorHolder, + final ApplicationEventPublisher eventPublisher) { this.discoveryUpstreamMapper = discoveryUpstreamMapper; this.discoveryProcessorHolder = discoveryProcessorHolder; this.discoveryHandlerMapper = discoveryHandlerMapper; @@ -90,6 +98,7 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { this.selectorMapper = selectorMapper; this.proxySelectorMapper = proxySelectorMapper; this.pluginMapper = pluginMapper; + this.eventPublisher = eventPublisher; } /** @@ -123,6 +132,13 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { public void nativeCreateOrUpdate(final DiscoveryUpstreamDTO discoveryUpstreamDTO) { DiscoveryUpstreamDO discoveryUpstreamDO = DiscoveryUpstreamDO.buildDiscoveryUpstreamDO(discoveryUpstreamDTO); if (StringUtils.hasLength(discoveryUpstreamDTO.getId())) { + if (!StringUtils.hasLength(discoveryUpstreamDTO.getManualStatus())) { + DiscoveryUpstreamDO existingRecord = discoveryUpstreamMapper.selectByDiscoveryHandlerIdAndUrl( + discoveryUpstreamDO.getDiscoveryHandlerId(), discoveryUpstreamDO.getUpstreamUrl()); + if (Objects.nonNull(existingRecord)) { + discoveryUpstreamDO.setManualStatus(existingRecord.getManualStatus()); + } + } discoveryUpstreamMapper.updateSelective(discoveryUpstreamDO); } else { DiscoveryUpstreamDO existingRecord = discoveryUpstreamMapper.selectByDiscoveryHandlerIdAndUrl(discoveryUpstreamDO.getDiscoveryHandlerId(), discoveryUpstreamDO.getUpstreamUrl()); @@ -217,6 +233,11 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { */ private String update(final DiscoveryUpstreamDTO discoveryUpstreamDTO) { DiscoveryUpstreamDO discoveryUpstreamDO = DiscoveryUpstreamDO.buildDiscoveryUpstreamDO(discoveryUpstreamDTO); + if (!StringUtils.hasLength(discoveryUpstreamDTO.getManualStatus())) { + discoveryUpstreamMapper.selectByIds(Collections.singletonList(discoveryUpstreamDTO.getId())).stream() + .findFirst() + .ifPresent(existing -> discoveryUpstreamDO.setManualStatus(existing.getManualStatus())); + } discoveryUpstreamMapper.update(discoveryUpstreamDO); fetchAll(discoveryUpstreamDTO.getDiscoveryHandlerId()); return ShenyuResultMessage.UPDATE_SUCCESS; @@ -246,10 +267,27 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { public void changeStatusBySelectorIdAndUrl(final String selectorId, final String url, final Boolean enabled) { DiscoveryHandlerDO discoveryHandlerDO = discoveryHandlerMapper.selectBySelectorId(selectorId); if (Objects.nonNull(discoveryHandlerDO)) { + if (Boolean.TRUE.equals(enabled)) { + DiscoveryUpstreamDO existingRecord = discoveryUpstreamMapper.selectByDiscoveryHandlerIdAndUrl(discoveryHandlerDO.getId(), url); + if (Objects.nonNull(existingRecord) && UpstreamManualStatusEnum.isForceOffline(existingRecord.getManualStatus())) { + return; + } + } discoveryUpstreamMapper.updateStatusByUrl(discoveryHandlerDO.getId(), url, enabled ? 0 : 1); } } + @Override + @Transactional(rollbackFor = Exception.class) + public void changeManualStatusBySelectorIdAndUrl(final String selectorId, final String url, final UpstreamManualStatusEnum manualStatus) { + DiscoveryHandlerDO discoveryHandlerDO = discoveryHandlerMapper.selectBySelectorId(selectorId); + if (Objects.isNull(discoveryHandlerDO)) { + return; + } + discoveryUpstreamMapper.updateManualStatusByUrl(discoveryHandlerDO.getId(), url, manualStatus.name()); + publishDiscoverySyncEvent(selectorId, discoveryHandlerDO.getId()); + } + @Override @Transactional(rollbackFor = Exception.class) public ConfigImportResult importData(final List<DiscoveryUpstreamDTO> discoveryUpstreamList) { @@ -351,4 +389,24 @@ public class DiscoveryUpstreamServiceImpl implements DiscoveryUpstreamService { discoveryProcessor.changeUpstream(proxySelectorDTO, collect); } + private void publishDiscoverySyncEvent(final String selectorId, final String discoveryHandlerId) { + DiscoveryRelDO discoveryRelDO = discoveryRelMapper.selectByDiscoveryHandlerId(discoveryHandlerId); + SelectorDO selectorDO = selectorMapper.selectById(selectorId); + if (Objects.isNull(discoveryRelDO) || Objects.isNull(selectorDO)) { + return; + } + DiscoverySyncData discoverySyncData = new DiscoverySyncData(); + discoverySyncData.setSelectorId(selectorId); + discoverySyncData.setSelectorName(selectorDO.getSelectorName()); + discoverySyncData.setPluginName(discoveryRelDO.getPluginName()); + discoverySyncData.setNamespaceId(selectorDO.getNamespaceId()); + discoverySyncData.setDiscoveryHandlerId(discoveryHandlerId); + List<DiscoveryUpstreamData> discoveryUpstreamDataList = discoveryUpstreamMapper.selectByDiscoveryHandlerId(discoveryHandlerId).stream() + .map(DiscoveryTransfer.INSTANCE::mapToData) + .collect(Collectors.toList()); + discoverySyncData.setUpstreamDataList(discoveryUpstreamDataList); + eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.DISCOVER_UPSTREAM, + DataEventTypeEnum.UPDATE, Collections.singletonList(discoverySyncData))); + } + } diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java index 91f854cd5f..f233d8b85b 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java @@ -35,6 +35,7 @@ import org.apache.shenyu.admin.utils.CommonUpstreamUtils; import org.apache.shenyu.common.dto.DiscoveryUpstreamData; import org.apache.shenyu.common.dto.ProxySelectorData; import org.apache.shenyu.common.dto.convert.selector.CommonUpstream; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import org.apache.shenyu.common.utils.GsonUtils; import java.util.Optional; @@ -64,6 +65,7 @@ public enum DiscoveryTransfer { .status(data.getStatus()) .weight(data.getWeight()) .props(data.getProps()) + .manualStatus(data.getManualStatus()) .url(data.getUrl()) .dateUpdated(data.getDateUpdated()) .dateCreated(data.getDateCreated()).build()).orElse(null); @@ -85,6 +87,8 @@ public enum DiscoveryTransfer { .orElse(new Properties()); commonUpstream .setHealthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled", "true"))); + commonUpstream.setNamespaceId(data.getNamespaceId()); + commonUpstream.setManualStatus(data.getManualStatus()); return commonUpstream; }).orElse(null); } @@ -106,6 +110,7 @@ public enum DiscoveryTransfer { vo.setWeight(data.getWeight()); vo.setProps(data.getProps()); vo.setStartupTime(String.valueOf(data.getDateCreated().getTime())); + vo.setManualStatus(data.getManualStatus()); return vo; }).orElse(null); } @@ -193,6 +198,8 @@ public enum DiscoveryTransfer { discoveryUpstreamData.setProps(data.getProps()); discoveryUpstreamData.setDateUpdated(data.getDateUpdated()); discoveryUpstreamData.setDateCreated(data.getDateCreated()); + discoveryUpstreamData.setNamespaceId(data.getNamespaceId()); + discoveryUpstreamData.setManualStatus(data.getManualStatus()); return discoveryUpstreamData; }).orElse(null); } @@ -216,6 +223,7 @@ public enum DiscoveryTransfer { discoveryUpstreamData.setNamespaceId(data.getNamespaceId()); discoveryUpstreamData.setDateCreated(data.getDateCreated()); discoveryUpstreamData.setDateUpdated(data.getDateUpdated()); + discoveryUpstreamData.setManualStatus(data.getManualStatus()); return discoveryUpstreamData; }).orElse(null); } @@ -337,6 +345,8 @@ public enum DiscoveryTransfer { discoveryUpstreamDTO.setWeight(data.getWeight()); discoveryUpstreamDTO.setDateCreated(data.getDateCreated()); discoveryUpstreamDTO.setDateUpdated(data.getDateUpdated()); + discoveryUpstreamDTO.setNamespaceId(data.getNamespaceId()); + discoveryUpstreamDTO.setManualStatus(data.getManualStatus()); return discoveryUpstreamDTO; }).orElse(null); } @@ -372,6 +382,7 @@ public enum DiscoveryTransfer { .orElse(new Properties()); properties.setProperty("healthCheckEnabled", String.valueOf(commonUpstream.isHealthCheckEnabled())); discoveryUpstreamDTO.setProps(GsonUtils.getInstance().toJson(properties)); + discoveryUpstreamDTO.setManualStatus(UpstreamManualStatusEnum.normalize(commonUpstream.getManualStatus())); return mapToData(discoveryUpstreamDTO); } } diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java index 6a6cdc5c2e..2f91947cce 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java @@ -251,6 +251,8 @@ public class CommonUpstreamUtils { upstream.getUpstreamHost(), upstream.getUpstreamUrl(), upstream.isStatus(), upstream.getTimestamp()); commonUpstream.setHealthCheckEnabled(upstream.isHealthCheckEnabled()); + commonUpstream.setNamespaceId(upstream.getNamespaceId()); + commonUpstream.setManualStatus(upstream.getManualStatus()); return commonUpstream; }) .collect(Collectors.toList()); diff --git a/shenyu-admin/src/main/resources/mappers/discovery-upstream-sqlmap.xml b/shenyu-admin/src/main/resources/mappers/discovery-upstream-sqlmap.xml index 7b17d713fd..616c25f3ef 100644 --- a/shenyu-admin/src/main/resources/mappers/discovery-upstream-sqlmap.xml +++ b/shenyu-admin/src/main/resources/mappers/discovery-upstream-sqlmap.xml @@ -27,6 +27,7 @@ <result column="protocol" jdbcType="VARCHAR" property="protocol"/> <result column="upstream_url" jdbcType="VARCHAR" property="upstreamUrl"/> <result column="upstream_status" jdbcType="INTEGER" property="upstreamStatus"/> + <result column="manual_status" jdbcType="VARCHAR" property="manualStatus"/> <result column="weight" jdbcType="INTEGER" property="weight"/> <result column="props" jdbcType="VARCHAR" property="props"/> </resultMap> @@ -40,6 +41,7 @@ protocol, upstream_url, upstream_status, + manual_status, weight, props </sql> @@ -58,6 +60,7 @@ protocol, upstream_url, upstream_status, + manual_status, weight, props, date_created, @@ -69,6 +72,7 @@ #{protocol,jdbcType=VARCHAR}, #{upstreamUrl,jdbcType=VARCHAR}, #{upstreamStatus,jdbcType=INTEGER}, + #{manualStatus,jdbcType=VARCHAR}, #{weight,jdbcType=INTEGER}, #{props,jdbcType=VARCHAR}, #{dateCreated, jdbcType=TIMESTAMP}, @@ -82,6 +86,7 @@ protocol=#{protocol,jdbcType=VARCHAR}, upstream_url=#{upstreamUrl,jdbcType=VARCHAR}, upstream_status=#{upstreamStatus,jdbcType=INTEGER}, + manual_status=#{manualStatus,jdbcType=VARCHAR}, weight=#{weight,jdbcType=INTEGER}, props=#{props,jdbcType=VARCHAR}, date_updated=#{dateUpdated, jdbcType=TIMESTAMP} @@ -103,6 +108,9 @@ <if test="upstreamStatus != null"> upstream_status=#{upstreamStatus,jdbcType=INTEGER}, </if> + <if test="manualStatus != null"> + manual_status=#{manualStatus,jdbcType=VARCHAR}, + </if> <if test="weight != null"> weight=#{weight,jdbcType=INTEGER}, </if> @@ -120,6 +128,7 @@ SET protocol=#{protocol,jdbcType=VARCHAR}, upstream_status=#{upstreamStatus,jdbcType=INTEGER}, + manual_status=#{manualStatus,jdbcType=VARCHAR}, weight=#{weight,jdbcType=INTEGER}, props=#{props,jdbcType=VARCHAR}, date_updated=#{dateUpdated, jdbcType=TIMESTAMP} @@ -176,9 +185,11 @@ du.date_created, du.date_updated, du.discovery_handler_id, + du.namespace_id, du.protocol, du.upstream_url, du.upstream_status, + du.manual_status, du.weight, du.props FROM discovery_upstream du @@ -186,7 +197,7 @@ </sql> <select id="selectByDiscoveryHandlerIdAndUrl" - resultType="org.apache.shenyu.admin.model.entity.DiscoveryUpstreamDO"> + resultMap="BaseResultMap"> SELECT <include refid="Base_Column_List"/> from discovery_upstream where discovery_handler_id = #{discoveryHandlerId} and upstream_url = #{upstreamUrl} for update </select> @@ -205,6 +216,7 @@ protocol, upstream_url, upstream_status, + manual_status, weight, props, date_created, @@ -217,6 +229,7 @@ #{item.protocol,jdbcType=VARCHAR}, #{item.upstreamUrl,jdbcType=VARCHAR}, #{item.upstreamStatus,jdbcType=INTEGER}, + #{item.manualStatus,jdbcType=VARCHAR}, #{item.weight,jdbcType=INTEGER}, #{item.props,jdbcType=VARCHAR}, #{item.dateCreated, jdbcType=TIMESTAMP}, @@ -239,4 +252,10 @@ WHERE discovery_handler_id = #{discoveryHandlerId} and upstream_url = #{upstreamUrl} </update> + <update id="updateManualStatusByUrl"> + UPDATE discovery_upstream + SET manual_status = #{manualStatus} + WHERE discovery_handler_id = #{discoveryHandlerId} and upstream_url = #{upstreamUrl} + </update> + </mapper> diff --git a/shenyu-admin/src/main/resources/sql-script/h2/schema.sql b/shenyu-admin/src/main/resources/sql-script/h2/schema.sql index 16dd2dd278..8dfe5dd2c8 100644 --- a/shenyu-admin/src/main/resources/sql-script/h2/schema.sql +++ b/shenyu-admin/src/main/resources/sql-script/h2/schema.sql @@ -1281,6 +1281,7 @@ CREATE TABLE IF NOT EXISTS `discovery_upstream` `protocol` varchar(64) COMMENT 'for http, https, tcp, ws', `upstream_url` varchar(64) NOT NULL COMMENT 'ip:port', `upstream_status` int(0) NOT NULL COMMENT 'type (0, healthy, 1 unhealthy)', + `manual_status` varchar(32) NOT NULL DEFAULT 'NONE' COMMENT 'manual status (NONE, FORCE_OFFLINE)', `weight` int(0) NOT NULL COMMENT 'the weight for lists', `props` text COMMENT 'the other field (json)', `date_created` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'create time', diff --git a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java index 28adf2c609..6562af2b56 100644 --- a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java +++ b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java @@ -17,6 +17,7 @@ package org.apache.shenyu.admin.service; +import org.apache.shenyu.admin.listener.DataChangedEvent; import org.apache.shenyu.admin.discovery.DiscoveryProcessor; import org.apache.shenyu.admin.discovery.DiscoveryProcessorHolder; import org.apache.shenyu.admin.mapper.DiscoveryHandlerMapper; @@ -39,6 +40,7 @@ import org.apache.shenyu.admin.model.vo.DiscoveryUpstreamVO; import org.apache.shenyu.admin.service.impl.DiscoveryUpstreamServiceImpl; import org.apache.shenyu.admin.utils.ShenyuResultMessage; import org.apache.shenyu.common.dto.DiscoverySyncData; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -46,6 +48,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -54,9 +57,12 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -95,6 +101,9 @@ public final class DiscoveryUpstreamServiceTest { @Mock private DiscoveryProcessor discoveryProcessor; + @Mock + private ApplicationEventPublisher eventPublisher; + @BeforeEach public void setUp() { @@ -105,7 +114,8 @@ public final class DiscoveryUpstreamServiceTest { discoveryRelMapper, selectorMapper, pluginMapper, - discoveryProcessorHolder + discoveryProcessorHolder, + eventPublisher ); } @@ -142,8 +152,11 @@ public final class DiscoveryUpstreamServiceTest { when(discoveryHandlerMapper.selectAll()).thenReturn(list); when(discoveryRelMapper.selectByDiscoveryHandlerId(any())).thenReturn(buildDiscoveryRelDO()); when(proxySelectorMapper.selectById(any())).thenReturn(buildProxySelectorDO()); + when(discoveryUpstreamMapper.selectByDiscoveryHandlerId(any())).thenReturn( + Collections.singletonList(buildDiscoveryUpstreamDO("", "123", "url1", UpstreamManualStatusEnum.FORCE_OFFLINE))); List<DiscoverySyncData> dataList = discoveryUpstreamService.listAll(); assertEquals(dataList.size(), list.size()); + assertEquals(UpstreamManualStatusEnum.FORCE_OFFLINE.name(), dataList.get(0).getUpstreamDataList().get(0).getManualStatus()); } @Test @@ -184,6 +197,31 @@ public final class DiscoveryUpstreamServiceTest { discoveryUpstreamService.updateBatch("123", Collections.singletonList(buildDiscoveryUpstreamDTO(""))); } + @Test + public void testChangeManualStatusBySelectorIdAndUrl() { + when(discoveryHandlerMapper.selectBySelectorId("selector_1")).thenReturn(buildDiscoveryHandlerDO()); + when(selectorMapper.selectById("selector_1")).thenReturn(buildSelectorDO()); + when(discoveryRelMapper.selectByDiscoveryHandlerId("123")).thenReturn(buildDiscoveryRelDOWithSelector()); + when(discoveryUpstreamMapper.selectByDiscoveryHandlerId("123")).thenReturn( + Collections.singletonList(buildDiscoveryUpstreamDO("", "123", "url1", UpstreamManualStatusEnum.FORCE_OFFLINE))); + + discoveryUpstreamService.changeManualStatusBySelectorIdAndUrl("selector_1", "url1", UpstreamManualStatusEnum.FORCE_OFFLINE); + + verify(discoveryUpstreamMapper).updateManualStatusByUrl("123", "url1", UpstreamManualStatusEnum.FORCE_OFFLINE.name()); + verify(eventPublisher).publishEvent(any(DataChangedEvent.class)); + } + + @Test + public void testChangeStatusBySelectorIdAndUrlShouldSkipAliveUpdateWhenForceOffline() { + when(discoveryHandlerMapper.selectBySelectorId("selector_1")).thenReturn(buildDiscoveryHandlerDO()); + when(discoveryUpstreamMapper.selectByDiscoveryHandlerIdAndUrl("123", "url1")) + .thenReturn(buildDiscoveryUpstreamDO("", "123", "url1", UpstreamManualStatusEnum.FORCE_OFFLINE)); + + discoveryUpstreamService.changeStatusBySelectorIdAndUrl("selector_1", "url1", Boolean.TRUE); + + verify(discoveryUpstreamMapper, never()).updateStatusByUrl(anyString(), anyString(), anyInt()); + } + private void testUpdate() { when(discoveryUpstreamMapper.update(any())).thenReturn(1); DiscoveryUpstreamDTO discoveryUpstreamDTO = buildDiscoveryUpstreamDTO("123"); @@ -233,12 +271,18 @@ public final class DiscoveryUpstreamServiceTest { } private DiscoveryUpstreamDO buildDiscoveryUpstreamDO(final String id, final String discoveryHandlerId, final String url) { + return buildDiscoveryUpstreamDO(id, discoveryHandlerId, url, UpstreamManualStatusEnum.NONE); + } + + private DiscoveryUpstreamDO buildDiscoveryUpstreamDO(final String id, final String discoveryHandlerId, final String url, + final UpstreamManualStatusEnum manualStatus) { DiscoveryUpstreamDO discoveryUpstreamDO = new DiscoveryUpstreamDO(); discoveryUpstreamDO.setId(id); discoveryUpstreamDO.setUpstreamStatus(1); discoveryUpstreamDO.setWeight(50); discoveryUpstreamDO.setUpstreamUrl(url); discoveryUpstreamDO.setDiscoveryHandlerId(discoveryHandlerId); + discoveryUpstreamDO.setManualStatus(manualStatus.name()); Timestamp now = Timestamp.valueOf(LocalDateTime.now()); discoveryUpstreamDO.setDateCreated(now); discoveryUpstreamDO.setDateUpdated(now); @@ -276,6 +320,7 @@ public final class DiscoveryUpstreamServiceTest { SelectorDO selectorDO = new SelectorDO(); selectorDO.setId("selector_1"); selectorDO.setSelectorName("selector_1"); + selectorDO.setNamespaceId("test"); return selectorDO; } @@ -293,4 +338,10 @@ public final class DiscoveryUpstreamServiceTest { return discoveryRelDO; } + private DiscoveryRelDO buildDiscoveryRelDOWithSelector() { + DiscoveryRelDO discoveryRelDO = buildDiscoveryRelDO(); + discoveryRelDO.setSelectorId("selector_1"); + return discoveryRelDO; + } + } diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/DiscoveryUpstreamData.java b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/DiscoveryUpstreamData.java index 20fcae9946..0567a0ae41 100644 --- a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/DiscoveryUpstreamData.java +++ b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/DiscoveryUpstreamData.java @@ -18,6 +18,7 @@ package org.apache.shenyu.common.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import java.sql.Timestamp; import java.util.Objects; @@ -77,6 +78,11 @@ public class DiscoveryUpstreamData { */ private String namespaceId; + /** + * manualStatus. + */ + private String manualStatus = UpstreamManualStatusEnum.NONE.name(); + /** * getDiscoveryHandlerId. @@ -258,6 +264,24 @@ public class DiscoveryUpstreamData { this.namespaceId = namespaceId; } + /** + * get manualStatus. + * + * @return manualStatus + */ + public String getManualStatus() { + return manualStatus; + } + + /** + * set manualStatus. + * + * @param manualStatus manualStatus + */ + public void setManualStatus(final String manualStatus) { + this.manualStatus = UpstreamManualStatusEnum.normalize(manualStatus); + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -271,12 +295,13 @@ public class DiscoveryUpstreamData { && Objects.equals(dateCreated, that.dateCreated) && Objects.equals(dateUpdated, that.dateUpdated) && Objects.equals(discoveryHandlerId, that.discoveryHandlerId) && Objects.equals(protocol, that.protocol) && Objects.equals(url, that.url) && Objects.equals(props, that.props) - && Objects.equals(namespaceId, that.namespaceId); + && Objects.equals(namespaceId, that.namespaceId) + && Objects.equals(manualStatus, that.manualStatus); } @Override public int hashCode() { - return Objects.hash(id, dateCreated, dateUpdated, discoveryHandlerId, protocol, url, status, weight, props, namespaceId); + return Objects.hash(id, dateCreated, dateUpdated, discoveryHandlerId, protocol, url, status, weight, props, namespaceId, manualStatus); } /** @@ -310,6 +335,8 @@ public class DiscoveryUpstreamData { private String namespaceId; + private String manualStatus = UpstreamManualStatusEnum.NONE.name(); + private Builder() { } @@ -432,6 +459,17 @@ public class DiscoveryUpstreamData { return this; } + /** + * build manualStatus. + * + * @param manualStatus manualStatus + * @return this + */ + public Builder manualStatus(final String manualStatus) { + this.manualStatus = UpstreamManualStatusEnum.normalize(manualStatus); + return this; + } + /** * build new Object. * @@ -449,6 +487,7 @@ public class DiscoveryUpstreamData { discoveryUpstreamData.setWeight(weight); discoveryUpstreamData.setProps(props); discoveryUpstreamData.setNamespaceId(namespaceId); + discoveryUpstreamData.setManualStatus(manualStatus); return discoveryUpstreamData; } } diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java index 13f10e7b1f..f81476b1ff 100644 --- a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java +++ b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java @@ -17,6 +17,8 @@ package org.apache.shenyu.common.dto.convert.selector; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; + import java.util.Objects; /** @@ -64,6 +66,11 @@ public class CommonUpstream { */ private boolean healthCheckEnabled = true; + /** + * manualStatus. + */ + private String manualStatus = UpstreamManualStatusEnum.NONE.name(); + /** * Instantiates a new Common upstream. */ @@ -214,6 +221,33 @@ public class CommonUpstream { this.gray = gray; } + /** + * get manualStatus. + * + * @return manualStatus + */ + public String getManualStatus() { + return manualStatus; + } + + /** + * set manualStatus. + * + * @param manualStatus manualStatus + */ + public void setManualStatus(final String manualStatus) { + this.manualStatus = UpstreamManualStatusEnum.normalize(manualStatus); + } + + /** + * whether manual offline. + * + * @return true if manual offline + */ + public boolean isManualOffline() { + return UpstreamManualStatusEnum.isForceOffline(manualStatus); + } + /** * get healthCheckEnabled. * diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java index 8379e719e8..f35cf4fa39 100644 --- a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java +++ b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java @@ -47,6 +47,7 @@ public final class GrpcUpstream extends CommonUpstream { setTimestamp(builder.timestamp); setNamespaceId(builder.namespaceId); setHealthCheckEnabled(builder.healthCheckEnabled); + setManualStatus(builder.manualStatus); } /** @@ -169,6 +170,11 @@ public final class GrpcUpstream extends CommonUpstream { */ private boolean healthCheckEnabled = true; + /** + * manual status. + */ + private String manualStatus; + /** * no args constructor. */ @@ -272,5 +278,16 @@ public final class GrpcUpstream extends CommonUpstream { this.healthCheckEnabled = healthCheckEnabled; return this; } + + /** + * build manualStatus. + * + * @param manualStatus manualStatus + * @return this + */ + public Builder manualStatus(final String manualStatus) { + this.manualStatus = manualStatus; + return this; + } } } diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/enums/UpstreamManualStatusEnum.java b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/UpstreamManualStatusEnum.java new file mode 100644 index 0000000000..073c4caebb --- /dev/null +++ b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/UpstreamManualStatusEnum.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.common.enums; + +import java.util.Arrays; + +/** + * Manual upstream status. + */ +public enum UpstreamManualStatusEnum { + + /** + * No manual override. + */ + NONE, + + /** + * Force upstream offline manually. + */ + FORCE_OFFLINE; + + /** + * Normalize status value. + * + * @param manualStatus status value + * @return normalized enum name + */ + public static String normalize(final String manualStatus) { + return Arrays.stream(values()) + .filter(value -> value.name().equalsIgnoreCase(manualStatus)) + .findFirst() + .orElse(NONE) + .name(); + } + + /** + * Whether force offline. + * + * @param manualStatus status value + * @return true if force offline + */ + public static boolean isForceOffline(final String manualStatus) { + return FORCE_OFFLINE.name().equalsIgnoreCase(normalize(manualStatus)); + } +} diff --git a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java index d832dc28ff..3363c574d1 100644 --- a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java +++ b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java @@ -18,6 +18,7 @@ package org.apache.shenyu.loadbalancer.entity; import org.apache.commons.lang3.StringUtils; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import java.util.Map; import java.util.Objects; @@ -109,6 +110,11 @@ public final class Upstream { * health check enabled. */ private boolean healthCheckEnabled = true; + + /** + * manualStatus. + */ + private String manualStatus = UpstreamManualStatusEnum.NONE.name(); private Map<String, String> metadata = new ConcurrentHashMap<>(); @@ -133,6 +139,7 @@ public final class Upstream { this.version = builder.version; this.gray = builder.gray; this.healthCheckEnabled = builder.healthCheckEnabled; + this.manualStatus = UpstreamManualStatusEnum.normalize(builder.manualStatus); } /** @@ -251,6 +258,33 @@ public final class Upstream { public void setHealthCheckEnabled(final boolean healthCheckEnabled) { this.healthCheckEnabled = healthCheckEnabled; } + + /** + * Gets manual status. + * + * @return manual status + */ + public String getManualStatus() { + return manualStatus; + } + + /** + * Sets manual status. + * + * @param manualStatus manual status + */ + public void setManualStatus(final String manualStatus) { + this.manualStatus = UpstreamManualStatusEnum.normalize(manualStatus); + } + + /** + * Is manual offline. + * + * @return true when manual offline + */ + public boolean isManualOffline() { + return UpstreamManualStatusEnum.isForceOffline(manualStatus); + } /** * Gets last health timestamp. @@ -519,6 +553,7 @@ public final class Upstream { + ", url='" + url + ", weight=" + weight + ", status=" + status + + ", manualStatus='" + manualStatus + '\'' + ", timestamp=" + timestamp + ", warmup=" + warmup + ", group='" + group @@ -581,6 +616,11 @@ public final class Upstream { */ private boolean healthCheckEnabled = true; + /** + * manual status. + */ + private String manualStatus; + /** * no args constructor. */ @@ -707,5 +747,16 @@ public final class Upstream { return this; } + /** + * build manualStatus. + * + * @param manualStatus manualStatus + * @return this builder + */ + public Builder manualStatus(final String manualStatus) { + this.manualStatus = manualStatus; + return this; + } + } } diff --git a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactory.java b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactory.java index 291039da43..a8702cc7a2 100644 --- a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactory.java +++ b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactory.java @@ -23,6 +23,7 @@ import org.apache.shenyu.loadbalancer.spi.LoadBalancer; import org.apache.shenyu.spi.ExtensionLoader; import java.util.List; +import java.util.Objects; /** * The type Load balance Factory. @@ -41,7 +42,17 @@ public final class LoadBalancerFactory { * @return the upstream */ public static Upstream selector(final List<Upstream> upstreamList, final String algorithm, final LoadBalanceData data) { + if (Objects.isNull(upstreamList)) { + return null; + } + List<Upstream> availableUpstreamList = upstreamList.stream() + .filter(Objects::nonNull) + .filter(upstream -> !upstream.isManualOffline()) + .toList(); + if (availableUpstreamList.isEmpty()) { + return null; + } LoadBalancer loadBalance = ExtensionLoader.getExtensionLoader(LoadBalancer.class).getJoin(algorithm); - return loadBalance.select(upstreamList, data); + return loadBalance.select(availableUpstreamList, data); } } diff --git a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactoryTest.java b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactoryTest.java index e7b7f2f280..46bfd6fd77 100644 --- a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactoryTest.java +++ b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/factory/LoadBalancerFactoryTest.java @@ -18,6 +18,7 @@ package org.apache.shenyu.loadbalancer.factory; import org.apache.shenyu.common.enums.LoadBalanceEnum; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import org.apache.shenyu.loadbalancer.entity.LoadBalanceData; import org.apache.shenyu.loadbalancer.entity.Upstream; import org.junit.jupiter.api.Test; @@ -30,6 +31,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * The type loadBalance utils test. @@ -92,4 +94,28 @@ public final class LoadBalancerFactoryTest { }); assertEquals(12, countMap.get("upstream-10").intValue()); } + + @Test + public void selectorShouldIgnoreForceOfflineUpstream() { + List<Upstream> upstreamList = List.of( + Upstream.builder().url("upstream-offline").weight(100).manualStatus(UpstreamManualStatusEnum.FORCE_OFFLINE.name()).build(), + Upstream.builder().url("upstream-online").weight(1).manualStatus(UpstreamManualStatusEnum.NONE.name()).build() + ); + + Upstream result = LoadBalancerFactory.selector(upstreamList, LoadBalanceEnum.ROUND_ROBIN.getName(), new LoadBalanceData()); + + assertEquals("upstream-online", result.getUrl()); + } + + @Test + public void selectorShouldReturnNullWhenAllUpstreamsAreForceOffline() { + List<Upstream> upstreamList = List.of( + Upstream.builder().url("upstream-offline-1").manualStatus(UpstreamManualStatusEnum.FORCE_OFFLINE.name()).build(), + Upstream.builder().url("upstream-offline-2").manualStatus(UpstreamManualStatusEnum.FORCE_OFFLINE.name()).build() + ); + + Upstream result = LoadBalancerFactory.selector(upstreamList, LoadBalanceEnum.ROUND_ROBIN.getName(), new LoadBalanceData()); + + assertNull(result); + } } diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java index a6196b46e4..c05b4b3ab0 100644 --- a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java @@ -76,6 +76,7 @@ public class DivideUpstreamDataHandler implements DiscoveryUpstreamDataHandler { .warmup(Integer.parseInt(properties.getProperty("warmup", "10"))) .gray(Boolean.parseBoolean(properties.getProperty("gray", "false"))) .healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled", "true"))) + .manualStatus(u.getManualStatus()) .status(0 == u.getStatus()) .timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis())) .build(); diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/test/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandlerTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/test/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandlerTest.java index 184ae75b61..225faf2d0b 100644 --- a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/test/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandlerTest.java +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/test/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandlerTest.java @@ -19,6 +19,7 @@ package org.apache.shenyu.plugin.divide.handler; import org.apache.shenyu.common.dto.DiscoverySyncData; import org.apache.shenyu.common.dto.DiscoveryUpstreamData; +import org.apache.shenyu.common.enums.UpstreamManualStatusEnum; import org.apache.shenyu.common.enums.PluginEnum; import org.apache.shenyu.common.utils.UpstreamCheckUtils; import org.apache.shenyu.loadbalancer.cache.UpstreamCacheManager; @@ -74,6 +75,8 @@ public class DivideUpstreamDataHandlerTest { @AfterEach public void tearDown() { + UpstreamCacheManager.getInstance().removeByKey("handler"); + UpstreamCacheManager.getInstance().removeByKey("manual-status-handler"); mockCheckUtils.close(); } @@ -97,4 +100,21 @@ public class DivideUpstreamDataHandlerTest { public void pluginNamedTest() { assertEquals(divideUpstreamDataHandler.pluginName(), PluginEnum.DIVIDE.getName()); } + + @Test + public void handlerDiscoveryUpstreamDataShouldCarryManualStatus() { + DiscoveryUpstreamData upstreamData = DiscoveryUpstreamData.builder() + .url("manual-status-upstream") + .manualStatus(UpstreamManualStatusEnum.FORCE_OFFLINE.name()) + .dateUpdated(new Timestamp(System.currentTimeMillis())) + .build(); + DiscoverySyncData syncData = new DiscoverySyncData(); + syncData.setSelectorId("manual-status-handler"); + syncData.setUpstreamDataList(List.of(upstreamData)); + + divideUpstreamDataHandler.handlerDiscoveryUpstreamData(syncData); + + List<Upstream> result = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId("manual-status-handler"); + assertEquals(UpstreamManualStatusEnum.FORCE_OFFLINE.name(), result.get(0).getManualStatus()); + } } diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java index d2e604e8a0..46b834cc3e 100644 --- a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java @@ -74,6 +74,7 @@ public class GrpcDiscoveryUpstreamDataHandler implements DiscoveryUpstreamDataHa .weight(u.getWeight()) .status(0 == u.getStatus()) .timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis())) + .manualStatus(u.getManualStatus()) .healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled", "true"))) .build(); }).collect(Collectors.toList()); diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java index b8450f109f..f53140e6d9 100644 --- a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java @@ -30,6 +30,7 @@ import org.apache.shenyu.plugin.grpc.context.GrpcConstants; import org.apache.shenyu.plugin.grpc.loadbalance.SubChannelCopy; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -55,6 +56,9 @@ public class ShenyuPicker extends AbstractReadyPicker { LoadBalanceData data = new LoadBalanceData(); data.setIp(remoteAddressIp); Upstream upstream = LoadBalancerFactory.selector(convertUpstreamList(grpcUpstreams), cacheRuleHandle.getLoadBalance(), data); + if (Objects.isNull(upstream)) { + return null; + } if (StringUtils.isBlank(upstream.getUrl()) && StringUtils.isBlank(upstream.getGroup()) && StringUtils.isBlank(upstream.getVersion())) { return randomPicker.pick(list); } @@ -71,6 +75,7 @@ public class ShenyuPicker extends AbstractReadyPicker { .weight(u.getWeight()) .status(u.isStatus()) .timestamp(u.getTimestamp()) + .manualStatus(u.getManualStatus()) .healthCheckEnabled(u.isHealthCheckEnabled()) .build()).collect(Collectors.toList()); } diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java index 923cdf67c5..7056be86e9 100644 --- a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java @@ -68,6 +68,7 @@ public class WebSocketUpstreamDataHandler implements DiscoveryUpstreamDataHandle .weight(u.getWeight()) .warmup(Integer.parseInt(properties.getProperty("warmup", "10"))) .healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled", "true"))) + .manualStatus(u.getManualStatus()) .status(0 == u.getStatus()) .timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis())) .build();
